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