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