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: ToXMLStream.java 469359 2006-10-31 03:43:19Z minchau $
     20  */
     21  package org.apache.xml.serializer;
     22 
     23 import java.io.IOException;
     24 
     25 import javax.xml.transform.ErrorListener;
     26 import javax.xml.transform.Result;
     27 import javax.xml.transform.Transformer;
     28 import javax.xml.transform.TransformerException;
     29 
     30 import org.apache.xml.serializer.utils.MsgKey;
     31 import org.apache.xml.serializer.utils.Utils;
     32 import org.xml.sax.SAXException;
     33 
     34 /**
     35  * This class converts SAX or SAX-like calls to a
     36  * serialized xml document.  The xsl:output method is "xml".
     37  *
     38  * This class is used explicitly in code generated by XSLTC,
     39  * so it is "public", but it should
     40  * be viewed as internal or package private, this is not an API.
     41  *
     42  * @xsl.usage internal
     43  */
     44 public class ToXMLStream extends ToStream
     45 {
     46     /**
     47      * Map that tells which XML characters should have special treatment, and it
     48      *  provides character to entity name lookup.
     49      */
     50     private CharInfo m_xmlcharInfo =
     51         CharInfo.getCharInfo(CharInfo.XML_ENTITIES_RESOURCE, Method.XML);
     52 
     53     /**
     54      * Default constructor.
     55      */
     56     public ToXMLStream()
     57     {
     58         m_charInfo = m_xmlcharInfo;
     59 
     60         initCDATA();
     61         // initialize namespaces
     62         m_prefixMap = new NamespaceMappings();
     63 
     64     }
     65 
     66     /**
     67      * Copy properties from another SerializerToXML.
     68      *
     69      * @param xmlListener non-null reference to a SerializerToXML object.
     70      */
     71     public void CopyFrom(ToXMLStream xmlListener)
     72     {
     73 
     74         setWriter(xmlListener.m_writer);
     75 
     76 
     77         // m_outputStream = xmlListener.m_outputStream;
     78         String encoding = xmlListener.getEncoding();
     79         setEncoding(encoding);
     80 
     81         setOmitXMLDeclaration(xmlListener.getOmitXMLDeclaration());
     82 
     83         m_ispreserve = xmlListener.m_ispreserve;
     84         m_preserves = xmlListener.m_preserves;
     85         m_isprevtext = xmlListener.m_isprevtext;
     86         m_doIndent = xmlListener.m_doIndent;
     87         setIndentAmount(xmlListener.getIndentAmount());
     88         m_startNewLine = xmlListener.m_startNewLine;
     89         m_needToOutputDocTypeDecl = xmlListener.m_needToOutputDocTypeDecl;
     90         setDoctypeSystem(xmlListener.getDoctypeSystem());
     91         setDoctypePublic(xmlListener.getDoctypePublic());
     92         setStandalone(xmlListener.getStandalone());
     93         setMediaType(xmlListener.getMediaType());
     94         m_encodingInfo = xmlListener.m_encodingInfo;
     95         m_spaceBeforeClose = xmlListener.m_spaceBeforeClose;
     96         m_cdataStartCalled = xmlListener.m_cdataStartCalled;
     97 
     98     }
     99 
    100     /**
    101      * Receive notification of the beginning of a document.
    102      *
    103      * @throws org.xml.sax.SAXException Any SAX exception, possibly
    104      *            wrapping another exception.
    105      *
    106      * @throws org.xml.sax.SAXException
    107      */
    108     public void startDocumentInternal() throws org.xml.sax.SAXException
    109     {
    110 
    111         if (m_needToCallStartDocument)
    112         {
    113             super.startDocumentInternal();
    114             m_needToCallStartDocument = false;
    115 
    116             if (m_inEntityRef)
    117                 return;
    118 
    119             m_needToOutputDocTypeDecl = true;
    120             m_startNewLine = false;
    121             /* The call to getXMLVersion() might emit an error message
    122              * and we should emit this message regardless of if we are
    123              * writing out an XML header or not.
    124              */
    125             final String version = getXMLVersion();
    126             if (getOmitXMLDeclaration() == false)
    127             {
    128                 String encoding = Encodings.getMimeEncoding(getEncoding());
    129                 String standalone;
    130 
    131                 if (m_standaloneWasSpecified)
    132                 {
    133                     standalone = " standalone=\"" + getStandalone() + "\"";
    134                 }
    135                 else
    136                 {
    137                     standalone = "";
    138                 }
    139 
    140                 try
    141                 {
    142                     final java.io.Writer writer = m_writer;
    143                     writer.write("<?xml version=\"");
    144                     writer.write(version);
    145                     writer.write("\" encoding=\"");
    146                     writer.write(encoding);
    147                     writer.write('\"');
    148                     writer.write(standalone);
    149                     writer.write("?>");
    150                     if (m_doIndent) {
    151                         if (m_standaloneWasSpecified
    152                                 || getDoctypePublic() != null
    153                                 || getDoctypeSystem() != null) {
    154                             // We almost never put a newline after the XML
    155                             // header because this XML could be used as
    156                             // an extenal general parsed entity
    157                             // and we don't know the context into which it
    158                             // will be used in the future.  Only when
    159                             // standalone, or a doctype system or public is
    160                             // specified are we free to insert a new line
    161                             // after the header.  Is it even worth bothering
    162                             // in these rare cases?
    163                             writer.write(m_lineSep, 0, m_lineSepLen);
    164                         }
    165                     }
    166                 }
    167                 catch(IOException e)
    168                 {
    169                     throw new SAXException(e);
    170                 }
    171 
    172             }
    173         }
    174     }
    175 
    176     /**
    177      * Receive notification of the end of a document.
    178      *
    179      * @throws org.xml.sax.SAXException Any SAX exception, possibly
    180      *            wrapping another exception.
    181      *
    182      * @throws org.xml.sax.SAXException
    183      */
    184     public void endDocument() throws org.xml.sax.SAXException
    185     {
    186         flushPending();
    187         if (m_doIndent && !m_isprevtext)
    188         {
    189             try
    190             {
    191             outputLineSep();
    192             }
    193             catch(IOException e)
    194             {
    195                 throw new SAXException(e);
    196             }
    197         }
    198 
    199         flushWriter();
    200 
    201         if (m_tracer != null)
    202             super.fireEndDoc();
    203     }
    204 
    205     /**
    206      * Starts a whitespace preserving section. All characters printed
    207      * within a preserving section are printed without indentation and
    208      * without consolidating multiple spaces. This is equivalent to
    209      * the <tt>xml:space=&quot;preserve&quot;</tt> attribute. Only XML
    210      * and HTML serializers need to support this method.
    211      * <p>
    212      * The contents of the whitespace preserving section will be delivered
    213      * through the regular <tt>characters</tt> event.
    214      *
    215      * @throws org.xml.sax.SAXException
    216      */
    217     public void startPreserving() throws org.xml.sax.SAXException
    218     {
    219 
    220         // Not sure this is really what we want.  -sb
    221         m_preserves.push(true);
    222 
    223         m_ispreserve = true;
    224     }
    225 
    226     /**
    227      * Ends a whitespace preserving section.
    228      *
    229      * @see #startPreserving
    230      *
    231      * @throws org.xml.sax.SAXException
    232      */
    233     public void endPreserving() throws org.xml.sax.SAXException
    234     {
    235 
    236         // Not sure this is really what we want.  -sb
    237         m_ispreserve = m_preserves.isEmpty() ? false : m_preserves.pop();
    238     }
    239 
    240     /**
    241      * Receive notification of a processing instruction.
    242      *
    243      * @param target The processing instruction target.
    244      * @param data The processing instruction data, or null if
    245      *        none was supplied.
    246      * @throws org.xml.sax.SAXException Any SAX exception, possibly
    247      *            wrapping another exception.
    248      *
    249      * @throws org.xml.sax.SAXException
    250      */
    251     public void processingInstruction(String target, String data)
    252         throws org.xml.sax.SAXException
    253     {
    254         if (m_inEntityRef)
    255             return;
    256 
    257         flushPending();
    258 
    259         if (target.equals(Result.PI_DISABLE_OUTPUT_ESCAPING))
    260         {
    261             startNonEscaping();
    262         }
    263         else if (target.equals(Result.PI_ENABLE_OUTPUT_ESCAPING))
    264         {
    265             endNonEscaping();
    266         }
    267         else
    268         {
    269             try
    270             {
    271                 if (m_elemContext.m_startTagOpen)
    272                 {
    273                     closeStartTag();
    274                     m_elemContext.m_startTagOpen = false;
    275                 }
    276                 else if (m_needToCallStartDocument)
    277                     startDocumentInternal();
    278 
    279                 if (shouldIndent())
    280                     indent();
    281 
    282                 final java.io.Writer writer = m_writer;
    283                 writer.write("<?");
    284                 writer.write(target);
    285 
    286                 if (data.length() > 0
    287                     && !Character.isSpaceChar(data.charAt(0)))
    288                     writer.write(' ');
    289 
    290                 int indexOfQLT = data.indexOf("?>");
    291 
    292                 if (indexOfQLT >= 0)
    293                 {
    294 
    295                     // See XSLT spec on error recovery of "?>" in PIs.
    296                     if (indexOfQLT > 0)
    297                     {
    298                         writer.write(data.substring(0, indexOfQLT));
    299                     }
    300 
    301                     writer.write("? >"); // add space between.
    302 
    303                     if ((indexOfQLT + 2) < data.length())
    304                     {
    305                         writer.write(data.substring(indexOfQLT + 2));
    306                     }
    307                 }
    308                 else
    309                 {
    310                     writer.write(data);
    311                 }
    312 
    313                 writer.write('?');
    314                 writer.write('>');
    315 
    316                 /*
    317                  * Don't write out any indentation whitespace now,
    318                  * because there may be non-whitespace text after this.
    319                  *
    320                  * Simply mark that at this point if we do decide
    321                  * to indent that we should
    322                  * add a newline on the end of the current line before
    323                  * the indentation at the start of the next line.
    324                  */
    325                 m_startNewLine = true;
    326             }
    327             catch(IOException e)
    328             {
    329                 throw new SAXException(e);
    330             }
    331         }
    332 
    333         if (m_tracer != null)
    334             super.fireEscapingEvent(target, data);
    335     }
    336 
    337     /**
    338      * Receive notivication of a entityReference.
    339      *
    340      * @param name The name of the entity.
    341      *
    342      * @throws org.xml.sax.SAXException
    343      */
    344     public void entityReference(String name) throws org.xml.sax.SAXException
    345     {
    346         if (m_elemContext.m_startTagOpen)
    347         {
    348             closeStartTag();
    349             m_elemContext.m_startTagOpen = false;
    350         }
    351 
    352         try
    353         {
    354             if (shouldIndent())
    355                 indent();
    356 
    357             final java.io.Writer writer = m_writer;
    358             writer.write('&');
    359             writer.write(name);
    360             writer.write(';');
    361         }
    362         catch(IOException e)
    363         {
    364             throw new SAXException(e);
    365         }
    366 
    367         if (m_tracer != null)
    368             super.fireEntityReference(name);
    369     }
    370 
    371     /**
    372      * This method is used to add an attribute to the currently open element.
    373      * The caller has guaranted that this attribute is unique, which means that it
    374      * not been seen before and will not be seen again.
    375      *
    376      * @param name the qualified name of the attribute
    377      * @param value the value of the attribute which can contain only
    378      * ASCII printable characters characters in the range 32 to 127 inclusive.
    379      * @param flags the bit values of this integer give optimization information.
    380      */
    381     public void addUniqueAttribute(String name, String value, int flags)
    382         throws SAXException
    383     {
    384         if (m_elemContext.m_startTagOpen)
    385         {
    386 
    387             try
    388             {
    389                 final String patchedName = patchName(name);
    390                 final java.io.Writer writer = m_writer;
    391                 if ((flags & NO_BAD_CHARS) > 0 && m_xmlcharInfo.onlyQuotAmpLtGt)
    392                 {
    393                     // "flags" has indicated that the characters
    394                     // '>'  '<'   '&'  and '"' are not in the value and
    395                     // m_htmlcharInfo has recorded that there are no other
    396                     // entities in the range 32 to 127 so we write out the
    397                     // value directly
    398 
    399                     writer.write(' ');
    400                     writer.write(patchedName);
    401                     writer.write("=\"");
    402                     writer.write(value);
    403                     writer.write('"');
    404                 }
    405                 else
    406                 {
    407                     writer.write(' ');
    408                     writer.write(patchedName);
    409                     writer.write("=\"");
    410                     writeAttrString(writer, value, this.getEncoding());
    411                     writer.write('"');
    412                 }
    413             } catch (IOException e) {
    414                 throw new SAXException(e);
    415             }
    416         }
    417     }
    418 
    419     /**
    420      * Add an attribute to the current element.
    421      * @param uri the URI associated with the element name
    422      * @param localName local part of the attribute name
    423      * @param rawName   prefix:localName
    424      * @param type
    425      * @param value the value of the attribute
    426      * @param xslAttribute true if this attribute is from an xsl:attribute,
    427      * false if declared within the elements opening tag.
    428      * @throws SAXException
    429      */
    430     public void addAttribute(
    431         String uri,
    432         String localName,
    433         String rawName,
    434         String type,
    435         String value,
    436         boolean xslAttribute)
    437         throws SAXException
    438     {
    439         if (m_elemContext.m_startTagOpen)
    440         {
    441             boolean was_added = addAttributeAlways(uri, localName, rawName, type, value, xslAttribute);
    442 
    443 
    444             /*
    445              * We don't run this block of code if:
    446              * 1. The attribute value was only replaced (was_added is false).
    447              * 2. The attribute is from an xsl:attribute element (that is handled
    448              *    in the addAttributeAlways() call just above.
    449              * 3. The name starts with "xmlns", i.e. it is a namespace declaration.
    450              */
    451             if (was_added && !xslAttribute && !rawName.startsWith("xmlns"))
    452             {
    453                 String prefixUsed =
    454                     ensureAttributesNamespaceIsDeclared(
    455                         uri,
    456                         localName,
    457                         rawName);
    458                 if (prefixUsed != null
    459                     && rawName != null
    460                     && !rawName.startsWith(prefixUsed))
    461                 {
    462                     // use a different raw name, with the prefix used in the
    463                     // generated namespace declaration
    464                     rawName = prefixUsed + ":" + localName;
    465 
    466                 }
    467             }
    468             addAttributeAlways(uri, localName, rawName, type, value, xslAttribute);
    469         }
    470         else
    471         {
    472             /*
    473              * The startTag is closed, yet we are adding an attribute?
    474              *
    475              * Section: 7.1.3 Creating Attributes Adding an attribute to an
    476              * element after a PI (for example) has been added to it is an
    477              * error. The attributes can be ignored. The spec doesn't explicitly
    478              * say this is disallowed, as it does for child elements, but it
    479              * makes sense to have the same treatment.
    480              *
    481              * We choose to ignore the attribute which is added too late.
    482              */
    483             // Generate a warning of the ignored attributes
    484 
    485             // Create the warning message
    486             String msg = Utils.messages.createMessage(
    487                     MsgKey.ER_ILLEGAL_ATTRIBUTE_POSITION,new Object[]{ localName });
    488 
    489             try {
    490                 // Prepare to issue the warning message
    491                 Transformer tran = super.getTransformer();
    492                 ErrorListener errHandler = tran.getErrorListener();
    493 
    494 
    495                 // Issue the warning message
    496                 if (null != errHandler && m_sourceLocator != null)
    497                   errHandler.warning(new TransformerException(msg, m_sourceLocator));
    498                 else
    499                   System.out.println(msg);
    500                 }
    501             catch (TransformerException e){
    502                 // A user defined error handler, errHandler, may throw
    503                 // a TransformerException if it chooses to, and if it does
    504                 // we will wrap it with a SAXException and re-throw.
    505                 // Of course if the handler throws another type of
    506                 // exception, like a RuntimeException, then that is OK too.
    507                 SAXException se = new SAXException(e);
    508                 throw se;
    509             }
    510         }
    511     }
    512 
    513     /**
    514      * @see ExtendedContentHandler#endElement(String)
    515      */
    516     public void endElement(String elemName) throws SAXException
    517     {
    518         endElement(null, null, elemName);
    519     }
    520 
    521     /**
    522      * This method is used to notify the serializer of a namespace mapping (or node)
    523      * that applies to the current element whose startElement() call has already been seen.
    524      * The official SAX startPrefixMapping(prefix,uri) is to define a mapping for a child
    525      * element that is soon to be seen with a startElement() call. The official SAX call
    526      * does not apply to the current element, hence the reason for this method.
    527      */
    528     public void namespaceAfterStartElement(
    529         final String prefix,
    530         final String uri)
    531         throws SAXException
    532     {
    533 
    534         // hack for XSLTC with finding URI for default namespace
    535         if (m_elemContext.m_elementURI == null)
    536         {
    537             String prefix1 = getPrefixPart(m_elemContext.m_elementName);
    538             if (prefix1 == null && EMPTYSTRING.equals(prefix))
    539             {
    540                 // the elements URI is not known yet, and it
    541                 // doesn't have a prefix, and we are currently
    542                 // setting the uri for prefix "", so we have
    543                 // the uri for the element... lets remember it
    544                 m_elemContext.m_elementURI = uri;
    545             }
    546         }
    547         startPrefixMapping(prefix,uri,false);
    548         return;
    549 
    550     }
    551 
    552     /**
    553      * From XSLTC
    554      * Declare a prefix to point to a namespace URI. Inform SAX handler
    555      * if this is a new prefix mapping.
    556      */
    557     protected boolean pushNamespace(String prefix, String uri)
    558     {
    559         try
    560         {
    561             if (m_prefixMap.pushNamespace(
    562                 prefix, uri, m_elemContext.m_currentElemDepth))
    563             {
    564                 startPrefixMapping(prefix, uri);
    565                 return true;
    566             }
    567         }
    568         catch (SAXException e)
    569         {
    570             // falls through
    571         }
    572         return false;
    573     }
    574     /**
    575      * Try's to reset the super class and reset this class for
    576      * re-use, so that you don't need to create a new serializer
    577      * (mostly for performance reasons).
    578      *
    579      * @return true if the class was successfuly reset.
    580      */
    581     public boolean reset()
    582     {
    583         boolean wasReset = false;
    584         if (super.reset())
    585         {
    586             // Make this call when resetToXMLStream does
    587             // something.
    588             // resetToXMLStream();
    589             wasReset = true;
    590         }
    591         return wasReset;
    592     }
    593 
    594     /**
    595      * Reset all of the fields owned by ToStream class
    596      *
    597      */
    598     private void resetToXMLStream()
    599     {
    600         // This is an empty method, but is kept for future use
    601         // as a place holder for a location to reset fields
    602         // defined within this class
    603         return;
    604     }
    605 
    606     /**
    607      * This method checks for the XML version of output document.
    608      * If XML version of output document is not specified, then output
    609      * document is of version XML 1.0.
    610      * If XML version of output doucment is specified, but it is not either
    611      * XML 1.0 or XML 1.1, a warning message is generated, the XML Version of
    612      * output document is set to XML 1.0 and processing continues.
    613      * @return string (XML version)
    614      */
    615     private String getXMLVersion()
    616     {
    617         String xmlVersion = getVersion();
    618         if(xmlVersion == null || xmlVersion.equals(XMLVERSION10))
    619         {
    620             xmlVersion = XMLVERSION10;
    621         }
    622         else if(xmlVersion.equals(XMLVERSION11))
    623         {
    624             xmlVersion = XMLVERSION11;
    625         }
    626         else
    627         {
    628             String msg = Utils.messages.createMessage(
    629                                MsgKey.ER_XML_VERSION_NOT_SUPPORTED,new Object[]{ xmlVersion });
    630             try
    631             {
    632                 // Prepare to issue the warning message
    633                 Transformer tran = super.getTransformer();
    634                 ErrorListener errHandler = tran.getErrorListener();
    635                 // Issue the warning message
    636                 if (null != errHandler && m_sourceLocator != null)
    637                     errHandler.warning(new TransformerException(msg, m_sourceLocator));
    638                 else
    639                     System.out.println(msg);
    640             }
    641             catch (Exception e){}
    642             xmlVersion = XMLVERSION10;
    643         }
    644         return xmlVersion;
    645     }
    646 }
    647