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 // We almost never put a newline after the XML 168 // header because this XML could be used as 169 // an extenal general parsed entity 170 // and we don't know the context into which it 171 // will be used in the future. Only when 172 // standalone, or a doctype system or public is 173 // specified are we free to insert a new line 174 // after the header. Is it even worth bothering 175 // in these rare cases? 176 writer.write(m_lineSep, 0, m_lineSepLen); 177 } 178 } 179 } 180 catch(IOException e) 181 { 182 throw new SAXException(e); 183 } 184 185 } 186 } 187 } 188 189 /** 190 * Receive notification of the end of a document. 191 * 192 * @throws org.xml.sax.SAXException Any SAX exception, possibly 193 * wrapping another exception. 194 * 195 * @throws org.xml.sax.SAXException 196 */ 197 public void endDocument() throws org.xml.sax.SAXException 198 { 199 flushPending(); 200 if (m_doIndent && !m_isprevtext) 201 { 202 try 203 { 204 outputLineSep(); 205 } 206 catch(IOException e) 207 { 208 throw new SAXException(e); 209 } 210 } 211 212 flushWriter(); 213 214 if (m_tracer != null) 215 super.fireEndDoc(); 216 } 217 218 /** 219 * Starts a whitespace preserving section. All characters printed 220 * within a preserving section are printed without indentation and 221 * without consolidating multiple spaces. This is equivalent to 222 * the <tt>xml:space="preserve"</tt> attribute. Only XML 223 * and HTML serializers need to support this method. 224 * <p> 225 * The contents of the whitespace preserving section will be delivered 226 * through the regular <tt>characters</tt> event. 227 * 228 * @throws org.xml.sax.SAXException 229 */ 230 public void startPreserving() throws org.xml.sax.SAXException 231 { 232 233 // Not sure this is really what we want. -sb 234 m_preserves.push(true); 235 236 m_ispreserve = true; 237 } 238 239 /** 240 * Ends a whitespace preserving section. 241 * 242 * @see #startPreserving 243 * 244 * @throws org.xml.sax.SAXException 245 */ 246 public void endPreserving() throws org.xml.sax.SAXException 247 { 248 249 // Not sure this is really what we want. -sb 250 m_ispreserve = m_preserves.isEmpty() ? false : m_preserves.pop(); 251 } 252 253 /** 254 * Receive notification of a processing instruction. 255 * 256 * @param target The processing instruction target. 257 * @param data The processing instruction data, or null if 258 * none was supplied. 259 * @throws org.xml.sax.SAXException Any SAX exception, possibly 260 * wrapping another exception. 261 * 262 * @throws org.xml.sax.SAXException 263 */ 264 public void processingInstruction(String target, String data) 265 throws org.xml.sax.SAXException 266 { 267 if (m_inEntityRef) 268 return; 269 270 flushPending(); 271 272 if (target.equals(Result.PI_DISABLE_OUTPUT_ESCAPING)) 273 { 274 startNonEscaping(); 275 } 276 else if (target.equals(Result.PI_ENABLE_OUTPUT_ESCAPING)) 277 { 278 endNonEscaping(); 279 } 280 else 281 { 282 try 283 { 284 if (m_elemContext.m_startTagOpen) 285 { 286 closeStartTag(); 287 m_elemContext.m_startTagOpen = false; 288 } 289 else if (m_needToCallStartDocument) 290 startDocumentInternal(); 291 292 if (shouldIndent()) 293 indent(); 294 295 final java.io.Writer writer = m_writer; 296 writer.write("<?"); 297 writer.write(target); 298 299 if (data.length() > 0 300 && !Character.isSpaceChar(data.charAt(0))) 301 writer.write(' '); 302 303 int indexOfQLT = data.indexOf("?>"); 304 305 if (indexOfQLT >= 0) 306 { 307 308 // See XSLT spec on error recovery of "?>" in PIs. 309 if (indexOfQLT > 0) 310 { 311 writer.write(data.substring(0, indexOfQLT)); 312 } 313 314 writer.write("? >"); // add space between. 315 316 if ((indexOfQLT + 2) < data.length()) 317 { 318 writer.write(data.substring(indexOfQLT + 2)); 319 } 320 } 321 else 322 { 323 writer.write(data); 324 } 325 326 writer.write('?'); 327 writer.write('>'); 328 329 /* 330 * Don't write out any indentation whitespace now, 331 * because there may be non-whitespace text after this. 332 * 333 * Simply mark that at this point if we do decide 334 * to indent that we should 335 * add a newline on the end of the current line before 336 * the indentation at the start of the next line. 337 */ 338 m_startNewLine = true; 339 } 340 catch(IOException e) 341 { 342 throw new SAXException(e); 343 } 344 } 345 346 if (m_tracer != null) 347 super.fireEscapingEvent(target, data); 348 } 349 350 /** 351 * Receive notivication of a entityReference. 352 * 353 * @param name The name of the entity. 354 * 355 * @throws org.xml.sax.SAXException 356 */ 357 public void entityReference(String name) throws org.xml.sax.SAXException 358 { 359 if (m_elemContext.m_startTagOpen) 360 { 361 closeStartTag(); 362 m_elemContext.m_startTagOpen = false; 363 } 364 365 try 366 { 367 if (shouldIndent()) 368 indent(); 369 370 final java.io.Writer writer = m_writer; 371 writer.write('&'); 372 writer.write(name); 373 writer.write(';'); 374 } 375 catch(IOException e) 376 { 377 throw new SAXException(e); 378 } 379 380 if (m_tracer != null) 381 super.fireEntityReference(name); 382 } 383 384 /** 385 * This method is used to add an attribute to the currently open element. 386 * The caller has guaranted that this attribute is unique, which means that it 387 * not been seen before and will not be seen again. 388 * 389 * @param name the qualified name of the attribute 390 * @param value the value of the attribute which can contain only 391 * ASCII printable characters characters in the range 32 to 127 inclusive. 392 * @param flags the bit values of this integer give optimization information. 393 */ 394 public void addUniqueAttribute(String name, String value, int flags) 395 throws SAXException 396 { 397 if (m_elemContext.m_startTagOpen) 398 { 399 400 try 401 { 402 final String patchedName = patchName(name); 403 final java.io.Writer writer = m_writer; 404 if ((flags & NO_BAD_CHARS) > 0 && m_xmlcharInfo.onlyQuotAmpLtGt) 405 { 406 // "flags" has indicated that the characters 407 // '>' '<' '&' and '"' are not in the value and 408 // m_htmlcharInfo has recorded that there are no other 409 // entities in the range 32 to 127 so we write out the 410 // value directly 411 412 writer.write(' '); 413 writer.write(patchedName); 414 writer.write("=\""); 415 writer.write(value); 416 writer.write('"'); 417 } 418 else 419 { 420 writer.write(' '); 421 writer.write(patchedName); 422 writer.write("=\""); 423 writeAttrString(writer, value, this.getEncoding()); 424 writer.write('"'); 425 } 426 } catch (IOException e) { 427 throw new SAXException(e); 428 } 429 } 430 } 431 432 /** 433 * Add an attribute to the current element. 434 * @param uri the URI associated with the element name 435 * @param localName local part of the attribute name 436 * @param rawName prefix:localName 437 * @param type 438 * @param value the value of the attribute 439 * @param xslAttribute true if this attribute is from an xsl:attribute, 440 * false if declared within the elements opening tag. 441 * @throws SAXException 442 */ 443 public void addAttribute( 444 String uri, 445 String localName, 446 String rawName, 447 String type, 448 String value, 449 boolean xslAttribute) 450 throws SAXException 451 { 452 if (m_elemContext.m_startTagOpen) 453 { 454 boolean was_added = addAttributeAlways(uri, localName, rawName, type, value, xslAttribute); 455 456 457 /* 458 * We don't run this block of code if: 459 * 1. The attribute value was only replaced (was_added is false). 460 * 2. The attribute is from an xsl:attribute element (that is handled 461 * in the addAttributeAlways() call just above. 462 * 3. The name starts with "xmlns", i.e. it is a namespace declaration. 463 */ 464 if (was_added && !xslAttribute && !rawName.startsWith("xmlns")) 465 { 466 String prefixUsed = 467 ensureAttributesNamespaceIsDeclared( 468 uri, 469 localName, 470 rawName); 471 if (prefixUsed != null 472 && rawName != null 473 && !rawName.startsWith(prefixUsed)) 474 { 475 // use a different raw name, with the prefix used in the 476 // generated namespace declaration 477 rawName = prefixUsed + ":" + localName; 478 479 } 480 } 481 addAttributeAlways(uri, localName, rawName, type, value, xslAttribute); 482 } 483 else 484 { 485 /* 486 * The startTag is closed, yet we are adding an attribute? 487 * 488 * Section: 7.1.3 Creating Attributes Adding an attribute to an 489 * element after a PI (for example) has been added to it is an 490 * error. The attributes can be ignored. The spec doesn't explicitly 491 * say this is disallowed, as it does for child elements, but it 492 * makes sense to have the same treatment. 493 * 494 * We choose to ignore the attribute which is added too late. 495 */ 496 // Generate a warning of the ignored attributes 497 498 // Create the warning message 499 String msg = Utils.messages.createMessage( 500 MsgKey.ER_ILLEGAL_ATTRIBUTE_POSITION,new Object[]{ localName }); 501 502 try { 503 // Prepare to issue the warning message 504 Transformer tran = super.getTransformer(); 505 ErrorListener errHandler = tran.getErrorListener(); 506 507 508 // Issue the warning message 509 if (null != errHandler && m_sourceLocator != null) 510 errHandler.warning(new TransformerException(msg, m_sourceLocator)); 511 else 512 System.out.println(msg); 513 } 514 catch (Exception e){} 515 } 516 } 517 518 /** 519 * @see ExtendedContentHandler#endElement(String) 520 */ 521 public void endElement(String elemName) throws SAXException 522 { 523 endElement(null, null, elemName); 524 } 525 526 /** 527 * This method is used to notify the serializer of a namespace mapping (or node) 528 * that applies to the current element whose startElement() call has already been seen. 529 * The official SAX startPrefixMapping(prefix,uri) is to define a mapping for a child 530 * element that is soon to be seen with a startElement() call. The official SAX call 531 * does not apply to the current element, hence the reason for this method. 532 */ 533 public void namespaceAfterStartElement( 534 final String prefix, 535 final String uri) 536 throws SAXException 537 { 538 539 // hack for XSLTC with finding URI for default namespace 540 if (m_elemContext.m_elementURI == null) 541 { 542 String prefix1 = getPrefixPart(m_elemContext.m_elementName); 543 if (prefix1 == null && EMPTYSTRING.equals(prefix)) 544 { 545 // the elements URI is not known yet, and it 546 // doesn't have a prefix, and we are currently 547 // setting the uri for prefix "", so we have 548 // the uri for the element... lets remember it 549 m_elemContext.m_elementURI = uri; 550 } 551 } 552 startPrefixMapping(prefix,uri,false); 553 return; 554 555 } 556 557 /** 558 * From XSLTC 559 * Declare a prefix to point to a namespace URI. Inform SAX handler 560 * if this is a new prefix mapping. 561 */ 562 protected boolean pushNamespace(String prefix, String uri) 563 { 564 try 565 { 566 if (m_prefixMap.pushNamespace( 567 prefix, uri, m_elemContext.m_currentElemDepth)) 568 { 569 startPrefixMapping(prefix, uri); 570 return true; 571 } 572 } 573 catch (SAXException e) 574 { 575 // falls through 576 } 577 return false; 578 } 579 /** 580 * Try's to reset the super class and reset this class for 581 * re-use, so that you don't need to create a new serializer 582 * (mostly for performance reasons). 583 * 584 * @return true if the class was successfuly reset. 585 */ 586 public boolean reset() 587 { 588 boolean wasReset = false; 589 if (super.reset()) 590 { 591 resetToXMLStream(); 592 wasReset = true; 593 } 594 return wasReset; 595 } 596 597 /** 598 * Reset all of the fields owned by ToStream class 599 * 600 */ 601 private void resetToXMLStream() 602 { 603 this.m_cdataTagOpen = false; 604 605 } 606 607 /** 608 * This method checks for the XML version of output document. 609 * If XML version of output document is not specified, then output 610 * document is of version XML 1.0. 611 * If XML version of output doucment is specified, but it is not either 612 * XML 1.0 or XML 1.1, a warning message is generated, the XML Version of 613 * output document is set to XML 1.0 and processing continues. 614 * @return string (XML version) 615 */ 616 private String getXMLVersion() 617 { 618 String xmlVersion = getVersion(); 619 if(xmlVersion == null || xmlVersion.equals(XMLVERSION10)) 620 { 621 xmlVersion = XMLVERSION10; 622 } 623 else if(xmlVersion.equals(XMLVERSION11)) 624 { 625 xmlVersion = XMLVERSION11; 626 } 627 else 628 { 629 String msg = Utils.messages.createMessage( 630 MsgKey.ER_XML_VERSION_NOT_SUPPORTED,new Object[]{ xmlVersion }); 631 try 632 { 633 // Prepare to issue the warning message 634 Transformer tran = super.getTransformer(); 635 ErrorListener errHandler = tran.getErrorListener(); 636 // Issue the warning message 637 if (null != errHandler && m_sourceLocator != null) 638 errHandler.warning(new TransformerException(msg, m_sourceLocator)); 639 else 640 System.out.println(msg); 641 } 642 catch (Exception e){} 643 xmlVersion = XMLVERSION10; 644 } 645 return xmlVersion; 646 } 647 }