001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 /* 019 * $Id: ToXMLStream.java 469359 2006-10-31 03:43:19Z minchau $ 020 */ 021 package org.apache.xml.serializer; 022 023 import java.io.IOException; 024 025 import javax.xml.transform.ErrorListener; 026 import javax.xml.transform.Result; 027 import javax.xml.transform.Transformer; 028 import javax.xml.transform.TransformerException; 029 030 import org.apache.xml.serializer.utils.MsgKey; 031 import org.apache.xml.serializer.utils.Utils; 032 import org.xml.sax.SAXException; 033 034 /** 035 * This class converts SAX or SAX-like calls to a 036 * serialized xml document. The xsl:output method is "xml". 037 * 038 * This class is used explicitly in code generated by XSLTC, 039 * so it is "public", but it should 040 * be viewed as internal or package private, this is not an API. 041 * 042 * @xsl.usage internal 043 */ 044 public class ToXMLStream extends ToStream 045 { 046 /** 047 * Map that tells which XML characters should have special treatment, and it 048 * provides character to entity name lookup. 049 */ 050 private CharInfo m_xmlcharInfo = 051 CharInfo.getCharInfo(CharInfo.XML_ENTITIES_RESOURCE, Method.XML); 052 053 /** 054 * Default constructor. 055 */ 056 public ToXMLStream() 057 { 058 m_charInfo = m_xmlcharInfo; 059 060 initCDATA(); 061 // initialize namespaces 062 m_prefixMap = new NamespaceMappings(); 063 064 } 065 066 /** 067 * Copy properties from another SerializerToXML. 068 * 069 * @param xmlListener non-null reference to a SerializerToXML object. 070 */ 071 public void CopyFrom(ToXMLStream xmlListener) 072 { 073 074 setWriter(xmlListener.m_writer); 075 076 077 // m_outputStream = xmlListener.m_outputStream; 078 String encoding = xmlListener.getEncoding(); 079 setEncoding(encoding); 080 081 setOmitXMLDeclaration(xmlListener.getOmitXMLDeclaration()); 082 083 m_ispreserve = xmlListener.m_ispreserve; 084 m_preserves = xmlListener.m_preserves; 085 m_isprevtext = xmlListener.m_isprevtext; 086 m_doIndent = xmlListener.m_doIndent; 087 setIndentAmount(xmlListener.getIndentAmount()); 088 m_startNewLine = xmlListener.m_startNewLine; 089 m_needToOutputDocTypeDecl = xmlListener.m_needToOutputDocTypeDecl; 090 setDoctypeSystem(xmlListener.getDoctypeSystem()); 091 setDoctypePublic(xmlListener.getDoctypePublic()); 092 setStandalone(xmlListener.getStandalone()); 093 setMediaType(xmlListener.getMediaType()); 094 m_encodingInfo = xmlListener.m_encodingInfo; 095 m_spaceBeforeClose = xmlListener.m_spaceBeforeClose; 096 m_cdataStartCalled = xmlListener.m_cdataStartCalled; 097 098 } 099 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 }