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