Home | History | Annotate | Download | only in serializer
      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one
      3  * or more contributor license agreements. See the NOTICE file
      4  * distributed with this work for additional information
      5  * regarding copyright ownership. The ASF licenses this file
      6  * to you under the Apache License, Version 2.0 (the  "License");
      7  * you may not use this file except in compliance with the License.
      8  * You may obtain a copy of the License at
      9  *
     10  *     http://www.apache.org/licenses/LICENSE-2.0
     11  *
     12  * Unless required by applicable law or agreed to in writing, software
     13  * distributed under the License is distributed on an "AS IS" BASIS,
     14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     15  * See the License for the specific language governing permissions and
     16  * limitations under the License.
     17  */
     18 /*
     19  * $Id: ToXMLSAXHandler.java 468654 2006-10-28 07:09:23Z minchau $
     20  */
     21  package org.apache.xml.serializer;
     22 
     23 import java.io.IOException;
     24 import java.io.OutputStream;
     25 import java.io.Writer;
     26 import java.util.Properties;
     27 
     28 import javax.xml.transform.Result;
     29 
     30 import org.w3c.dom.Node;
     31 import org.xml.sax.Attributes;
     32 import org.xml.sax.ContentHandler;
     33 import org.xml.sax.Locator;
     34 import org.xml.sax.SAXException;
     35 import org.xml.sax.ext.LexicalHandler;
     36 
     37 /**
     38  * This class receives notification of SAX-like events, and with gathered
     39  * information over these calls it will invoke the equivalent SAX methods
     40  * on a handler, the ultimate xsl:output method is known to be "xml".
     41  *
     42  * This class is not a public API.
     43  * @xsl.usage internal
     44  */
     45 public final class ToXMLSAXHandler extends ToSAXHandler
     46 {
     47 
     48     /**
     49      * Keeps track of whether output escaping is currently enabled
     50      */
     51     protected boolean m_escapeSetting = true;
     52 
     53     public ToXMLSAXHandler()
     54     {
     55         // default constructor (need to set content handler ASAP !)
     56         m_prefixMap = new NamespaceMappings();
     57         initCDATA();
     58     }
     59 
     60     /**
     61      * @see Serializer#getOutputFormat()
     62      */
     63     public Properties getOutputFormat()
     64     {
     65         return null;
     66     }
     67 
     68     /**
     69      * @see Serializer#getOutputStream()
     70      */
     71     public OutputStream getOutputStream()
     72     {
     73         return null;
     74     }
     75 
     76     /**
     77      * @see Serializer#getWriter()
     78      */
     79     public Writer getWriter()
     80     {
     81         return null;
     82     }
     83 
     84     /**
     85      * Do nothing for SAX.
     86      */
     87     public void indent(int n) throws SAXException
     88     {
     89     }
     90 
     91 
     92     /**
     93      * @see DOMSerializer#serialize(Node)
     94      */
     95     public void serialize(Node node) throws IOException
     96     {
     97     }
     98 
     99     /**
    100      * @see SerializationHandler#setEscaping(boolean)
    101      */
    102     public boolean setEscaping(boolean escape) throws SAXException
    103     {
    104         boolean oldEscapeSetting = m_escapeSetting;
    105         m_escapeSetting = escape;
    106 
    107         if (escape) {
    108             processingInstruction(Result.PI_ENABLE_OUTPUT_ESCAPING, "");
    109         } else {
    110             processingInstruction(Result.PI_DISABLE_OUTPUT_ESCAPING, "");
    111         }
    112 
    113         return oldEscapeSetting;
    114     }
    115 
    116     /**
    117      * @see Serializer#setOutputFormat(Properties)
    118      */
    119     public void setOutputFormat(Properties format)
    120     {
    121     }
    122 
    123     /**
    124      * @see Serializer#setOutputStream(OutputStream)
    125      */
    126     public void setOutputStream(OutputStream output)
    127     {
    128     }
    129 
    130     /**
    131      * @see Serializer#setWriter(Writer)
    132      */
    133     public void setWriter(Writer writer)
    134     {
    135     }
    136 
    137     /**
    138      * @see org.xml.sax.ext.DeclHandler#attributeDecl(String, String, String, String, String)
    139      */
    140     public void attributeDecl(
    141         String arg0,
    142         String arg1,
    143         String arg2,
    144         String arg3,
    145         String arg4)
    146         throws SAXException
    147     {
    148     }
    149 
    150     /**
    151      * @see org.xml.sax.ext.DeclHandler#elementDecl(String, String)
    152      */
    153     public void elementDecl(String arg0, String arg1) throws SAXException
    154     {
    155     }
    156 
    157     /**
    158      * @see org.xml.sax.ext.DeclHandler#externalEntityDecl(String, String, String)
    159      */
    160     public void externalEntityDecl(String arg0, String arg1, String arg2)
    161         throws SAXException
    162     {
    163     }
    164 
    165     /**
    166      * @see org.xml.sax.ext.DeclHandler#internalEntityDecl(String, String)
    167      */
    168     public void internalEntityDecl(String arg0, String arg1)
    169         throws SAXException
    170     {
    171     }
    172 
    173     /**
    174      * Receives notification of the end of the document.
    175      * @see org.xml.sax.ContentHandler#endDocument()
    176      */
    177     public void endDocument() throws SAXException
    178     {
    179 
    180         flushPending();
    181 
    182         // Close output document
    183         m_saxHandler.endDocument();
    184 
    185         if (m_tracer != null)
    186             super.fireEndDoc();
    187     }
    188 
    189     /**
    190      * This method is called when all the data needed for a call to the
    191      * SAX handler's startElement() method has been gathered.
    192      */
    193     protected void closeStartTag() throws SAXException
    194     {
    195 
    196         m_elemContext.m_startTagOpen = false;
    197 
    198         final String localName = getLocalName(m_elemContext.m_elementName);
    199         final String uri = getNamespaceURI(m_elemContext.m_elementName, true);
    200 
    201         // Now is time to send the startElement event
    202         if (m_needToCallStartDocument)
    203         {
    204             startDocumentInternal();
    205         }
    206         m_saxHandler.startElement(uri, localName, m_elemContext.m_elementName, m_attributes);
    207         // we've sent the official SAX attributes on their way,
    208         // now we don't need them anymore.
    209         m_attributes.clear();
    210 
    211         if(m_state != null)
    212           m_state.setCurrentNode(null);
    213     }
    214 
    215     /**
    216      * Closes ane open cdata tag, and
    217      * unlike the this.endCDATA() method (from the LexicalHandler) interface,
    218      * this "internal" method will send the endCDATA() call to the wrapped
    219      * handler.
    220      *
    221      */
    222     public void closeCDATA() throws SAXException
    223     {
    224 
    225         // Output closing bracket - "]]>"
    226         if (m_lexHandler != null && m_cdataTagOpen) {
    227             m_lexHandler.endCDATA();
    228         }
    229 
    230 
    231         // There are no longer any calls made to
    232         // m_lexHandler.startCDATA() without a balancing call to
    233         // m_lexHandler.endCDATA()
    234         // so we set m_cdataTagOpen to false to remember this.
    235         m_cdataTagOpen = false;
    236     }
    237 
    238     /**
    239      * @see org.xml.sax.ContentHandler#endElement(String, String, String)
    240      */
    241     public void endElement(String namespaceURI, String localName, String qName)
    242         throws SAXException
    243     {
    244         // Close any open elements etc.
    245         flushPending();
    246 
    247         if (namespaceURI == null)
    248         {
    249             if (m_elemContext.m_elementURI != null)
    250                 namespaceURI = m_elemContext.m_elementURI;
    251             else
    252                 namespaceURI = getNamespaceURI(qName, true);
    253         }
    254 
    255         if (localName == null)
    256         {
    257             if (m_elemContext.m_elementLocalName != null)
    258                 localName = m_elemContext.m_elementLocalName;
    259             else
    260                 localName = getLocalName(qName);
    261         }
    262 
    263         m_saxHandler.endElement(namespaceURI, localName, qName);
    264 
    265         if (m_tracer != null)
    266             super.fireEndElem(qName);
    267 
    268         /* Pop all namespaces at the current element depth.
    269          * We are not waiting for official endPrefixMapping() calls.
    270          */
    271         m_prefixMap.popNamespaces(m_elemContext.m_currentElemDepth,
    272             m_saxHandler);
    273         m_elemContext = m_elemContext.m_prev;
    274     }
    275 
    276     /**
    277      * @see org.xml.sax.ContentHandler#endPrefixMapping(String)
    278      */
    279     public void endPrefixMapping(String prefix) throws SAXException
    280     {
    281         /* poping all prefix mappings should have been done
    282          * in endElement() already
    283          */
    284          return;
    285     }
    286 
    287     /**
    288      * @see org.xml.sax.ContentHandler#ignorableWhitespace(char[], int, int)
    289      */
    290     public void ignorableWhitespace(char[] arg0, int arg1, int arg2)
    291         throws SAXException
    292     {
    293         m_saxHandler.ignorableWhitespace(arg0,arg1,arg2);
    294     }
    295 
    296     /**
    297      * @see org.xml.sax.ContentHandler#setDocumentLocator(Locator)
    298      */
    299     public void setDocumentLocator(Locator arg0)
    300     {
    301         m_saxHandler.setDocumentLocator(arg0);
    302     }
    303 
    304     /**
    305      * @see org.xml.sax.ContentHandler#skippedEntity(String)
    306      */
    307     public void skippedEntity(String arg0) throws SAXException
    308     {
    309         m_saxHandler.skippedEntity(arg0);
    310     }
    311 
    312     /**
    313      * @see org.xml.sax.ContentHandler#startPrefixMapping(String, String)
    314      * @param prefix The prefix that maps to the URI
    315      * @param uri The URI for the namespace
    316      */
    317     public void startPrefixMapping(String prefix, String uri)
    318         throws SAXException
    319     {
    320        startPrefixMapping(prefix, uri, true);
    321     }
    322 
    323     /**
    324      * Remember the prefix/uri mapping at the current nested element depth.
    325      *
    326      * @see org.xml.sax.ContentHandler#startPrefixMapping(String, String)
    327      * @param prefix The prefix that maps to the URI
    328      * @param uri The URI for the namespace
    329      * @param shouldFlush a flag indicating if the mapping applies to the
    330      * current element or an up coming child (not used).
    331      */
    332 
    333     public boolean startPrefixMapping(
    334         String prefix,
    335         String uri,
    336         boolean shouldFlush)
    337         throws org.xml.sax.SAXException
    338     {
    339 
    340         /* Remember the mapping, and at what depth it was declared
    341          * This is one greater than the current depth because these
    342          * mappings will apply to the next depth. This is in
    343          * consideration that startElement() will soon be called
    344          */
    345 
    346         boolean pushed;
    347         int pushDepth;
    348         if (shouldFlush)
    349         {
    350             flushPending();
    351             // the prefix mapping applies to the child element (one deeper)
    352             pushDepth = m_elemContext.m_currentElemDepth + 1;
    353         }
    354         else
    355         {
    356             // the prefix mapping applies to the current element
    357             pushDepth = m_elemContext.m_currentElemDepth;
    358         }
    359         pushed = m_prefixMap.pushNamespace(prefix, uri, pushDepth);
    360 
    361         if (pushed)
    362         {
    363             m_saxHandler.startPrefixMapping(prefix,uri);
    364 
    365             if (getShouldOutputNSAttr())
    366             {
    367 
    368 	              /* I don't know if we really needto do this. The
    369 	               * callers of this object should have injected both
    370 	               * startPrefixMapping and the attributes.  We are
    371 	               * just covering our butt here.
    372 	               */
    373 	              String name;
    374   	            if (EMPTYSTRING.equals(prefix))
    375   	            {
    376   	                name = "xmlns";
    377   	                addAttributeAlways(XMLNS_URI, name, name,"CDATA",uri, false);
    378   	            }
    379   	            else
    380                 {
    381   	                if (!EMPTYSTRING.equals(uri)) // hack for attribset16 test
    382   	                {                             // that maps ns1 prefix to "" URI
    383   	                    name = "xmlns:" + prefix;
    384 
    385   	                    /* for something like xmlns:abc="w3.pretend.org"
    386   	             	 	     *  the uri is the value, that is why we pass it in the
    387   	             	 	     * value, or 5th slot of addAttributeAlways()
    388   	                 	   */
    389   	                    addAttributeAlways(XMLNS_URI, prefix, name,"CDATA",uri, false );
    390   	                }
    391   	            }
    392             }
    393         }
    394         return pushed;
    395     }
    396 
    397 
    398     /**
    399      * @see org.xml.sax.ext.LexicalHandler#comment(char[], int, int)
    400      */
    401     public void comment(char[] arg0, int arg1, int arg2) throws SAXException
    402     {
    403         flushPending();
    404         if (m_lexHandler != null)
    405             m_lexHandler.comment(arg0, arg1, arg2);
    406 
    407         if (m_tracer != null)
    408             super.fireCommentEvent(arg0, arg1, arg2);
    409     }
    410 
    411     /**
    412      * @see org.xml.sax.ext.LexicalHandler#endCDATA()
    413      */
    414     public void endCDATA() throws SAXException
    415     {
    416         /* Normally we would do somthing with this but we ignore it.
    417          * The neccessary call to m_lexHandler.endCDATA() will be made
    418          * in flushPending().
    419          *
    420          * This is so that if we get calls like these:
    421          *   this.startCDATA();
    422          *   this.characters(chars1, off1, len1);
    423          *   this.endCDATA();
    424          *   this.startCDATA();
    425          *   this.characters(chars2, off2, len2);
    426          *   this.endCDATA();
    427          *
    428          * that we will only make these calls to the wrapped handlers:
    429          *
    430          *   m_lexHandler.startCDATA();
    431          *   m_saxHandler.characters(chars1, off1, len1);
    432          *   m_saxHandler.characters(chars1, off2, len2);
    433          *   m_lexHandler.endCDATA();
    434          *
    435          * We will merge adjacent CDATA blocks.
    436          */
    437     }
    438 
    439     /**
    440      * @see org.xml.sax.ext.LexicalHandler#endDTD()
    441      */
    442     public void endDTD() throws SAXException
    443     {
    444         if (m_lexHandler != null)
    445             m_lexHandler.endDTD();
    446     }
    447 
    448     /**
    449      * @see org.xml.sax.ext.LexicalHandler#startEntity(String)
    450      */
    451     public void startEntity(String arg0) throws SAXException
    452     {
    453         if (m_lexHandler != null)
    454             m_lexHandler.startEntity(arg0);
    455     }
    456 
    457     /**
    458      * @see ExtendedContentHandler#characters(String)
    459      */
    460     public void characters(String chars) throws SAXException
    461     {
    462         final int length = chars.length();
    463         if (length > m_charsBuff.length)
    464         {
    465             m_charsBuff = new char[length*2 + 1];
    466         }
    467         chars.getChars(0, length, m_charsBuff, 0);
    468         this.characters(m_charsBuff, 0, length);
    469     }
    470 
    471     public ToXMLSAXHandler(ContentHandler handler, String encoding)
    472     {
    473         super(handler, encoding);
    474 
    475         initCDATA();
    476         // initNamespaces();
    477         m_prefixMap = new NamespaceMappings();
    478     }
    479 
    480     public ToXMLSAXHandler(
    481         ContentHandler handler,
    482         LexicalHandler lex,
    483         String encoding)
    484     {
    485         super(handler, lex, encoding);
    486 
    487         initCDATA();
    488         //      initNamespaces();
    489         m_prefixMap = new NamespaceMappings();
    490     }
    491 
    492     /**
    493      * Start an element in the output document. This might be an XML element
    494      * (<elem>data</elem> type) or a CDATA section.
    495      */
    496     public void startElement(
    497     String elementNamespaceURI,
    498     String elementLocalName,
    499     String elementName) throws SAXException
    500     {
    501         startElement(
    502             elementNamespaceURI,elementLocalName,elementName, null);
    503 
    504 
    505     }
    506     public void startElement(String elementName) throws SAXException
    507     {
    508         startElement(null, null, elementName, null);
    509     }
    510 
    511 
    512     public void characters(char[] ch, int off, int len) throws SAXException
    513     {
    514         // We do the first two things in flushPending() but we don't
    515         // close any open CDATA calls.
    516         if (m_needToCallStartDocument)
    517         {
    518             startDocumentInternal();
    519             m_needToCallStartDocument = false;
    520         }
    521 
    522         if (m_elemContext.m_startTagOpen)
    523         {
    524             closeStartTag();
    525             m_elemContext.m_startTagOpen = false;
    526         }
    527 
    528         if (m_elemContext.m_isCdataSection && !m_cdataTagOpen
    529         && m_lexHandler != null)
    530         {
    531             m_lexHandler.startCDATA();
    532             // We have made a call to m_lexHandler.startCDATA() with
    533             // no balancing call to m_lexHandler.endCDATA()
    534             // so we set m_cdataTagOpen true to remember this.
    535             m_cdataTagOpen = true;
    536         }
    537 
    538         /* If there are any occurances of "]]>" in the character data
    539          * let m_saxHandler worry about it, we've already warned them with
    540          * the previous call of m_lexHandler.startCDATA();
    541          */
    542         m_saxHandler.characters(ch, off, len);
    543 
    544         // time to generate characters event
    545         if (m_tracer != null)
    546             fireCharEvent(ch, off, len);
    547     }
    548 
    549 
    550     /**
    551      * @see ExtendedContentHandler#endElement(String)
    552      */
    553     public void endElement(String elemName) throws SAXException
    554     {
    555         endElement(null, null, elemName);
    556     }
    557 
    558 
    559     /**
    560      * Send a namespace declaration in the output document. The namespace
    561      * declaration will not be include if the namespace is already in scope
    562      * with the same prefix.
    563      */
    564     public void namespaceAfterStartElement(
    565         final String prefix,
    566         final String uri)
    567         throws SAXException
    568     {
    569         startPrefixMapping(prefix,uri,false);
    570     }
    571 
    572     /**
    573      *
    574      * @see org.xml.sax.ContentHandler#processingInstruction(String, String)
    575      * Send a processing instruction to the output document
    576      */
    577     public void processingInstruction(String target, String data)
    578         throws SAXException
    579     {
    580         flushPending();
    581 
    582         // Pass the processing instruction to the SAX handler
    583         m_saxHandler.processingInstruction(target, data);
    584 
    585         // we don't want to leave serializer to fire off this event,
    586         // so do it here.
    587         if (m_tracer != null)
    588             super.fireEscapingEvent(target, data);
    589     }
    590 
    591     /**
    592      * Undeclare the namespace that is currently pointed to by a given
    593      * prefix. Inform SAX handler if prefix was previously mapped.
    594      */
    595     protected boolean popNamespace(String prefix)
    596     {
    597         try
    598         {
    599             if (m_prefixMap.popNamespace(prefix))
    600             {
    601                 m_saxHandler.endPrefixMapping(prefix);
    602                 return true;
    603             }
    604         }
    605         catch (SAXException e)
    606         {
    607             // falls through
    608         }
    609         return false;
    610     }
    611 
    612     public void startCDATA() throws SAXException
    613     {
    614         /* m_cdataTagOpen can only be true here if we have ignored the
    615          * previous call to this.endCDATA() and the previous call
    616          * this.startCDATA() before that is still "open". In this way
    617          * we merge adjacent CDATA. If anything else happened after the
    618          * ignored call to this.endCDATA() and this call then a call to
    619          * flushPending() would have been made which would have
    620          * closed the CDATA and set m_cdataTagOpen to false.
    621          */
    622         if (!m_cdataTagOpen )
    623         {
    624             flushPending();
    625             if (m_lexHandler != null) {
    626                 m_lexHandler.startCDATA();
    627 
    628                 // We have made a call to m_lexHandler.startCDATA() with
    629                 // no balancing call to m_lexHandler.endCDATA()
    630                 // so we set m_cdataTagOpen true to remember this.
    631                 m_cdataTagOpen = true;
    632             }
    633         }
    634     }
    635 
    636     /**
    637      * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes)
    638      */
    639     public void startElement(
    640     String namespaceURI,
    641     String localName,
    642     String name,
    643     Attributes atts)
    644         throws SAXException
    645     {
    646         flushPending();
    647         super.startElement(namespaceURI, localName, name, atts);
    648 
    649         // Handle document type declaration (for first element only)
    650          if (m_needToOutputDocTypeDecl)
    651          {
    652              String doctypeSystem = getDoctypeSystem();
    653              if (doctypeSystem != null && m_lexHandler != null)
    654              {
    655                  String doctypePublic = getDoctypePublic();
    656                  if (doctypeSystem != null)
    657                      m_lexHandler.startDTD(
    658                          name,
    659                          doctypePublic,
    660                          doctypeSystem);
    661              }
    662              m_needToOutputDocTypeDecl = false;
    663          }
    664         m_elemContext = m_elemContext.push(namespaceURI, localName, name);
    665 
    666         // ensurePrefixIsDeclared depends on the current depth, so
    667         // the previous increment is necessary where it is.
    668         if (namespaceURI != null)
    669             ensurePrefixIsDeclared(namespaceURI, name);
    670 
    671         // add the attributes to the collected ones
    672         if (atts != null)
    673             addAttributes(atts);
    674 
    675 
    676         // do we really need this CDATA section state?
    677         m_elemContext.m_isCdataSection = isCdataSection();
    678 
    679     }
    680 
    681     private void ensurePrefixIsDeclared(String ns, String rawName)
    682         throws org.xml.sax.SAXException
    683     {
    684 
    685         if (ns != null && ns.length() > 0)
    686         {
    687             int index;
    688             final boolean no_prefix = ((index = rawName.indexOf(":")) < 0);
    689             String prefix = (no_prefix) ? "" : rawName.substring(0, index);
    690 
    691 
    692             if (null != prefix)
    693             {
    694                 String foundURI = m_prefixMap.lookupNamespace(prefix);
    695 
    696                 if ((null == foundURI) || !foundURI.equals(ns))
    697                 {
    698                     this.startPrefixMapping(prefix, ns, false);
    699 
    700                     if (getShouldOutputNSAttr()) {
    701                         // Bugzilla1133: Generate attribute as well as namespace event.
    702                         // SAX does expect both.
    703                         this.addAttributeAlways(
    704                             "http://www.w3.org/2000/xmlns/",
    705                             no_prefix ? "xmlns" : prefix,  // local name
    706                             no_prefix ? "xmlns" : ("xmlns:"+ prefix), // qname
    707                             "CDATA",
    708                             ns,
    709                             false);
    710                     }
    711                 }
    712 
    713             }
    714         }
    715     }
    716     /**
    717      * Adds the given attribute to the set of attributes, and also makes sure
    718      * that the needed prefix/uri mapping is declared, but only if there is a
    719      * currently open element.
    720      *
    721      * @param uri the URI of the attribute
    722      * @param localName the local name of the attribute
    723      * @param rawName    the qualified name of the attribute
    724      * @param type the type of the attribute (probably CDATA)
    725      * @param value the value of the attribute
    726      * @param XSLAttribute true if this attribute is coming from an xsl:attribute element
    727      * @see ExtendedContentHandler#addAttribute(String, String, String, String, String)
    728      */
    729     public void addAttribute(
    730         String uri,
    731         String localName,
    732         String rawName,
    733         String type,
    734         String value,
    735         boolean XSLAttribute)
    736         throws SAXException
    737     {
    738         if (m_elemContext.m_startTagOpen)
    739         {
    740             ensurePrefixIsDeclared(uri, rawName);
    741             addAttributeAlways(uri, localName, rawName, type, value, false);
    742         }
    743 
    744     }
    745 
    746     /**
    747      * Try's to reset the super class and reset this class for
    748      * re-use, so that you don't need to create a new serializer
    749      * (mostly for performance reasons).
    750      *
    751      * @return true if the class was successfuly reset.
    752      * @see Serializer#reset()
    753      */
    754     public boolean reset()
    755     {
    756         boolean wasReset = false;
    757         if (super.reset())
    758         {
    759             resetToXMLSAXHandler();
    760             wasReset = true;
    761         }
    762         return wasReset;
    763     }
    764 
    765     /**
    766      * Reset all of the fields owned by ToXMLSAXHandler class
    767      *
    768      */
    769     private void resetToXMLSAXHandler()
    770     {
    771         this.m_escapeSetting = true;
    772     }
    773 
    774 }
    775