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