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