Save This Page
Home » cocoon-2.1.11-src » org.apache » cocoon » transformation » [javadoc | source]
    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
   10    *
   11    * Unless required by applicable law or agreed to in writing, software
   12    * distributed under the License is distributed on an "AS IS" BASIS,
   13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14    * See the License for the specific language governing permissions and
   15    * limitations under the License.
   16    */
   17   package org.apache.cocoon.transformation;
   18   
   19   import org.apache.avalon.framework.activity.Disposable;
   20   import org.apache.avalon.framework.configuration.Configurable;
   21   import org.apache.avalon.framework.configuration.Configuration;
   22   import org.apache.avalon.framework.configuration.ConfigurationException;
   23   import org.apache.avalon.framework.parameters.Parameters;
   24   import org.apache.avalon.framework.service.ServiceException;
   25   import org.apache.avalon.framework.service.ServiceManager;
   26   import org.apache.avalon.framework.service.Serviceable;
   27   
   28   import org.apache.cocoon.ProcessingException;
   29   import org.apache.cocoon.environment.Context;
   30   import org.apache.cocoon.environment.ObjectModelHelper;
   31   import org.apache.cocoon.environment.Request;
   32   import org.apache.cocoon.environment.Response;
   33   import org.apache.cocoon.environment.SourceResolver;
   34   import org.apache.cocoon.transformation.helpers.ParametersRecorder;
   35   import org.apache.cocoon.transformation.helpers.TextRecorder;
   36   import org.apache.cocoon.util.ClassUtils;
   37   import org.apache.cocoon.util.TraxErrorHandler;
   38   import org.apache.cocoon.xml.AttributesImpl;
   39   import org.apache.cocoon.xml.ImmutableAttributesImpl;
   40   import org.apache.cocoon.xml.IncludeXMLConsumer;
   41   import org.apache.cocoon.xml.SaxBuffer;
   42   import org.apache.cocoon.xml.XMLConsumer;
   43   import org.apache.cocoon.xml.XMLUtils;
   44   import org.apache.cocoon.xml.dom.DOMBuilder;
   45   
   46   import org.apache.excalibur.source.SourceParameters;
   47   import org.apache.excalibur.xml.sax.XMLizable;
   48   import org.w3c.dom.Document;
   49   import org.w3c.dom.DocumentFragment;
   50   import org.w3c.dom.Node;
   51   import org.xml.sax.Attributes;
   52   import org.xml.sax.ContentHandler;
   53   import org.xml.sax.Locator;
   54   import org.xml.sax.SAXException;
   55   import org.xml.sax.ext.LexicalHandler;
   56   
   57   import javax.xml.transform.TransformerFactory;
   58   import javax.xml.transform.sax.SAXTransformerFactory;
   59   import java.io.IOException;
   60   import java.util.ArrayList;
   61   import java.util.Iterator;
   62   import java.util.List;
   63   import java.util.Map;
   64   import java.util.Properties;
   65   import java.util.Stack;
   66   
   67   /**
   68    * This class is the basis for all transformers. It provides various useful
   69    * methods and hooks for implementing own custom transformers.
   70    *
   71    * <p>The basic behaviour of each transformer consists of the following four
   72    * parts:</p>
   73    * <ul>
   74    * <li>Listen for specific events with a given namespace</li>
   75    * <li>Collect information via these events</li>
   76    * <li>Process the information</li>
   77    * <li>Create new events from the processed information</li>
   78    * </ul>
   79    *
   80    * <p>For all these four purposes the AbstractSAXTransformer offers some
   81    * powerful methods and hooks:</p>
   82    *
   83    * <h3>Namespace handling</h3>
   84    * By setting the instance variable namespaceURI to the namespace the
   85    * events are filtered and only events with this namespace are send to
   86    * the two hooks: <code>startTransformingElement</code> and
   87    * <code>endTransformingElement</code>. It is possible to override the default
   88    * namespace for the transformer by specifying the parameter "namespaceURI"
   89    * in the pipeline. This avoids possible namespace collisions.
   90    *
   91    * <h3>Recording of information</h3>
   92    * There are several methods for recording information, e.g. startRecording(),
   93    * startTextRecording() etc. These methods collect information from the xml
   94    * stream for further processing.
   95    *
   96    * <h3>Creating new events</h3>
   97    * New events can be easily created with the <code>sendEvents()</code>
   98    * method, the <code>sendStartElementEvent()</code> methods, the
   99    * <code>sendEndElementEvent()</code> method or the
  100    * <code>sendTextEvent()</code> method.
  101    *
  102    * <h3>Initialization</h3>
  103    * Before the document is processed the <code>setupTransforming</code> hook
  104    * is invoked.
  105    *
  106    * @author <a href="mailto:cziegeler@s-und-n.de">Carsten Ziegeler</a>
  107    * @version $Id: AbstractSAXTransformer.java 433543 2006-08-22 06:22:54Z crossley $
  108   */
  109   public abstract class AbstractSAXTransformer extends AbstractTransformer
  110                                                implements Serviceable, Configurable, Disposable {
  111   
  112       /**
  113        * Empty immutable attributes (for performance). Use them
  114        * whenever creating an element with no attributes.
  115        */
  116       protected static final Attributes EMPTY_ATTRIBUTES = XMLUtils.EMPTY_ATTRIBUTES;
  117   
  118       /**
  119        * The trax <code>TransformerFactory</code> used by this transformer.
  120        */
  121       private SAXTransformerFactory tfactory;
  122   
  123       /**
  124        * Controlls SAX event handling.
  125        * If set to true all whitespace events are ignored.
  126        */
  127       protected boolean ignoreWhitespaces;
  128   
  129       /**
  130        * Controlls SAX event handling.
  131        * If set to true all characters events containing only whitespaces
  132        * are ignored.
  133        */
  134       protected boolean ignoreEmptyCharacters;
  135   
  136       /**
  137        * Controlls SAX event handling.
  138        * If this is incremented all events are not forwarded to the next
  139        * pipeline component, but the hooks are still called.
  140        */
  141       protected int ignoreEventsCount;
  142   
  143       /**
  144        * Controlls SAX event handling.
  145        * If this is greater than zero, the hooks are not called. Attention,
  146        * make sure, that you decrement this counter properly as your hooks are
  147        * not called anymore!
  148        */
  149       protected int ignoreHooksCount;
  150   
  151       /**
  152        * The namespace used by the transformer for the SAX events filtering.
  153        * This either equals to the {@link #defaultNamespaceURI} or to the value
  154        * set by the <code>namespaceURI</code> sitemap parameter for the pipeline.
  155        * Must never be null.
  156        */
  157       protected String namespaceURI;
  158   
  159       /**
  160        * This is the default namespace used by the transformer.
  161        * Implementations should set its value in the constructor.
  162        * Must never be null.
  163        */
  164       protected String defaultNamespaceURI;
  165   
  166       /**
  167        * A stack for collecting information.
  168        * The stack is important for collection information especially when
  169        * the tags can be nested.
  170        */
  171       protected final Stack stack = new Stack();
  172   
  173       /**
  174        * The stack of current used recorders
  175        */
  176       protected final Stack recorderStack = new Stack();
  177   
  178       /**
  179        * The current Request object
  180        */
  181       protected Request request;
  182   
  183       /**
  184        * The current Response object
  185        */
  186       protected Response response;
  187   
  188       /**
  189        * The current Context object
  190        */
  191       protected Context context;
  192   
  193       /**
  194        * The current objectModel of the environment
  195        */
  196       protected Map objectModel;
  197   
  198       /**
  199        * The parameters specified in the sitemap
  200        */
  201       protected Parameters parameters;
  202   
  203       /**
  204        * The source attribute specified in the sitemap
  205        */
  206       protected String source;
  207   
  208       /**
  209        * The Avalon ServiceManager for getting Components
  210        */
  211       protected ServiceManager manager;
  212   
  213       /**
  214        * The SourceResolver for this request
  215        */
  216       protected SourceResolver resolver;
  217   
  218       /**
  219        * Are we already initialized for the current request?
  220        */
  221       private boolean isInitialized;
  222   
  223       /**
  224        * Empty attributes (for performance). This can be used
  225        * do create own attributes, but make sure to clean them
  226        * afterwords.
  227        * @deprecated Use {@link AbstractSAXTransformer#EMPTY_ATTRIBUTES}.
  228        */
  229       protected Attributes emptyAttributes = EMPTY_ATTRIBUTES;
  230   
  231       /**
  232        * The namespaces and their prefixes
  233        */
  234       private final List namespaces = new ArrayList(5);
  235   
  236       /**
  237        * The current prefix for our namespace
  238        */
  239       private String ourPrefix;
  240   
  241       //
  242       // Lifecycle
  243       //
  244   
  245       /* (non-Javadoc)
  246        * @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager)
  247        */
  248       public void service(ServiceManager manager) throws ServiceException {
  249           this.manager = manager;
  250       }
  251   
  252       /* (non-Javadoc)
  253        * @see Configurable#configure(Configuration)
  254        */
  255       public void configure(Configuration configuration) throws ConfigurationException {
  256           String tFactoryClass = configuration.getChild("transformer-factory").getValue(null);
  257           if (tFactoryClass != null) {
  258               try {
  259                   this.tfactory = (SAXTransformerFactory) ClassUtils.newInstance(tFactoryClass);
  260                   if (getLogger().isDebugEnabled()) {
  261                       getLogger().debug("Using transformer factory " + tFactoryClass);
  262                   }
  263               } catch (Exception e) {
  264                   throw new ConfigurationException("Cannot load transformer factory " + tFactoryClass, e);
  265               }
  266           } else {
  267               // Standard TrAX behaviour
  268               this.tfactory = (SAXTransformerFactory) TransformerFactory.newInstance();
  269           }
  270           tfactory.setErrorListener(new TraxErrorHandler(getLogger()));
  271       }
  272   
  273       /* (non-Javadoc)
  274        * @see org.apache.cocoon.sitemap.SitemapModelComponent#setup(SourceResolver, Map, String, Parameters)
  275        */
  276       public void setup(SourceResolver resolver,
  277                         Map            objectModel,
  278                         String         src,
  279                         Parameters     params)
  280       throws ProcessingException, SAXException, IOException {
  281   
  282           if (getLogger().isDebugEnabled()) {
  283               getLogger().debug("Setup resolver=" + resolver +
  284                                 ", objectModel=" + objectModel +
  285                                 ", src=" + src +
  286                                 ", parameters=" + params);
  287           }
  288   
  289           // defaultNamespaceURI should never be null
  290           if (this.defaultNamespaceURI == null) {
  291               this.defaultNamespaceURI = "";
  292           }
  293           this.objectModel = objectModel;
  294   
  295           this.request = ObjectModelHelper.getRequest(objectModel);
  296           this.response = ObjectModelHelper.getResponse(objectModel);
  297           this.context = ObjectModelHelper.getContext(objectModel);
  298           this.resolver = resolver;
  299           this.parameters = params;
  300           this.source = src;
  301           this.isInitialized = false;
  302   
  303           // get the current namespace
  304           this.namespaceURI = params.getParameter("namespaceURI",
  305                                                   this.defaultNamespaceURI);
  306   
  307           this.ignoreHooksCount = 0;
  308           this.ignoreEventsCount = 0;
  309           this.ignoreWhitespaces = true;
  310           this.ignoreEmptyCharacters = false;
  311       }
  312   
  313       /* (non-Javadoc)
  314        * @see org.apache.avalon.excalibur.pool.Recyclable#recycle()
  315        */
  316       public void recycle() {
  317           this.namespaceURI = null;
  318           this.objectModel = null;
  319           this.request = null;
  320           this.response = null;
  321           this.context = null;
  322           this.resolver = null;
  323           this.stack.clear();
  324           this.recorderStack.clear();
  325           this.parameters = null;
  326           this.source = null;
  327           this.namespaces.clear();
  328           this.ourPrefix = null;
  329   
  330           super.recycle();
  331       }
  332   
  333       public void dispose() {
  334           this.manager = null;
  335       }
  336   
  337       //
  338       // SAX ContentHandler methods
  339       //
  340   
  341       /**
  342        * Process the SAX event.
  343        * @see ContentHandler#setDocumentLocator
  344        */
  345       public void setDocumentLocator(Locator locator) {
  346           if (this.ignoreEventsCount == 0) {
  347               super.setDocumentLocator(locator);
  348           }
  349       }
  350   
  351       /**
  352        * Process the SAX event. A new document is processed. The hook method
  353        * {@link #setupTransforming} is invoked.
  354        * @see ContentHandler#startDocument
  355        */
  356       public void startDocument()
  357       throws SAXException {
  358           if (!this.isInitialized) {
  359               try {
  360                   setupTransforming();
  361               } catch (ProcessingException e) {
  362                   throw new SAXException("ProcessingException: " + e, e);
  363               } catch (IOException e) {
  364                   throw new SAXException("IOException: " + e, e);
  365               }
  366               this.isInitialized = true;
  367           }
  368   
  369           if (this.ignoreEventsCount == 0) {
  370               super.startDocument();
  371           }
  372       }
  373   
  374       /**
  375        * Process the SAX event. The processing of the document is finished.
  376        * @see org.xml.sax.ContentHandler#endDocument
  377        */
  378       public void endDocument()
  379       throws SAXException {
  380           if (this.ignoreEventsCount == 0) {
  381               super.endDocument();
  382           }
  383       }
  384   
  385       /**
  386        * Process the SAX event.
  387        * @see org.xml.sax.ContentHandler#startPrefixMapping
  388        */
  389       public void startPrefixMapping(String prefix, String uri)
  390       throws SAXException {
  391           if (prefix != null) {
  392               this.namespaces.add(new String[] {prefix, uri});
  393           }
  394           if (namespaceURI.equals(uri)) {
  395               this.ourPrefix = prefix;
  396           }
  397           if (this.ignoreEventsCount == 0) {
  398               super.startPrefixMapping(prefix, uri);
  399           }
  400       }
  401   
  402       /**
  403        * Process the SAX event.
  404        * @see org.xml.sax.ContentHandler#endPrefixMapping
  405        */
  406       public void endPrefixMapping(String prefix)
  407       throws SAXException {
  408   
  409           if (prefix != null) {
  410               // Find and remove the namespace prefix
  411               boolean found = false;
  412               for (int i = this.namespaces.size() - 1; i >= 0; i--) {
  413                   final String[] prefixAndUri = (String[]) this.namespaces.get(i);
  414                   if (prefixAndUri[0].equals(prefix)) {
  415                       this.namespaces.remove(i);
  416                       found = true;
  417                       break;
  418                   }
  419               }
  420               if (!found) {
  421                   throw new SAXException("Namespace for prefix '" + prefix + "' not found.");
  422               }
  423   
  424               if (prefix.equals(this.ourPrefix)) {
  425                   // Reset our current prefix
  426                   this.ourPrefix = null;
  427   
  428                   // Now search if we have a different prefix for our namespace
  429                   for (int i = this.namespaces.size() - 1; i >= 0; i--) {
  430                       final String[] prefixAndUri = (String[]) this.namespaces.get(i);
  431                       if (namespaceURI.equals(prefixAndUri[1])) {
  432                           this.ourPrefix = prefixAndUri[0];
  433                           break;
  434                       }
  435                   }
  436               }
  437           }
  438   
  439           if (this.ignoreEventsCount == 0) {
  440               super.endPrefixMapping(prefix);
  441           }
  442       }
  443   
  444       /**
  445        * Process the SAX event. The namespace of the event is checked.
  446        * If it is the defined namespace for this transformer,
  447        * the {@link #startTransformingElement} hook is called.
  448        * @see org.xml.sax.ContentHandler#startElement
  449        */
  450       public void startElement(String uri,
  451                                String name,
  452                                String raw,
  453                                Attributes attr)
  454       throws SAXException {
  455           if (namespaceURI.equals(uri) && ignoreHooksCount == 0) {
  456               // this is our namespace:
  457               try {
  458                   startTransformingElement(uri, name, raw, attr);
  459               } catch (ProcessingException e) {
  460                   throw new SAXException("ProcessingException: " + e, e);
  461               } catch (IOException e) {
  462                   throw new SAXException("IOException occured during processing: " + e, e);
  463               }
  464           } else {
  465               if (ignoreEventsCount == 0) {
  466                   super.startElement(uri, name, raw, attr);
  467               }
  468           }
  469       }
  470   
  471       /**
  472        * Process the SAX event. The namespace of the event is checked.
  473        * If it is the defined namespace for this transformer,
  474        * the {@link #endTransformingElement} hook is called.
  475        * @see org.xml.sax.ContentHandler#endElement
  476        */
  477       public void endElement(String uri, String name, String raw)
  478       throws SAXException {
  479           if (namespaceURI.equals(uri) && this.ignoreHooksCount == 0) {
  480               // this is our namespace:
  481               try {
  482                   endTransformingElement(uri, name, raw);
  483               } catch (ProcessingException e) {
  484                   throw new SAXException("ProcessingException: " + e, e);
  485               } catch (IOException e) {
  486                   throw new SAXException("IOException occured during processing: " + e, e);
  487               }
  488           } else {
  489               if (ignoreEventsCount == 0) {
  490                   super.endElement(uri, name, raw);
  491               }
  492           }
  493       }
  494   
  495       /**
  496        * Process the SAX event.
  497        * @see org.xml.sax.ContentHandler#characters
  498        */
  499       public void characters(char[] p0, int p1, int p2)
  500       throws SAXException {
  501           if (this.ignoreEventsCount == 0) {
  502               if (this.ignoreEmptyCharacters) {
  503                   String value = new String(p0, p1, p2);
  504                   if (value.trim().length() > 0) {
  505                       super.characters(p0, p1, p2);
  506                   }
  507               } else {
  508                   super.characters(p0, p1, p2);
  509               }
  510           }
  511       }
  512   
  513       /**
  514        * Process the SAX event.
  515        * @see org.xml.sax.ContentHandler#ignorableWhitespace
  516        */
  517       public void ignorableWhitespace(char[] p0, int p1, int p2)
  518       throws SAXException {
  519           if (ignoreWhitespaces == false && ignoreEventsCount == 0) {
  520               super.ignorableWhitespace(p0, p1, p2);
  521           }
  522       }
  523   
  524       /**
  525        * Process the SAX event.
  526        * @see ContentHandler#processingInstruction
  527        */
  528       public void processingInstruction(String target, String data)
  529       throws SAXException {
  530           if (this.ignoreEventsCount == 0) {
  531               super.processingInstruction(target, data);
  532           }
  533       }
  534   
  535       /**
  536        * Process the SAX event.
  537        * @see ContentHandler#skippedEntity
  538        */
  539       public void skippedEntity(String name)
  540       throws SAXException {
  541           if (this.ignoreEventsCount == 0) {
  542               super.skippedEntity(name);
  543           }
  544       }
  545   
  546       //
  547       // SAX LexicalHandler methods
  548       //
  549   
  550       /**
  551        * @see LexicalHandler#startDTD
  552        */
  553       public void startDTD(String name, String public_id, String system_id)
  554       throws SAXException {
  555           if (this.ignoreEventsCount == 0) {
  556               super.startDTD(name, public_id, system_id);
  557           }
  558       }
  559   
  560       /**
  561        * @see LexicalHandler#endDTD
  562        */
  563       public void endDTD() throws SAXException {
  564           if (this.ignoreEventsCount == 0) {
  565               super.endDTD();
  566           }
  567       }
  568   
  569       /**
  570        * @see LexicalHandler#startEntity
  571        */
  572       public void startEntity (String name)
  573       throws SAXException {
  574           if (this.ignoreEventsCount == 0) {
  575               super.startEntity(name);
  576           }
  577       }
  578   
  579       /**
  580        * @see LexicalHandler#endEntity
  581        */
  582       public void endEntity (String name)
  583       throws SAXException {
  584           if (this.ignoreEventsCount == 0) {
  585               super.endEntity(name);
  586           }
  587       }
  588   
  589       /**
  590        * @see LexicalHandler#startCDATA
  591        */
  592       public void startCDATA() throws SAXException {
  593           if (this.ignoreEventsCount == 0) {
  594               super.startCDATA();
  595           }
  596       }
  597   
  598       /**
  599        * @see LexicalHandler#endCDATA
  600        */
  601       public void endCDATA() throws SAXException {
  602           if (this.ignoreEventsCount == 0) {
  603               super.endCDATA();
  604           }
  605       }
  606   
  607       /**
  608        * @see LexicalHandler#comment
  609        */
  610       public void comment(char ary[], int start, int length)
  611       throws SAXException {
  612           if (this.ignoreEventsCount == 0) {
  613               super.comment(ary, start, length);
  614           }
  615       }
  616   
  617   
  618       /*
  619        * Recording of events.
  620        * With this method all events are not forwarded to the next component in the pipeline.
  621        * They are recorded to create a document fragment.
  622        */
  623   
  624       private LexicalHandler   originalLexicalHandler;
  625       private ContentHandler   originalContentHandler;
  626   
  627       /**
  628        * Add a new recorder to the recording chain.
  629        * Do not invoke this method directly.
  630        */
  631       protected void addRecorder(XMLConsumer recorder) {
  632           if (this.recorderStack.empty()) {
  633               // redirect if first (top) recorder
  634               this.originalLexicalHandler = this.lexicalHandler;
  635               this.originalContentHandler = this.contentHandler;
  636           }
  637           setContentHandler(recorder);
  638           setLexicalHandler(recorder);
  639           this.recorderStack.push(recorder);
  640       }
  641   
  642       /**
  643        * Remove a recorder from the recording chain.
  644        * Do not invoke this method directly.
  645        */
  646       protected Object removeRecorder() {
  647           Object recorder = this.recorderStack.pop();
  648           if (this.recorderStack.empty() == true) {
  649               // undo redirect if no recorder any more
  650               setContentHandler(originalContentHandler);
  651               setLexicalHandler(originalLexicalHandler);
  652               this.originalLexicalHandler = null;
  653               this.originalContentHandler = null;
  654           } else {
  655               XMLConsumer next = (XMLConsumer) recorderStack.peek();
  656               setContentHandler(next);
  657               setLexicalHandler(next);
  658           }
  659   
  660           return recorder;
  661       }
  662   
  663       /**
  664        * Start recording of SAX events.
  665        * All incoming events are recorded and not forwarded. The resulting
  666        * XMLizable can be obtained by the matching {@link #endSAXRecording} call.
  667        * @since 2.1.5
  668        */
  669       public void startSAXRecording()
  670       throws SAXException {
  671           addRecorder(new SaxBuffer());
  672           sendStartPrefixMapping();
  673       }
  674   
  675       /**
  676        * Stop recording of SAX events.
  677        * This method returns the resulting XMLizable.
  678        * @since 2.1.5
  679        */
  680       public XMLizable endSAXRecording()
  681       throws SAXException {
  682           sendEndPrefixMapping();
  683           return (XMLizable) removeRecorder();
  684       }
  685   
  686       /**
  687        * Start recording of a text.
  688        * No events forwarded, and all characters events
  689        * are collected into a string.
  690        */
  691       public void startTextRecording()
  692       throws SAXException {
  693           if (getLogger().isDebugEnabled()) {
  694               getLogger().debug("Start text recording");
  695           }
  696           addRecorder(new TextRecorder());
  697           sendStartPrefixMapping();
  698       }
  699   
  700       /**
  701        * Stop recording of text and return the recorded information.
  702        * @return The String, trimmed.
  703        */
  704       public String endTextRecording()
  705       throws SAXException {
  706           sendEndPrefixMapping();
  707   
  708           TextRecorder recorder = (TextRecorder) removeRecorder();
  709           String text = recorder.getText();
  710           if (getLogger().isDebugEnabled()) {
  711               getLogger().debug("End text recording. Text=" + text);
  712           }
  713           return text;
  714       }
  715   
  716       /**
  717        * Start recording of serialized xml
  718        * All events are converted to an xml string which can be retrieved by
  719        * endSerializedXMLRecording.
  720        * @param format The format for the serialized output. If <CODE>null</CODE>
  721        *               is specified, the default format is used.
  722        */
  723       public void startSerializedXMLRecording(Properties format)
  724       throws SAXException {
  725           if (getLogger().isDebugEnabled()) {
  726               getLogger().debug("Start serialized XML recording. Format=" + format);
  727           }
  728           this.stack.push(format == null? XMLUtils.createPropertiesForXML(false): format);
  729           startSAXRecording();
  730       }
  731   
  732       /**
  733        * Return the serialized xml string.
  734        * @return A string containing the recorded xml information, formatted by
  735        * the properties passed to the corresponding startSerializedXMLRecording().
  736        */
  737       public String endSerializedXMLRecording()
  738       throws SAXException, ProcessingException {
  739           XMLizable xml = endSAXRecording();
  740           String text = XMLUtils.serialize(xml, (Properties) this.stack.pop());
  741           if (getLogger().isDebugEnabled()) {
  742               getLogger().debug("End serialized XML recording. XML=" + text);
  743           }
  744           return text;
  745       }
  746   
  747       /**
  748        * Start recording of parameters.
  749        * All events are not forwarded and the incoming xml is converted to
  750        * parameters. Each toplevel node is a parameter and its text subnodes
  751        * form the value.
  752        * The Parameters can eiter be retrieved by endParametersRecording().
  753        */
  754       public void startParametersRecording()
  755       throws SAXException {
  756           if (getLogger().isDebugEnabled()) {
  757               getLogger().debug("Start parameters recording");
  758           }
  759           addRecorder(new ParametersRecorder());
  760           sendStartPrefixMapping();
  761       }
  762   
  763       /**
  764        * End recording of parameters
  765        * If source is null a new parameters object is created, otherwise
  766        * the parameters are added to this object.
  767        * @param source An optional parameters object.
  768        * @return The object containing all parameters.
  769        */
  770       public SourceParameters endParametersRecording(Parameters source)
  771       throws SAXException {
  772           sendEndPrefixMapping();
  773   
  774           ParametersRecorder recorder = (ParametersRecorder) this.removeRecorder();
  775           SourceParameters parameters = recorder.getParameters(source);
  776           if (getLogger().isDebugEnabled()) {
  777               getLogger().debug("End parameters recording. Parameters=" + parameters);
  778           }
  779           return parameters;
  780       }
  781   
  782       /**
  783        * End recording of parameters
  784        * If source is null a new parameters object is created, otherwise
  785        * the parameters are added to this object.
  786        * @param source An optional parameters object.
  787        * @return The object containing all parameters.
  788        */
  789       public SourceParameters endParametersRecording(SourceParameters source)
  790       throws SAXException {
  791           sendEndPrefixMapping();
  792   
  793           ParametersRecorder recorder = (ParametersRecorder) removeRecorder();
  794           SourceParameters parameters = recorder.getParameters(source);
  795           if (getLogger().isDebugEnabled()) {
  796               getLogger().debug("End parameters recording. Parameters=" + parameters);
  797           }
  798           return parameters;
  799       }
  800   
  801       /**
  802        * Start DOM DocumentFragment recording.
  803        * All incoming events are recorded and not forwarded. The resulting
  804        * DocumentFragment can be obtained by the matching {@link #endRecording} call.
  805        */
  806       public void startRecording()
  807       throws SAXException {
  808           if (getLogger().isDebugEnabled()) {
  809               getLogger().debug("Start recording");
  810           }
  811           DOMBuilder builder = new DOMBuilder(this.tfactory);
  812           addRecorder(builder);
  813           builder.startDocument();
  814           builder.startElement("", "cocoon", "cocoon", EMPTY_ATTRIBUTES);
  815           sendStartPrefixMapping();
  816       }
  817   
  818       /**
  819        * Stop DOM DocumentFragment recording.
  820        * This method returns the resulting DocumentFragment, normalized.
  821        */
  822       public DocumentFragment endRecording()
  823       throws SAXException {
  824           sendEndPrefixMapping();
  825   
  826           DOMBuilder builder = (DOMBuilder) removeRecorder();
  827           builder.endElement("", "cocoon", "cocoon");
  828           builder.endDocument();
  829   
  830           // Create Document Fragment
  831           final Document doc = builder.getDocument();
  832           final DocumentFragment fragment = doc.createDocumentFragment();
  833           final Node root = doc.getDocumentElement();
  834   
  835           // Remove empty text nodes and collapse neighbouring text nodes
  836           root.normalize();
  837   
  838           // Move all nodes into the fragment
  839           boolean space = true;
  840           while (root.hasChildNodes()) {
  841               Node child = root.getFirstChild();
  842               root.removeChild(child);
  843   
  844               // Leave out leading whitespace nodes
  845               // FIXME: Why leading spaces are trimmed at all? Why not trailing spaces?
  846               if (space && child.getNodeType() == Node.TEXT_NODE
  847                       && child.getNodeValue().trim().length() == 0) {
  848                   continue;
  849               }
  850               space = false;
  851   
  852               fragment.appendChild(child);
  853           }
  854   
  855           if (getLogger().isDebugEnabled()) {
  856               Object serializedXML = null;
  857               try {
  858                   serializedXML = fragment == null? "null": XMLUtils.serializeNode(fragment, XMLUtils.createPropertiesForXML(false));
  859               } catch (ProcessingException ignore) {
  860                   serializedXML = fragment;
  861               }
  862               getLogger().debug("End recording. Fragment=" + serializedXML);
  863           }
  864   
  865           return fragment;
  866       }
  867   
  868       //
  869       // Hooks
  870       //
  871   
  872       /**
  873        * Setup the transformation of an xml document.
  874        * This method is called just before the transformation (sending of sax events)
  875        * starts. It should be used to initialize setup parameter depending on the
  876        * object modell.
  877        */
  878       public void setupTransforming()
  879       throws IOException, ProcessingException, SAXException {
  880           if (getLogger().isDebugEnabled()) {
  881               getLogger().debug("setupTransforming");
  882           }
  883           this.stack.clear();
  884           this.recorderStack.clear();
  885           this.ignoreWhitespaces = true;
  886           this.ignoreEmptyCharacters = false;
  887       }
  888   
  889       /**
  890        * Start processing elements of our namespace.
  891        * This hook is invoked for each sax event with our namespace.
  892        * @param uri The namespace of the element.
  893        * @param name The local name of the element.
  894        * @param raw The qualified name of the element.
  895        * @param attr The attributes of the element.
  896        */
  897       public void startTransformingElement(String uri,
  898                                            String name,
  899                                            String raw,
  900                                            Attributes attr)
  901       throws ProcessingException, IOException, SAXException {
  902           if (this.ignoreEventsCount == 0) {
  903               super.startElement(uri, name, raw, attr);
  904           }
  905       }
  906   
  907       /**
  908        * Start processing elements of our namespace.
  909        * This hook is invoked for each sax event with our namespace.
  910        * @param uri The namespace of the element.
  911        * @param name The local name of the element.
  912        * @param raw The qualified name of the element.
  913        */
  914       public void endTransformingElement(String uri,
  915                                          String name,
  916                                          String raw)
  917       throws ProcessingException, IOException, SAXException {
  918           if (this.ignoreEventsCount == 0) {
  919               super.endElement(uri, name, raw);
  920           }
  921       }
  922   
  923       /**
  924        * Send SAX events to the next pipeline component.
  925        * The characters event for the given text is send to the next
  926        * component in the current pipeline.
  927        * @param text The string containing the information.
  928        */
  929       public void sendTextEvent(String text)
  930       throws SAXException {
  931           characters(text.toCharArray(), 0, text.length());
  932       }
  933   
  934       /**
  935        * Send SAX events to the next pipeline component.
  936        * The startElement event for the given element is send
  937        * to the next component in the current pipeline.
  938        * The element has no namespace and no attributes
  939        * @param localname The name of the event.
  940        */
  941       public void sendStartElementEvent(String localname)
  942       throws SAXException {
  943           startElement("", localname, localname, EMPTY_ATTRIBUTES);
  944       }
  945   
  946       /**
  947        * Send SAX events to the next pipeline component.
  948        * The startElement event for the given element is send
  949        * to the next component in the current pipeline.
  950        * The element has the namespace of the transformer,
  951        * but not attributes
  952        * @param localname The name of the event.
  953        */
  954       public void sendStartElementEventNS(String localname)
  955       throws SAXException {
  956           startElement(this.namespaceURI,
  957                        localname, this.ourPrefix + ':' + localname, EMPTY_ATTRIBUTES);
  958       }
  959   
  960       /**
  961        * Send SAX events to the next pipeline component.
  962        * The startElement event for the given element is send
  963        * to the next component in the current pipeline.
  964        * The element has no namespace.
  965        * @param localname The name of the event.
  966        * @param attr The Attributes of the element
  967        */
  968       public void sendStartElementEvent(String localname, Attributes attr)
  969       throws SAXException {
  970           startElement("", localname, localname, attr);
  971       }
  972   
  973       /**
  974        * Send SAX events to the next pipeline component.
  975        * The startElement event for the given element is send
  976        * to the next component in the current pipeline.
  977        * The element has the namespace of the transformer.
  978        * @param localname The name of the event.
  979        * @param attr The Attributes of the element
  980        */
  981       public void sendStartElementEventNS(String localname, Attributes attr)
  982       throws SAXException {
  983           startElement(this.namespaceURI,
  984                        localname, this.ourPrefix + ':' + localname, attr);
  985       }
  986   
  987       /**
  988        * Send SAX events to the next pipeline component.
  989        * The endElement event for the given element is send
  990        * to the next component in the current pipeline.
  991        * The element has no namespace.
  992        * @param localname The name of the event.
  993        */
  994       public void sendEndElementEvent(String localname)
  995       throws SAXException {
  996           endElement("", localname, localname);
  997       }
  998   
  999       /**
 1000        * Send SAX events to the next pipeline component.
 1001        * The endElement event for the given element is send
 1002        * to the next component in the current pipeline.
 1003        * The element has the namespace of the transformer.
 1004        * @param localname The name of the event.
 1005        */
 1006       public void sendEndElementEventNS(String localname)
 1007       throws SAXException {
 1008           endElement(this.namespaceURI,
 1009                      localname, this.ourPrefix + ':' + localname);
 1010       }
 1011   
 1012       /**
 1013        * Send SAX events to the next pipeline component.
 1014        * The node is parsed and the events are send to
 1015        * the next component in the pipeline.
 1016        * @param node The tree to be included.
 1017        */
 1018       public void sendEvents(Node node)
 1019       throws SAXException {
 1020           IncludeXMLConsumer.includeNode(node, this, this);
 1021       }
 1022   
 1023       /**
 1024        * Send SAX events for the <code>SourceParameters</code>.
 1025        * For each parametername/value pair an element is
 1026        * created with the name of the parameter and the content
 1027        * of this element is the value.
 1028        */
 1029       public void sendParametersEvents(SourceParameters pars)
 1030       throws SAXException {
 1031   
 1032           if (pars != null) {
 1033               Iterator names = pars.getParameterNames();
 1034               while (names.hasNext()) {
 1035                   final String currentName = (String)names.next();
 1036                   Iterator values = pars.getParameterValues(currentName);
 1037                   while (values.hasNext()) {
 1038                       final String currentValue = (String)values.next();
 1039                       sendStartElementEvent(currentName);
 1040                       sendTextEvent(currentValue);
 1041                       sendEndElementEvent(currentName);
 1042                   }
 1043               }
 1044           }
 1045       }
 1046   
 1047       /**
 1048        * Send all start prefix mapping events to the current content handler
 1049        */
 1050       protected void sendStartPrefixMapping()
 1051       throws SAXException {
 1052           final int l = this.namespaces.size();
 1053           for (int i = 0; i < l; i++) {
 1054               String[] prefixAndUri = (String[]) this.namespaces.get(i);
 1055               super.contentHandler.startPrefixMapping(prefixAndUri[0], prefixAndUri[1]);
 1056           }
 1057       }
 1058   
 1059       /**
 1060        * Send all end prefix mapping events to the current content handler
 1061        */
 1062       protected void sendEndPrefixMapping()
 1063       throws SAXException {
 1064           final int l = this.namespaces.size();
 1065           for (int i = 0; i < l; i++) {
 1066               String[] prefixAndUri = (String[]) this.namespaces.get(i);
 1067               super.contentHandler.endPrefixMapping(prefixAndUri[0]);
 1068           }
 1069       }
 1070   
 1071       /**
 1072        * Find prefix mapping for the given namespace URI.
 1073        * @return Prefix mapping or null if no prefix defined
 1074        */
 1075       protected String findPrefixMapping(String uri) {
 1076           final int l = this.namespaces.size();
 1077           for (int i = 0; i < l; i++) {
 1078               String[] prefixAndUri = (String[]) this.namespaces.get(i);
 1079               if (prefixAndUri[1].equals(uri)) {
 1080                   return prefixAndUri[0];
 1081               }
 1082           }
 1083   
 1084           return null;
 1085       }
 1086   
 1087       /**
 1088        * Helper method to get a modifiable attribute set.
 1089        */
 1090       protected AttributesImpl getMutableAttributes(Attributes a) {
 1091           if ( a instanceof AttributesImpl && !(a instanceof ImmutableAttributesImpl)) {
 1092               return (AttributesImpl)a;
 1093           }
 1094           return new AttributesImpl(a);
 1095       }
 1096   }

Save This Page
Home » cocoon-2.1.11-src » org.apache » cocoon » transformation » [javadoc | source]