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="preserve"</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 }