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