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: ToStream.java 1225444 2011-12-29 05:52:39Z mrglavas $ 020 */ 021 package org.apache.xml.serializer; 022 023 import java.io.IOException; 024 import java.io.OutputStream; 025 import java.io.OutputStreamWriter; 026 import java.io.UnsupportedEncodingException; 027 import java.io.Writer; 028 import java.util.EmptyStackException; 029 import java.util.Enumeration; 030 import java.util.Iterator; 031 import java.util.Properties; 032 import java.util.Set; 033 import java.util.StringTokenizer; 034 import java.util.Vector; 035 036 import javax.xml.transform.ErrorListener; 037 import javax.xml.transform.OutputKeys; 038 import javax.xml.transform.Transformer; 039 import javax.xml.transform.TransformerException; 040 041 import org.apache.xml.serializer.utils.MsgKey; 042 import org.apache.xml.serializer.utils.Utils; 043 import org.apache.xml.serializer.utils.WrappedRuntimeException; 044 import org.w3c.dom.Node; 045 import org.xml.sax.Attributes; 046 import org.xml.sax.ContentHandler; 047 import org.xml.sax.SAXException; 048 049 /** 050 * This abstract class is a base class for other stream 051 * serializers (xml, html, text ...) that write output to a stream. 052 * 053 * @xsl.usage internal 054 */ 055 abstract public class ToStream extends SerializerBase 056 { 057 058 private static final String COMMENT_BEGIN = "<!--"; 059 private static final String COMMENT_END = "-->"; 060 061 /** Stack to keep track of disabling output escaping. */ 062 protected BoolStack m_disableOutputEscapingStates = new BoolStack(); 063 064 065 /** 066 * The encoding information associated with this serializer. 067 * Although initially there is no encoding, 068 * there is a dummy EncodingInfo object that will say 069 * that every character is in the encoding. This is useful 070 * for a serializer that is in temporary output state and has 071 * no associated encoding. A serializer in final output state 072 * will have an encoding, and will worry about whether 073 * single chars or surrogate pairs of high/low chars form 074 * characters in the output encoding. 075 */ 076 EncodingInfo m_encodingInfo = new EncodingInfo(null,null, '\u0000'); 077 078 /** 079 * Stack to keep track of whether or not we need to 080 * preserve whitespace. 081 * 082 * Used to push/pop values used for the field m_ispreserve, but 083 * m_ispreserve is only relevant if m_doIndent is true. 084 * If m_doIndent is false this field has no impact. 085 * 086 */ 087 protected BoolStack m_preserves = new BoolStack(); 088 089 /** 090 * State flag to tell if preservation of whitespace 091 * is important. 092 * 093 * Used only in shouldIndent() but only if m_doIndent is true. 094 * If m_doIndent is false this flag has no impact. 095 * 096 */ 097 protected boolean m_ispreserve = false; 098 099 /** 100 * State flag that tells if the previous node processed 101 * was text, so we can tell if we should preserve whitespace. 102 * 103 * Used in endDocument() and shouldIndent() but 104 * only if m_doIndent is true. 105 * If m_doIndent is false this flag has no impact. 106 */ 107 protected boolean m_isprevtext = false; 108 109 private static final char[] s_systemLineSep; 110 static { 111 s_systemLineSep = SecuritySupport.getSystemProperty("line.separator").toCharArray(); 112 } 113 114 /** 115 * The system line separator for writing out line breaks. 116 * The default value is from the system property, 117 * but this value can be set through the xsl:output 118 * extension attribute xalan:line-separator. 119 */ 120 protected char[] m_lineSep = s_systemLineSep; 121 122 123 /** 124 * True if the the system line separator is to be used. 125 */ 126 protected boolean m_lineSepUse = true; 127 128 /** 129 * The length of the line seperator, since the write is done 130 * one character at a time. 131 */ 132 protected int m_lineSepLen = m_lineSep.length; 133 134 /** 135 * Map that tells which characters should have special treatment, and it 136 * provides character to entity name lookup. 137 */ 138 protected CharInfo m_charInfo; 139 140 /** True if we control the buffer, and we should flush the output on endDocument. */ 141 boolean m_shouldFlush = true; 142 143 /** 144 * Add space before '/>' for XHTML. 145 */ 146 protected boolean m_spaceBeforeClose = false; 147 148 /** 149 * Flag to signal that a newline should be added. 150 * 151 * Used only in indent() which is called only if m_doIndent is true. 152 * If m_doIndent is false this flag has no impact. 153 */ 154 boolean m_startNewLine; 155 156 /** 157 * Tells if we're in an internal document type subset. 158 */ 159 protected boolean m_inDoctype = false; 160 161 /** 162 * Flag to quickly tell if the encoding is UTF8. 163 */ 164 boolean m_isUTF8 = false; 165 166 167 /** 168 * remembers if we are in between the startCDATA() and endCDATA() callbacks 169 */ 170 protected boolean m_cdataStartCalled = false; 171 172 /** 173 * If this flag is true DTD entity references are not left as-is, 174 * which is exiting older behavior. 175 */ 176 private boolean m_expandDTDEntities = true; 177 178 179 /** 180 * Default constructor 181 */ 182 public ToStream() 183 { 184 } 185 186 /** 187 * This helper method to writes out "]]>" when closing a CDATA section. 188 * 189 * @throws org.xml.sax.SAXException 190 */ 191 protected void closeCDATA() throws org.xml.sax.SAXException 192 { 193 try 194 { 195 m_writer.write(CDATA_DELIMITER_CLOSE); 196 // write out a CDATA section closing "]]>" 197 m_cdataTagOpen = false; // Remember that we have done so. 198 } 199 catch (IOException e) 200 { 201 throw new SAXException(e); 202 } 203 } 204 205 /** 206 * Serializes the DOM node. Throws an exception only if an I/O 207 * exception occured while serializing. 208 * 209 * @param node Node to serialize. 210 * @throws IOException An I/O exception occured while serializing 211 */ 212 public void serialize(Node node) throws IOException 213 { 214 215 try 216 { 217 TreeWalker walker = 218 new TreeWalker(this); 219 220 walker.traverse(node); 221 } 222 catch (org.xml.sax.SAXException se) 223 { 224 throw new WrappedRuntimeException(se); 225 } 226 } 227 228 /** 229 * Taken from XSLTC 230 */ 231 protected boolean m_escaping = true; 232 233 /** 234 * Flush the formatter's result stream. 235 * 236 * @throws org.xml.sax.SAXException 237 */ 238 protected final void flushWriter() throws org.xml.sax.SAXException 239 { 240 final java.io.Writer writer = m_writer; 241 if (null != writer) 242 { 243 try 244 { 245 if (writer instanceof WriterToUTF8Buffered) 246 { 247 if (m_shouldFlush) 248 ((WriterToUTF8Buffered) writer).flush(); 249 else 250 ((WriterToUTF8Buffered) writer).flushBuffer(); 251 } 252 if (writer instanceof WriterToASCI) 253 { 254 if (m_shouldFlush) 255 writer.flush(); 256 } 257 else 258 { 259 // Flush always. 260 // Not a great thing if the writer was created 261 // by this class, but don't have a choice. 262 writer.flush(); 263 } 264 } 265 catch (IOException ioe) 266 { 267 throw new org.xml.sax.SAXException(ioe); 268 } 269 } 270 } 271 272 OutputStream m_outputStream; 273 /** 274 * Get the output stream where the events will be serialized to. 275 * 276 * @return reference to the result stream, or null of only a writer was 277 * set. 278 */ 279 public OutputStream getOutputStream() 280 { 281 return m_outputStream; 282 } 283 284 // Implement DeclHandler 285 286 /** 287 * Report an element type declaration. 288 * 289 * <p>The content model will consist of the string "EMPTY", the 290 * string "ANY", or a parenthesised group, optionally followed 291 * by an occurrence indicator. The model will be normalized so 292 * that all whitespace is removed,and will include the enclosing 293 * parentheses.</p> 294 * 295 * @param name The element type name. 296 * @param model The content model as a normalized string. 297 * @exception SAXException The application may raise an exception. 298 */ 299 public void elementDecl(String name, String model) throws SAXException 300 { 301 // Do not inline external DTD 302 if (m_inExternalDTD) 303 return; 304 try 305 { 306 final java.io.Writer writer = m_writer; 307 DTDprolog(); 308 309 writer.write("<!ELEMENT "); 310 writer.write(name); 311 writer.write(' '); 312 writer.write(model); 313 writer.write('>'); 314 writer.write(m_lineSep, 0, m_lineSepLen); 315 } 316 catch (IOException e) 317 { 318 throw new SAXException(e); 319 } 320 321 } 322 323 /** 324 * Report an internal entity declaration. 325 * 326 * <p>Only the effective (first) declaration for each entity 327 * will be reported.</p> 328 * 329 * @param name The name of the entity. If it is a parameter 330 * entity, the name will begin with '%'. 331 * @param value The replacement text of the entity. 332 * @exception SAXException The application may raise an exception. 333 * @see #externalEntityDecl 334 * @see org.xml.sax.DTDHandler#unparsedEntityDecl 335 */ 336 public void internalEntityDecl(String name, String value) 337 throws SAXException 338 { 339 // Do not inline external DTD 340 if (m_inExternalDTD) 341 return; 342 try 343 { 344 DTDprolog(); 345 outputEntityDecl(name, value); 346 } 347 catch (IOException e) 348 { 349 throw new SAXException(e); 350 } 351 352 } 353 354 /** 355 * Output the doc type declaration. 356 * 357 * @param name non-null reference to document type name. 358 * NEEDSDOC @param value 359 * 360 * @throws org.xml.sax.SAXException 361 */ 362 void outputEntityDecl(String name, String value) throws IOException 363 { 364 final java.io.Writer writer = m_writer; 365 writer.write("<!ENTITY "); 366 writer.write(name); 367 writer.write(" \""); 368 writer.write(value); 369 writer.write("\">"); 370 writer.write(m_lineSep, 0, m_lineSepLen); 371 } 372 373 /** 374 * Output a system-dependent line break. 375 * 376 * @throws org.xml.sax.SAXException 377 */ 378 protected final void outputLineSep() throws IOException 379 { 380 381 m_writer.write(m_lineSep, 0, m_lineSepLen); 382 } 383 384 void setProp(String name, String val, boolean defaultVal) { 385 if (val != null) { 386 387 388 char first = getFirstCharLocName(name); 389 switch (first) { 390 case 'c': 391 if (OutputKeys.CDATA_SECTION_ELEMENTS.equals(name)) { 392 String cdataSectionNames = val; 393 addCdataSectionElements(cdataSectionNames); 394 } 395 break; 396 case 'd': 397 if (OutputKeys.DOCTYPE_SYSTEM.equals(name)) { 398 this.m_doctypeSystem = val; 399 } else if (OutputKeys.DOCTYPE_PUBLIC.equals(name)) { 400 this.m_doctypePublic = val; 401 if (val.startsWith("-//W3C//DTD XHTML")) 402 m_spaceBeforeClose = true; 403 } 404 break; 405 case 'e': 406 String newEncoding = val; 407 if (OutputKeys.ENCODING.equals(name)) { 408 String possible_encoding = Encodings.getMimeEncoding(val); 409 if (possible_encoding != null) { 410 // if the encoding is being set, try to get the 411 // preferred 412 // mime-name and set it too. 413 super.setProp("mime-name", possible_encoding, 414 defaultVal); 415 } 416 final String oldExplicitEncoding = getOutputPropertyNonDefault(OutputKeys.ENCODING); 417 final String oldDefaultEncoding = getOutputPropertyDefault(OutputKeys.ENCODING); 418 if ( (defaultVal && ( oldDefaultEncoding == null || !oldDefaultEncoding.equalsIgnoreCase(newEncoding))) 419 || ( !defaultVal && (oldExplicitEncoding == null || !oldExplicitEncoding.equalsIgnoreCase(newEncoding) ))) { 420 // We are trying to change the default or the non-default setting of the encoding to a different value 421 // from what it was 422 423 EncodingInfo encodingInfo = Encodings.getEncodingInfo(newEncoding); 424 if (newEncoding != null && encodingInfo.name == null) { 425 // We tried to get an EncodingInfo for Object for the given 426 // encoding, but it came back with an internall null name 427 // so the encoding is not supported by the JDK, issue a message. 428 final String msg = Utils.messages.createMessage( 429 MsgKey.ER_ENCODING_NOT_SUPPORTED,new Object[]{ newEncoding }); 430 431 final String msg2 = 432 "Warning: encoding \"" + newEncoding + "\" not supported, using " 433 + Encodings.DEFAULT_MIME_ENCODING; 434 try { 435 // Prepare to issue the warning message 436 final Transformer tran = super.getTransformer(); 437 if (tran != null) { 438 final ErrorListener errHandler = tran 439 .getErrorListener(); 440 // Issue the warning message 441 if (null != errHandler 442 && m_sourceLocator != null) { 443 errHandler 444 .warning(new TransformerException( 445 msg, m_sourceLocator)); 446 errHandler 447 .warning(new TransformerException( 448 msg2, m_sourceLocator)); 449 } else { 450 System.out.println(msg); 451 System.out.println(msg2); 452 } 453 } else { 454 System.out.println(msg); 455 System.out.println(msg2); 456 } 457 } catch (Exception e) { 458 } 459 460 // We said we are using UTF-8, so use it 461 newEncoding = Encodings.DEFAULT_MIME_ENCODING; 462 val = Encodings.DEFAULT_MIME_ENCODING; // to store the modified value into the properties a little later 463 encodingInfo = Encodings.getEncodingInfo(newEncoding); 464 465 } 466 // The encoding was good, or was forced to UTF-8 above 467 468 469 // If there is already a non-default set encoding and we 470 // are trying to set the default encoding, skip the this block 471 // as the non-default value is already the one to use. 472 if (defaultVal == false || oldExplicitEncoding == null) { 473 m_encodingInfo = encodingInfo; 474 if (newEncoding != null) 475 m_isUTF8 = newEncoding.equals(Encodings.DEFAULT_MIME_ENCODING); 476 477 // if there was a previously set OutputStream 478 OutputStream os = getOutputStream(); 479 if (os != null) { 480 Writer w = getWriter(); 481 482 // If the writer was previously set, but 483 // set by the user, or if the new encoding is the same 484 // as the old encoding, skip this block 485 String oldEncoding = getOutputProperty(OutputKeys.ENCODING); 486 if ((w == null || !m_writer_set_by_user) 487 && !newEncoding.equalsIgnoreCase(oldEncoding)) { 488 // Make the change of encoding in our internal 489 // table, then call setOutputStreamInternal 490 // which will stomp on the old Writer (if any) 491 // with a new Writer with the new encoding. 492 super.setProp(name, val, defaultVal); 493 setOutputStreamInternal(os,false); 494 } 495 } 496 } 497 } 498 } 499 break; 500 case 'i': 501 if (OutputPropertiesFactory.S_KEY_INDENT_AMOUNT.equals(name)) { 502 setIndentAmount(Integer.parseInt(val)); 503 } else if (OutputKeys.INDENT.equals(name)) { 504 boolean b = "yes".equals(val) ? true : false; 505 m_doIndent = b; 506 } 507 508 break; 509 case 'l': 510 if (OutputPropertiesFactory.S_KEY_LINE_SEPARATOR.equals(name)) { 511 m_lineSep = val.toCharArray(); 512 m_lineSepLen = m_lineSep.length; 513 } 514 515 break; 516 case 'm': 517 if (OutputKeys.MEDIA_TYPE.equals(name)) { 518 m_mediatype = val; 519 } 520 break; 521 case 'o': 522 if (OutputKeys.OMIT_XML_DECLARATION.equals(name)) { 523 boolean b = "yes".equals(val) ? true : false; 524 this.m_shouldNotWriteXMLHeader = b; 525 } 526 break; 527 case 's': 528 // if standalone was explicitly specified 529 if (OutputKeys.STANDALONE.equals(name)) { 530 if (defaultVal) { 531 setStandaloneInternal(val); 532 } else { 533 m_standaloneWasSpecified = true; 534 setStandaloneInternal(val); 535 } 536 } 537 538 break; 539 case 'v': 540 if (OutputKeys.VERSION.equals(name)) { 541 m_version = val; 542 } 543 break; 544 default: 545 break; 546 547 } 548 super.setProp(name, val, defaultVal); 549 } 550 } 551 /** 552 * Specifies an output format for this serializer. It the 553 * serializer has already been associated with an output format, 554 * it will switch to the new format. This method should not be 555 * called while the serializer is in the process of serializing 556 * a document. 557 * 558 * @param format The output format to use 559 */ 560 public void setOutputFormat(Properties format) 561 { 562 563 boolean shouldFlush = m_shouldFlush; 564 565 if (format != null) 566 { 567 // Set the default values first, 568 // and the non-default values after that, 569 // just in case there is some unexpected 570 // residual values left over from over-ridden default values 571 Enumeration propNames; 572 propNames = format.propertyNames(); 573 while (propNames.hasMoreElements()) 574 { 575 String key = (String) propNames.nextElement(); 576 // Get the value, possibly a default value 577 String value = format.getProperty(key); 578 // Get the non-default value (if any). 579 String explicitValue = (String) format.get(key); 580 if (explicitValue == null && value != null) { 581 // This is a default value 582 this.setOutputPropertyDefault(key,value); 583 } 584 if (explicitValue != null) { 585 // This is an explicit non-default value 586 this.setOutputProperty(key,explicitValue); 587 } 588 } 589 } 590 591 // Access this only from the Hashtable level... we don't want to 592 // get default properties. 593 String entitiesFileName = 594 (String) format.get(OutputPropertiesFactory.S_KEY_ENTITIES); 595 596 if (null != entitiesFileName) 597 { 598 599 String method = 600 (String) format.get(OutputKeys.METHOD); 601 602 m_charInfo = CharInfo.getCharInfo(entitiesFileName, method); 603 } 604 605 606 607 608 m_shouldFlush = shouldFlush; 609 } 610 611 /** 612 * Returns the output format for this serializer. 613 * 614 * @return The output format in use 615 */ 616 public Properties getOutputFormat() { 617 Properties def = new Properties(); 618 { 619 Set s = getOutputPropDefaultKeys(); 620 Iterator i = s.iterator(); 621 while (i.hasNext()) { 622 String key = (String) i.next(); 623 String val = getOutputPropertyDefault(key); 624 def.put(key, val); 625 } 626 } 627 628 Properties props = new Properties(def); 629 { 630 Set s = getOutputPropKeys(); 631 Iterator i = s.iterator(); 632 while (i.hasNext()) { 633 String key = (String) i.next(); 634 String val = getOutputPropertyNonDefault(key); 635 if (val != null) 636 props.put(key, val); 637 } 638 } 639 return props; 640 } 641 642 /** 643 * Specifies a writer to which the document should be serialized. 644 * This method should not be called while the serializer is in 645 * the process of serializing a document. 646 * 647 * @param writer The output writer stream 648 */ 649 public void setWriter(Writer writer) 650 { 651 setWriterInternal(writer, true); 652 } 653 654 private boolean m_writer_set_by_user; 655 private void setWriterInternal(Writer writer, boolean setByUser) { 656 657 m_writer_set_by_user = setByUser; 658 m_writer = writer; 659 // if we are tracing events we need to trace what 660 // characters are written to the output writer. 661 if (m_tracer != null) { 662 boolean noTracerYet = true; 663 Writer w2 = m_writer; 664 while (w2 instanceof WriterChain) { 665 if (w2 instanceof SerializerTraceWriter) { 666 noTracerYet = false; 667 break; 668 } 669 w2 = ((WriterChain)w2).getWriter(); 670 } 671 if (noTracerYet) 672 m_writer = new SerializerTraceWriter(m_writer, m_tracer); 673 } 674 } 675 676 /** 677 * Set if the operating systems end-of-line line separator should 678 * be used when serializing. If set false NL character 679 * (decimal 10) is left alone, otherwise the new-line will be replaced on 680 * output with the systems line separator. For example on UNIX this is 681 * NL, while on Windows it is two characters, CR NL, where CR is the 682 * carriage-return (decimal 13). 683 * 684 * @param use_sytem_line_break True if an input NL is replaced with the 685 * operating systems end-of-line separator. 686 * @return The previously set value of the serializer. 687 */ 688 public boolean setLineSepUse(boolean use_sytem_line_break) 689 { 690 boolean oldValue = m_lineSepUse; 691 m_lineSepUse = use_sytem_line_break; 692 return oldValue; 693 } 694 695 /** 696 * Specifies an output stream to which the document should be 697 * serialized. This method should not be called while the 698 * serializer is in the process of serializing a document. 699 * <p> 700 * The encoding specified in the output properties is used, or 701 * if no encoding was specified, the default for the selected 702 * output method. 703 * 704 * @param output The output stream 705 */ 706 public void setOutputStream(OutputStream output) 707 { 708 setOutputStreamInternal(output, true); 709 } 710 711 private void setOutputStreamInternal(OutputStream output, boolean setByUser) 712 { 713 m_outputStream = output; 714 String encoding = getOutputProperty(OutputKeys.ENCODING); 715 if (Encodings.DEFAULT_MIME_ENCODING.equalsIgnoreCase(encoding)) 716 { 717 // We wrap the OutputStream with a writer, but 718 // not one set by the user 719 setWriterInternal(new WriterToUTF8Buffered(output), false); 720 } else if ( 721 "WINDOWS-1250".equals(encoding) 722 || "US-ASCII".equals(encoding) 723 || "ASCII".equals(encoding)) 724 { 725 setWriterInternal(new WriterToASCI(output), false); 726 } else if (encoding != null) { 727 Writer osw = null; 728 try 729 { 730 osw = Encodings.getWriter(output, encoding); 731 } 732 catch (UnsupportedEncodingException uee) 733 { 734 osw = null; 735 } 736 737 738 if (osw == null) { 739 System.out.println( 740 "Warning: encoding \"" 741 + encoding 742 + "\" not supported" 743 + ", using " 744 + Encodings.DEFAULT_MIME_ENCODING); 745 746 encoding = Encodings.DEFAULT_MIME_ENCODING; 747 setEncoding(encoding); 748 try { 749 osw = Encodings.getWriter(output, encoding); 750 } catch (UnsupportedEncodingException e) { 751 // We can't really get here, UTF-8 is always supported 752 // This try-catch exists to make the compiler happy 753 e.printStackTrace(); 754 } 755 } 756 setWriterInternal(osw,false); 757 } 758 else { 759 // don't have any encoding, but we have an OutputStream 760 Writer osw = new OutputStreamWriter(output); 761 setWriterInternal(osw,false); 762 } 763 } 764 765 /** 766 * @see SerializationHandler#setEscaping(boolean) 767 */ 768 public boolean setEscaping(boolean escape) 769 { 770 final boolean temp = m_escaping; 771 m_escaping = escape; 772 return temp; 773 774 } 775 776 777 /** 778 * Might print a newline character and the indentation amount 779 * of the given depth. 780 * 781 * @param depth the indentation depth (element nesting depth) 782 * 783 * @throws org.xml.sax.SAXException if an error occurs during writing. 784 */ 785 protected void indent(int depth) throws IOException 786 { 787 788 if (m_startNewLine) 789 outputLineSep(); 790 /* For m_indentAmount > 0 this extra test might be slower 791 * but Xalan's default value is 0, so this extra test 792 * will run faster in that situation. 793 */ 794 if (m_indentAmount > 0) 795 printSpace(depth * m_indentAmount); 796 797 } 798 799 /** 800 * Indent at the current element nesting depth. 801 * @throws IOException 802 */ 803 protected void indent() throws IOException 804 { 805 indent(m_elemContext.m_currentElemDepth); 806 } 807 /** 808 * Prints <var>n</var> spaces. 809 * @param n Number of spaces to print. 810 * 811 * @throws org.xml.sax.SAXException if an error occurs when writing. 812 */ 813 private void printSpace(int n) throws IOException 814 { 815 final java.io.Writer writer = m_writer; 816 for (int i = 0; i < n; i++) 817 { 818 writer.write(' '); 819 } 820 821 } 822 823 /** 824 * Report an attribute type declaration. 825 * 826 * <p>Only the effective (first) declaration for an attribute will 827 * be reported. The type will be one of the strings "CDATA", 828 * "ID", "IDREF", "IDREFS", "NMTOKEN", "NMTOKENS", "ENTITY", 829 * "ENTITIES", or "NOTATION", or a parenthesized token group with 830 * the separator "|" and all whitespace removed.</p> 831 * 832 * @param eName The name of the associated element. 833 * @param aName The name of the attribute. 834 * @param type A string representing the attribute type. 835 * @param valueDefault A string representing the attribute default 836 * ("#IMPLIED", "#REQUIRED", or "#FIXED") or null if 837 * none of these applies. 838 * @param value A string representing the attribute's default value, 839 * or null if there is none. 840 * @exception SAXException The application may raise an exception. 841 */ 842 public void attributeDecl( 843 String eName, 844 String aName, 845 String type, 846 String valueDefault, 847 String value) 848 throws SAXException 849 { 850 // Do not inline external DTD 851 if (m_inExternalDTD) 852 return; 853 try 854 { 855 final java.io.Writer writer = m_writer; 856 DTDprolog(); 857 858 writer.write("<!ATTLIST "); 859 writer.write(eName); 860 writer.write(' '); 861 862 writer.write(aName); 863 writer.write(' '); 864 writer.write(type); 865 if (valueDefault != null) 866 { 867 writer.write(' '); 868 writer.write(valueDefault); 869 } 870 871 //writer.write(" "); 872 //writer.write(value); 873 writer.write('>'); 874 writer.write(m_lineSep, 0, m_lineSepLen); 875 } 876 catch (IOException e) 877 { 878 throw new SAXException(e); 879 } 880 } 881 882 /** 883 * Get the character stream where the events will be serialized to. 884 * 885 * @return Reference to the result Writer, or null. 886 */ 887 public Writer getWriter() 888 { 889 return m_writer; 890 } 891 892 /** 893 * Report a parsed external entity declaration. 894 * 895 * <p>Only the effective (first) declaration for each entity 896 * will be reported.</p> 897 * 898 * @param name The name of the entity. If it is a parameter 899 * entity, the name will begin with '%'. 900 * @param publicId The declared public identifier of the entity, or 901 * null if none was declared. 902 * @param systemId The declared system identifier of the entity. 903 * @exception SAXException The application may raise an exception. 904 * @see #internalEntityDecl 905 * @see org.xml.sax.DTDHandler#unparsedEntityDecl 906 */ 907 public void externalEntityDecl( 908 String name, 909 String publicId, 910 String systemId) 911 throws SAXException 912 { 913 try { 914 DTDprolog(); 915 916 m_writer.write("<!ENTITY "); 917 m_writer.write(name); 918 if (publicId != null) { 919 m_writer.write(" PUBLIC \""); 920 m_writer.write(publicId); 921 922 } 923 else { 924 m_writer.write(" SYSTEM \""); 925 m_writer.write(systemId); 926 } 927 m_writer.write("\" >"); 928 m_writer.write(m_lineSep, 0, m_lineSepLen); 929 } catch (IOException e) { 930 // TODO Auto-generated catch block 931 e.printStackTrace(); 932 } 933 934 } 935 936 /** 937 * Tell if this character can be written without escaping. 938 */ 939 protected boolean escapingNotNeeded(char ch) 940 { 941 final boolean ret; 942 if (ch < 127) 943 { 944 // This is the old/fast code here, but is this 945 // correct for all encodings? 946 if (ch >= CharInfo.S_SPACE || (CharInfo.S_LINEFEED == ch || 947 CharInfo.S_CARRIAGERETURN == ch || CharInfo.S_HORIZONAL_TAB == ch)) 948 ret= true; 949 else 950 ret = false; 951 } 952 else { 953 ret = m_encodingInfo.isInEncoding(ch); 954 } 955 return ret; 956 } 957 958 /** 959 * Once a surrogate has been detected, write out the pair of 960 * characters if it is in the encoding, or if there is no 961 * encoding, otherwise write out an entity reference 962 * of the value of the unicode code point of the character 963 * represented by the high/low surrogate pair. 964 * <p> 965 * An exception is thrown if there is no low surrogate in the pair, 966 * because the array ends unexpectely, or if the low char is there 967 * but its value is such that it is not a low surrogate. 968 * 969 * @param c the first (high) part of the surrogate, which 970 * must be confirmed before calling this method. 971 * @param ch Character array. 972 * @param i position Where the surrogate was detected. 973 * @param end The end index of the significant characters. 974 * @return 0 if the pair of characters was written out as-is, 975 * the unicode code point of the character represented by 976 * the surrogate pair if an entity reference with that value 977 * was written out. 978 * 979 * @throws IOException 980 * @throws org.xml.sax.SAXException if invalid UTF-16 surrogate detected. 981 */ 982 protected int writeUTF16Surrogate(char c, char ch[], int i, int end) 983 throws IOException 984 { 985 int codePoint = 0; 986 if (i + 1 >= end) 987 { 988 throw new IOException( 989 Utils.messages.createMessage( 990 MsgKey.ER_INVALID_UTF16_SURROGATE, 991 new Object[] { Integer.toHexString((int) c)})); 992 } 993 994 final char high = c; 995 final char low = ch[i+1]; 996 if (!Encodings.isLowUTF16Surrogate(low)) { 997 throw new IOException( 998 Utils.messages.createMessage( 999 MsgKey.ER_INVALID_UTF16_SURROGATE, 1000 new Object[] { 1001 Integer.toHexString((int) c) 1002 + " " 1003 + Integer.toHexString(low)})); 1004 } 1005 1006 final java.io.Writer writer = m_writer; 1007 1008 // If we make it to here we have a valid high, low surrogate pair 1009 if (m_encodingInfo.isInEncoding(c,low)) { 1010 // If the character formed by the surrogate pair 1011 // is in the encoding, so just write it out 1012 writer.write(ch,i,2); 1013 } 1014 else { 1015 // Don't know what to do with this char, it is 1016 // not in the encoding and not a high char in 1017 // a surrogate pair, so write out as an entity ref 1018 final String encoding = getEncoding(); 1019 if (encoding != null) { 1020 /* The output encoding is known, 1021 * so somthing is wrong. 1022 */ 1023 codePoint = Encodings.toCodePoint(high, low); 1024 // not in the encoding, so write out a character reference 1025 writer.write('&'); 1026 writer.write('#'); 1027 writer.write(Integer.toString(codePoint)); 1028 writer.write(';'); 1029 } else { 1030 /* The output encoding is not known, 1031 * so just write it out as-is. 1032 */ 1033 writer.write(ch, i, 2); 1034 } 1035 } 1036 // non-zero only if character reference was written out. 1037 return codePoint; 1038 } 1039 1040 /** 1041 * Handle one of the default entities, return false if it 1042 * is not a default entity. 1043 * 1044 * @param ch character to be escaped. 1045 * @param i index into character array. 1046 * @param chars non-null reference to character array. 1047 * @param len length of chars. 1048 * @param fromTextNode true if the characters being processed 1049 * are from a text node, false if they are from an attribute value 1050 * @param escLF true if the linefeed should be escaped. 1051 * 1052 * @return i+1 if the character was written, else i. 1053 * 1054 * @throws java.io.IOException 1055 */ 1056 int accumDefaultEntity( 1057 java.io.Writer writer, 1058 char ch, 1059 int i, 1060 char[] chars, 1061 int len, 1062 boolean fromTextNode, 1063 boolean escLF) 1064 throws IOException 1065 { 1066 1067 if (!escLF && CharInfo.S_LINEFEED == ch) 1068 { 1069 writer.write(m_lineSep, 0, m_lineSepLen); 1070 } 1071 else 1072 { 1073 // if this is text node character and a special one of those, 1074 // or if this is a character from attribute value and a special one of those 1075 if ((fromTextNode && m_charInfo.shouldMapTextChar(ch)) || (!fromTextNode && m_charInfo.shouldMapAttrChar(ch))) 1076 { 1077 String outputStringForChar = m_charInfo.getOutputStringForChar(ch); 1078 1079 if (null != outputStringForChar) 1080 { 1081 writer.write(outputStringForChar); 1082 } 1083 else 1084 return i; 1085 } 1086 else 1087 return i; 1088 } 1089 1090 return i + 1; 1091 1092 } 1093 /** 1094 * Normalize the characters, but don't escape. 1095 * 1096 * @param ch The characters from the XML document. 1097 * @param start The start position in the array. 1098 * @param length The number of characters to read from the array. 1099 * @param isCData true if a CDATA block should be built around the characters. 1100 * @param useSystemLineSeparator true if the operating systems 1101 * end-of-line separator should be output rather than a new-line character. 1102 * 1103 * @throws IOException 1104 * @throws org.xml.sax.SAXException 1105 */ 1106 void writeNormalizedChars( 1107 char ch[], 1108 int start, 1109 int length, 1110 boolean isCData, 1111 boolean useSystemLineSeparator) 1112 throws IOException, org.xml.sax.SAXException 1113 { 1114 final java.io.Writer writer = m_writer; 1115 int end = start + length; 1116 1117 for (int i = start; i < end; i++) 1118 { 1119 char c = ch[i]; 1120 1121 if (CharInfo.S_LINEFEED == c && useSystemLineSeparator) 1122 { 1123 writer.write(m_lineSep, 0, m_lineSepLen); 1124 } 1125 else if (isCData && (!escapingNotNeeded(c))) 1126 { 1127 // if (i != 0) 1128 if (m_cdataTagOpen) 1129 closeCDATA(); 1130 1131 // This needs to go into a function... 1132 if (Encodings.isHighUTF16Surrogate(c)) 1133 { 1134 writeUTF16Surrogate(c, ch, i, end); 1135 i++ ; // process two input characters 1136 } 1137 else 1138 { 1139 writer.write("&#"); 1140 1141 String intStr = Integer.toString((int) c); 1142 1143 writer.write(intStr); 1144 writer.write(';'); 1145 } 1146 1147 // if ((i != 0) && (i < (end - 1))) 1148 // if (!m_cdataTagOpen && (i < (end - 1))) 1149 // { 1150 // writer.write(CDATA_DELIMITER_OPEN); 1151 // m_cdataTagOpen = true; 1152 // } 1153 } 1154 else if ( 1155 isCData 1156 && ((i < (end - 2)) 1157 && (']' == c) 1158 && (']' == ch[i + 1]) 1159 && ('>' == ch[i + 2]))) 1160 { 1161 writer.write(CDATA_CONTINUE); 1162 1163 i += 2; 1164 } 1165 else 1166 { 1167 if (escapingNotNeeded(c)) 1168 { 1169 if (isCData && !m_cdataTagOpen) 1170 { 1171 writer.write(CDATA_DELIMITER_OPEN); 1172 m_cdataTagOpen = true; 1173 } 1174 writer.write(c); 1175 } 1176 1177 // This needs to go into a function... 1178 else if (Encodings.isHighUTF16Surrogate(c)) 1179 { 1180 if (m_cdataTagOpen) 1181 closeCDATA(); 1182 writeUTF16Surrogate(c, ch, i, end); 1183 i++; // process two input characters 1184 } 1185 else 1186 { 1187 if (m_cdataTagOpen) 1188 closeCDATA(); 1189 writer.write("&#"); 1190 1191 String intStr = Integer.toString((int) c); 1192 1193 writer.write(intStr); 1194 writer.write(';'); 1195 } 1196 } 1197 } 1198 1199 } 1200 1201 /** 1202 * Ends an un-escaping section. 1203 * 1204 * @see #startNonEscaping 1205 * 1206 * @throws org.xml.sax.SAXException 1207 */ 1208 public void endNonEscaping() throws org.xml.sax.SAXException 1209 { 1210 m_disableOutputEscapingStates.pop(); 1211 } 1212 1213 /** 1214 * Starts an un-escaping section. All characters printed within an un- 1215 * escaping section are printed as is, without escaping special characters 1216 * into entity references. Only XML and HTML serializers need to support 1217 * this method. 1218 * <p> The contents of the un-escaping section will be delivered through the 1219 * regular <tt>characters</tt> event. 1220 * 1221 * @throws org.xml.sax.SAXException 1222 */ 1223 public void startNonEscaping() throws org.xml.sax.SAXException 1224 { 1225 m_disableOutputEscapingStates.push(true); 1226 } 1227 1228 /** 1229 * Receive notification of cdata. 1230 * 1231 * <p>The Parser will call this method to report each chunk of 1232 * character data. SAX parsers may return all contiguous character 1233 * data in a single chunk, or they may split it into several 1234 * chunks; however, all of the characters in any single event 1235 * must come from the same external entity, so that the Locator 1236 * provides useful information.</p> 1237 * 1238 * <p>The application must not attempt to read from the array 1239 * outside of the specified range.</p> 1240 * 1241 * <p>Note that some parsers will report whitespace using the 1242 * ignorableWhitespace() method rather than this one (validating 1243 * parsers must do so).</p> 1244 * 1245 * @param ch The characters from the XML document. 1246 * @param start The start position in the array. 1247 * @param length The number of characters to read from the array. 1248 * @throws org.xml.sax.SAXException Any SAX exception, possibly 1249 * wrapping another exception. 1250 * @see #ignorableWhitespace 1251 * @see org.xml.sax.Locator 1252 * 1253 * @throws org.xml.sax.SAXException 1254 */ 1255 protected void cdata(char ch[], int start, final int length) 1256 throws org.xml.sax.SAXException 1257 { 1258 1259 try 1260 { 1261 final int old_start = start; 1262 if (m_elemContext.m_startTagOpen) 1263 { 1264 closeStartTag(); 1265 m_elemContext.m_startTagOpen = false; 1266 } 1267 m_ispreserve = true; 1268 1269 if (shouldIndent()) 1270 indent(); 1271 1272 boolean writeCDataBrackets = 1273 (((length >= 1) && escapingNotNeeded(ch[start]))); 1274 1275 /* Write out the CDATA opening delimiter only if 1276 * we are supposed to, and if we are not already in 1277 * the middle of a CDATA section 1278 */ 1279 if (writeCDataBrackets && !m_cdataTagOpen) 1280 { 1281 m_writer.write(CDATA_DELIMITER_OPEN); 1282 m_cdataTagOpen = true; 1283 } 1284 1285 // writer.write(ch, start, length); 1286 if (isEscapingDisabled()) 1287 { 1288 charactersRaw(ch, start, length); 1289 } 1290 else 1291 writeNormalizedChars(ch, start, length, true, m_lineSepUse); 1292 1293 /* used to always write out CDATA closing delimiter here, 1294 * but now we delay, so that we can merge CDATA sections on output. 1295 * need to write closing delimiter later 1296 */ 1297 if (writeCDataBrackets) 1298 { 1299 /* if the CDATA section ends with ] don't leave it open 1300 * as there is a chance that an adjacent CDATA sections 1301 * starts with ]>. 1302 * We don't want to merge ]] with > , or ] with ]> 1303 */ 1304 if (ch[start + length - 1] == ']') 1305 closeCDATA(); 1306 } 1307 1308 // time to fire off CDATA event 1309 if (m_tracer != null) 1310 super.fireCDATAEvent(ch, old_start, length); 1311 } 1312 catch (IOException ioe) 1313 { 1314 throw new org.xml.sax.SAXException( 1315 Utils.messages.createMessage( 1316 MsgKey.ER_OIERROR, 1317 null), 1318 ioe); 1319 //"IO error", ioe); 1320 } 1321 } 1322 1323 /** 1324 * Tell if the character escaping should be disabled for the current state. 1325 * 1326 * @return true if the character escaping should be disabled. 1327 */ 1328 private boolean isEscapingDisabled() 1329 { 1330 return m_disableOutputEscapingStates.peekOrFalse(); 1331 } 1332 1333 /** 1334 * If available, when the disable-output-escaping attribute is used, 1335 * output raw text without escaping. 1336 * 1337 * @param ch The characters from the XML document. 1338 * @param start The start position in the array. 1339 * @param length The number of characters to read from the array. 1340 * 1341 * @throws org.xml.sax.SAXException 1342 */ 1343 protected void charactersRaw(char ch[], int start, int length) 1344 throws org.xml.sax.SAXException 1345 { 1346 1347 if (m_inEntityRef) 1348 return; 1349 try 1350 { 1351 if (m_elemContext.m_startTagOpen) 1352 { 1353 closeStartTag(); 1354 m_elemContext.m_startTagOpen = false; 1355 } 1356 1357 m_ispreserve = true; 1358 1359 m_writer.write(ch, start, length); 1360 } 1361 catch (IOException e) 1362 { 1363 throw new SAXException(e); 1364 } 1365 1366 } 1367 1368 /** 1369 * Receive notification of character data. 1370 * 1371 * <p>The Parser will call this method to report each chunk of 1372 * character data. SAX parsers may return all contiguous character 1373 * data in a single chunk, or they may split it into several 1374 * chunks; however, all of the characters in any single event 1375 * must come from the same external entity, so that the Locator 1376 * provides useful information.</p> 1377 * 1378 * <p>The application must not attempt to read from the array 1379 * outside of the specified range.</p> 1380 * 1381 * <p>Note that some parsers will report whitespace using the 1382 * ignorableWhitespace() method rather than this one (validating 1383 * parsers must do so).</p> 1384 * 1385 * @param chars The characters from the XML document. 1386 * @param start The start position in the array. 1387 * @param length The number of characters to read from the array. 1388 * @throws org.xml.sax.SAXException Any SAX exception, possibly 1389 * wrapping another exception. 1390 * @see #ignorableWhitespace 1391 * @see org.xml.sax.Locator 1392 * 1393 * @throws org.xml.sax.SAXException 1394 */ 1395 public void characters(final char chars[], final int start, final int length) 1396 throws org.xml.sax.SAXException 1397 { 1398 // It does not make sense to continue with rest of the method if the number of 1399 // characters to read from array is 0. 1400 // Section 7.6.1 of XSLT 1.0 (http://www.w3.org/TR/xslt#value-of) suggest no text node 1401 // is created if string is empty. 1402 if (length == 0 || (m_inEntityRef && !m_expandDTDEntities)) 1403 return; 1404 1405 m_docIsEmpty = false; 1406 1407 if (m_elemContext.m_startTagOpen) 1408 { 1409 closeStartTag(); 1410 m_elemContext.m_startTagOpen = false; 1411 } 1412 else if (m_needToCallStartDocument) 1413 { 1414 startDocumentInternal(); 1415 } 1416 1417 if (m_cdataStartCalled || m_elemContext.m_isCdataSection) 1418 { 1419 /* either due to startCDATA() being called or due to 1420 * cdata-section-elements atribute, we need this as cdata 1421 */ 1422 cdata(chars, start, length); 1423 1424 return; 1425 } 1426 1427 if (m_cdataTagOpen) 1428 closeCDATA(); 1429 1430 if (m_disableOutputEscapingStates.peekOrFalse() || (!m_escaping)) 1431 { 1432 charactersRaw(chars, start, length); 1433 1434 // time to fire off characters generation event 1435 if (m_tracer != null) 1436 super.fireCharEvent(chars, start, length); 1437 1438 return; 1439 } 1440 1441 if (m_elemContext.m_startTagOpen) 1442 { 1443 closeStartTag(); 1444 m_elemContext.m_startTagOpen = false; 1445 } 1446 1447 1448 try 1449 { 1450 int i; 1451 int startClean; 1452 1453 // skip any leading whitspace 1454 // don't go off the end and use a hand inlined version 1455 // of isWhitespace(ch) 1456 final int end = start + length; 1457 int lastDirtyCharProcessed = start - 1; // last non-clean character that was processed 1458 // that was processed 1459 final Writer writer = m_writer; 1460 boolean isAllWhitespace = true; 1461 1462 // process any leading whitspace 1463 i = start; 1464 while (i < end && isAllWhitespace) { 1465 char ch1 = chars[i]; 1466 1467 if (m_charInfo.shouldMapTextChar(ch1)) { 1468 // The character is supposed to be replaced by a String 1469 // so write out the clean whitespace characters accumulated 1470 // so far 1471 // then the String. 1472 writeOutCleanChars(chars, i, lastDirtyCharProcessed); 1473 String outputStringForChar = m_charInfo 1474 .getOutputStringForChar(ch1); 1475 writer.write(outputStringForChar); 1476 // We can't say that everything we are writing out is 1477 // all whitespace, we just wrote out a String. 1478 isAllWhitespace = false; 1479 lastDirtyCharProcessed = i; // mark the last non-clean 1480 // character processed 1481 i++; 1482 } else { 1483 // The character is clean, but is it a whitespace ? 1484 switch (ch1) { 1485 // TODO: Any other whitespace to consider? 1486 case CharInfo.S_SPACE: 1487 // Just accumulate the clean whitespace 1488 i++; 1489 break; 1490 case CharInfo.S_LINEFEED: 1491 lastDirtyCharProcessed = processLineFeed(chars, i, 1492 lastDirtyCharProcessed, writer); 1493 i++; 1494 break; 1495 case CharInfo.S_CARRIAGERETURN: 1496 writeOutCleanChars(chars, i, lastDirtyCharProcessed); 1497 writer.write(" "); 1498 lastDirtyCharProcessed = i; 1499 i++; 1500 break; 1501 case CharInfo.S_HORIZONAL_TAB: 1502 // Just accumulate the clean whitespace 1503 i++; 1504 break; 1505 default: 1506 // The character was clean, but not a whitespace 1507 // so break the loop to continue with this character 1508 // (we don't increment index i !!) 1509 isAllWhitespace = false; 1510 break; 1511 } 1512 } 1513 } 1514 1515 /* If there is some non-whitespace, mark that we may need 1516 * to preserve this. This is only important if we have indentation on. 1517 */ 1518 if (i < end || !isAllWhitespace) 1519 m_ispreserve = true; 1520 1521 1522 for (; i < end; i++) 1523 { 1524 char ch = chars[i]; 1525 1526 if (m_charInfo.shouldMapTextChar(ch)) { 1527 // The character is supposed to be replaced by a String 1528 // e.g. '&' --> "&" 1529 // e.g. '<' --> "<" 1530 writeOutCleanChars(chars, i, lastDirtyCharProcessed); 1531 String outputStringForChar = m_charInfo.getOutputStringForChar(ch); 1532 writer.write(outputStringForChar); 1533 lastDirtyCharProcessed = i; 1534 } 1535 else { 1536 if (ch <= 0x1F) { 1537 // Range 0x00 through 0x1F inclusive 1538 // 1539 // This covers the non-whitespace control characters 1540 // in the range 0x1 to 0x1F inclusive. 1541 // It also covers the whitespace control characters in the same way: 1542 // 0x9 TAB 1543 // 0xA NEW LINE 1544 // 0xD CARRIAGE RETURN 1545 // 1546 // We also cover 0x0 ... It isn't valid 1547 // but we will output "�" 1548 1549 // The default will handle this just fine, but this 1550 // is a little performance boost to handle the more 1551 // common TAB, NEW-LINE, CARRIAGE-RETURN 1552 switch (ch) { 1553 1554 case CharInfo.S_HORIZONAL_TAB: 1555 // Leave whitespace TAB as a real character 1556 break; 1557 case CharInfo.S_LINEFEED: 1558 lastDirtyCharProcessed = processLineFeed(chars, i, lastDirtyCharProcessed, writer); 1559 break; 1560 case CharInfo.S_CARRIAGERETURN: 1561 writeOutCleanChars(chars, i, lastDirtyCharProcessed); 1562 writer.write(" "); 1563 lastDirtyCharProcessed = i; 1564 // Leave whitespace carriage return as a real character 1565 break; 1566 default: 1567 writeOutCleanChars(chars, i, lastDirtyCharProcessed); 1568 writer.write("&#"); 1569 writer.write(Integer.toString(ch)); 1570 writer.write(';'); 1571 lastDirtyCharProcessed = i; 1572 break; 1573 1574 } 1575 } 1576 else if (ch < 0x7F) { 1577 // Range 0x20 through 0x7E inclusive 1578 // Normal ASCII chars, do nothing, just add it to 1579 // the clean characters 1580 1581 } 1582 else if (ch <= 0x9F){ 1583 // Range 0x7F through 0x9F inclusive 1584 // More control characters, including NEL (0x85) 1585 writeOutCleanChars(chars, i, lastDirtyCharProcessed); 1586 writer.write("&#"); 1587 writer.write(Integer.toString(ch)); 1588 writer.write(';'); 1589 lastDirtyCharProcessed = i; 1590 } 1591 else if (ch == CharInfo.S_LINE_SEPARATOR) { 1592 // LINE SEPARATOR 1593 writeOutCleanChars(chars, i, lastDirtyCharProcessed); 1594 writer.write("
"); 1595 lastDirtyCharProcessed = i; 1596 } 1597 else if (m_encodingInfo.isInEncoding(ch)) { 1598 // If the character is in the encoding, and 1599 // not in the normal ASCII range, we also 1600 // just leave it get added on to the clean characters 1601 1602 } 1603 else { 1604 // This is a fallback plan, we should never get here 1605 // but if the character wasn't previously handled 1606 // (i.e. isn't in the encoding, etc.) then what 1607 // should we do? We choose to write out an entity 1608 writeOutCleanChars(chars, i, lastDirtyCharProcessed); 1609 writer.write("&#"); 1610 writer.write(Integer.toString(ch)); 1611 writer.write(';'); 1612 lastDirtyCharProcessed = i; 1613 } 1614 } 1615 } 1616 1617 // we've reached the end. Any clean characters at the 1618 // end of the array than need to be written out? 1619 startClean = lastDirtyCharProcessed + 1; 1620 if (i > startClean) 1621 { 1622 int lengthClean = i - startClean; 1623 m_writer.write(chars, startClean, lengthClean); 1624 } 1625 1626 // For indentation purposes, mark that we've just writen text out 1627 m_isprevtext = true; 1628 } 1629 catch (IOException e) 1630 { 1631 throw new SAXException(e); 1632 } 1633 1634 // time to fire off characters generation event 1635 if (m_tracer != null) 1636 super.fireCharEvent(chars, start, length); 1637 } 1638 1639 private int processLineFeed(final char[] chars, int i, int lastProcessed, final Writer writer) throws IOException { 1640 if (!m_lineSepUse 1641 || (m_lineSepLen ==1 && m_lineSep[0] == CharInfo.S_LINEFEED)){ 1642 // We are leaving the new-line alone, and it is just 1643 // being added to the 'clean' characters, 1644 // so the last dirty character processed remains unchanged 1645 } 1646 else { 1647 writeOutCleanChars(chars, i, lastProcessed); 1648 writer.write(m_lineSep, 0, m_lineSepLen); 1649 lastProcessed = i; 1650 } 1651 return lastProcessed; 1652 } 1653 1654 private void writeOutCleanChars(final char[] chars, int i, int lastProcessed) throws IOException { 1655 int startClean; 1656 startClean = lastProcessed + 1; 1657 if (startClean < i) 1658 { 1659 int lengthClean = i - startClean; 1660 m_writer.write(chars, startClean, lengthClean); 1661 } 1662 } 1663 /** 1664 * This method checks if a given character is between C0 or C1 range 1665 * of Control characters. 1666 * This method is added to support Control Characters for XML 1.1 1667 * If a given character is TAB (0x09), LF (0x0A) or CR (0x0D), this method 1668 * return false. Since they are whitespace characters, no special processing is needed. 1669 * 1670 * @param ch 1671 * @return boolean 1672 */ 1673 private static boolean isCharacterInC0orC1Range(char ch) 1674 { 1675 if(ch == 0x09 || ch == 0x0A || ch == 0x0D) 1676 return false; 1677 else 1678 return (ch >= 0x7F && ch <= 0x9F)|| (ch >= 0x01 && ch <= 0x1F); 1679 } 1680 /** 1681 * This method checks if a given character either NEL (0x85) or LSEP (0x2028) 1682 * These are new end of line charcters added in XML 1.1. These characters must be 1683 * written as Numeric Character References (NCR) in XML 1.1 output document. 1684 * 1685 * @param ch 1686 * @return boolean 1687 */ 1688 private static boolean isNELorLSEPCharacter(char ch) 1689 { 1690 return (ch == 0x85 || ch == 0x2028); 1691 } 1692 /** 1693 * Process a dirty character and any preeceding clean characters 1694 * that were not yet processed. 1695 * @param chars array of characters being processed 1696 * @param end one (1) beyond the last character 1697 * in chars to be processed 1698 * @param i the index of the dirty character 1699 * @param ch the character in chars[i] 1700 * @param lastDirty the last dirty character previous to i 1701 * @param fromTextNode true if the characters being processed are 1702 * from a text node, false if they are from an attribute value. 1703 * @return the index of the last character processed 1704 */ 1705 private int processDirty( 1706 char[] chars, 1707 int end, 1708 int i, 1709 char ch, 1710 int lastDirty, 1711 boolean fromTextNode) throws IOException 1712 { 1713 int startClean = lastDirty + 1; 1714 // if we have some clean characters accumulated 1715 // process them before the dirty one. 1716 if (i > startClean) 1717 { 1718 int lengthClean = i - startClean; 1719 m_writer.write(chars, startClean, lengthClean); 1720 } 1721 1722 // process the "dirty" character 1723 if (CharInfo.S_LINEFEED == ch && fromTextNode) 1724 { 1725 m_writer.write(m_lineSep, 0, m_lineSepLen); 1726 } 1727 else 1728 { 1729 startClean = 1730 accumDefaultEscape( 1731 m_writer, 1732 (char)ch, 1733 i, 1734 chars, 1735 end, 1736 fromTextNode, 1737 false); 1738 i = startClean - 1; 1739 } 1740 // Return the index of the last character that we just processed 1741 // which is a dirty character. 1742 return i; 1743 } 1744 1745 /** 1746 * Receive notification of character data. 1747 * 1748 * @param s The string of characters to process. 1749 * 1750 * @throws org.xml.sax.SAXException 1751 */ 1752 public void characters(String s) throws org.xml.sax.SAXException 1753 { 1754 if (m_inEntityRef && !m_expandDTDEntities) 1755 return; 1756 final int length = s.length(); 1757 if (length > m_charsBuff.length) 1758 { 1759 m_charsBuff = new char[length * 2 + 1]; 1760 } 1761 s.getChars(0, length, m_charsBuff, 0); 1762 characters(m_charsBuff, 0, length); 1763 } 1764 1765 /** 1766 * Escape and writer.write a character. 1767 * 1768 * @param ch character to be escaped. 1769 * @param i index into character array. 1770 * @param chars non-null reference to character array. 1771 * @param len length of chars. 1772 * @param fromTextNode true if the characters being processed are 1773 * from a text node, false if the characters being processed are from 1774 * an attribute value. 1775 * @param escLF true if the linefeed should be escaped. 1776 * 1777 * @return i+1 if a character was written, i+2 if two characters 1778 * were written out, else return i. 1779 * 1780 * @throws org.xml.sax.SAXException 1781 */ 1782 private int accumDefaultEscape( 1783 Writer writer, 1784 char ch, 1785 int i, 1786 char[] chars, 1787 int len, 1788 boolean fromTextNode, 1789 boolean escLF) 1790 throws IOException 1791 { 1792 1793 int pos = accumDefaultEntity(writer, ch, i, chars, len, fromTextNode, escLF); 1794 1795 if (i == pos) 1796 { 1797 if (Encodings.isHighUTF16Surrogate(ch)) 1798 { 1799 1800 // Should be the UTF-16 low surrogate of the hig/low pair. 1801 char next; 1802 // Unicode code point formed from the high/low pair. 1803 int codePoint = 0; 1804 1805 if (i + 1 >= len) 1806 { 1807 throw new IOException( 1808 Utils.messages.createMessage( 1809 MsgKey.ER_INVALID_UTF16_SURROGATE, 1810 new Object[] { Integer.toHexString(ch)})); 1811 //"Invalid UTF-16 surrogate detected: " 1812 1813 //+Integer.toHexString(ch)+ " ?"); 1814 } 1815 else 1816 { 1817 next = chars[++i]; 1818 1819 if (!(Encodings.isLowUTF16Surrogate(next))) 1820 throw new IOException( 1821 Utils.messages.createMessage( 1822 MsgKey 1823 .ER_INVALID_UTF16_SURROGATE, 1824 new Object[] { 1825 Integer.toHexString(ch) 1826 + " " 1827 + Integer.toHexString(next)})); 1828 //"Invalid UTF-16 surrogate detected: " 1829 1830 //+Integer.toHexString(ch)+" "+Integer.toHexString(next)); 1831 codePoint = Encodings.toCodePoint(ch,next); 1832 } 1833 1834 writer.write("&#"); 1835 writer.write(Integer.toString(codePoint)); 1836 writer.write(';'); 1837 pos += 2; // count the two characters that went into writing out this entity 1838 } 1839 else 1840 { 1841 /* This if check is added to support control characters in XML 1.1. 1842 * If a character is a Control Character within C0 and C1 range, it is desirable 1843 * to write it out as Numeric Character Reference(NCR) regardless of XML Version 1844 * being used for output document. 1845 */ 1846 if (isCharacterInC0orC1Range(ch) || isNELorLSEPCharacter(ch)) 1847 { 1848 writer.write("&#"); 1849 writer.write(Integer.toString(ch)); 1850 writer.write(';'); 1851 } 1852 else if ((!escapingNotNeeded(ch) || 1853 ( (fromTextNode && m_charInfo.shouldMapTextChar(ch)) 1854 || (!fromTextNode && m_charInfo.shouldMapAttrChar(ch)))) 1855 && m_elemContext.m_currentElemDepth > 0) 1856 { 1857 writer.write("&#"); 1858 writer.write(Integer.toString(ch)); 1859 writer.write(';'); 1860 } 1861 else 1862 { 1863 writer.write(ch); 1864 } 1865 pos++; // count the single character that was processed 1866 } 1867 1868 } 1869 return pos; 1870 } 1871 1872 /** 1873 * Receive notification of the beginning of an element, although this is a 1874 * SAX method additional namespace or attribute information can occur before 1875 * or after this call, that is associated with this element. 1876 * 1877 * 1878 * @param namespaceURI The Namespace URI, or the empty string if the 1879 * element has no Namespace URI or if Namespace 1880 * processing is not being performed. 1881 * @param localName The local name (without prefix), or the 1882 * empty string if Namespace processing is not being 1883 * performed. 1884 * @param name The element type name. 1885 * @param atts The attributes attached to the element, if any. 1886 * @throws org.xml.sax.SAXException Any SAX exception, possibly 1887 * wrapping another exception. 1888 * @see org.xml.sax.ContentHandler#startElement 1889 * @see org.xml.sax.ContentHandler#endElement 1890 * @see org.xml.sax.AttributeList 1891 * 1892 * @throws org.xml.sax.SAXException 1893 */ 1894 public void startElement( 1895 String namespaceURI, 1896 String localName, 1897 String name, 1898 Attributes atts) 1899 throws org.xml.sax.SAXException 1900 { 1901 if (m_inEntityRef) 1902 return; 1903 1904 if (m_needToCallStartDocument) 1905 { 1906 startDocumentInternal(); 1907 m_needToCallStartDocument = false; 1908 m_docIsEmpty = false; 1909 } 1910 else if (m_cdataTagOpen) 1911 closeCDATA(); 1912 try 1913 { 1914 if (m_needToOutputDocTypeDecl) { 1915 if(null != getDoctypeSystem()) { 1916 outputDocTypeDecl(name, true); 1917 } 1918 m_needToOutputDocTypeDecl = false; 1919 } 1920 1921 /* before we over-write the current elementLocalName etc. 1922 * lets close out the old one (if we still need to) 1923 */ 1924 if (m_elemContext.m_startTagOpen) 1925 { 1926 closeStartTag(); 1927 m_elemContext.m_startTagOpen = false; 1928 } 1929 1930 if (namespaceURI != null) 1931 ensurePrefixIsDeclared(namespaceURI, name); 1932 1933 m_ispreserve = false; 1934 1935 if (shouldIndent() && m_startNewLine) 1936 { 1937 indent(); 1938 } 1939 1940 m_startNewLine = true; 1941 1942 final java.io.Writer writer = m_writer; 1943 writer.write('<'); 1944 writer.write(name); 1945 } 1946 catch (IOException e) 1947 { 1948 throw new SAXException(e); 1949 } 1950 1951 // process the attributes now, because after this SAX call they might be gone 1952 if (atts != null) 1953 addAttributes(atts); 1954 1955 m_elemContext = m_elemContext.push(namespaceURI,localName,name); 1956 m_isprevtext = false; 1957 1958 if (m_tracer != null) 1959 firePseudoAttributes(); 1960 } 1961 1962 /** 1963 * Receive notification of the beginning of an element, additional 1964 * namespace or attribute information can occur before or after this call, 1965 * that is associated with this element. 1966 * 1967 * 1968 * @param elementNamespaceURI The Namespace URI, or the empty string if the 1969 * element has no Namespace URI or if Namespace 1970 * processing is not being performed. 1971 * @param elementLocalName The local name (without prefix), or the 1972 * empty string if Namespace processing is not being 1973 * performed. 1974 * @param elementName The element type name. 1975 * @throws org.xml.sax.SAXException Any SAX exception, possibly 1976 * wrapping another exception. 1977 * @see org.xml.sax.ContentHandler#startElement 1978 * @see org.xml.sax.ContentHandler#endElement 1979 * @see org.xml.sax.AttributeList 1980 * 1981 * @throws org.xml.sax.SAXException 1982 */ 1983 public void startElement( 1984 String elementNamespaceURI, 1985 String elementLocalName, 1986 String elementName) 1987 throws SAXException 1988 { 1989 startElement(elementNamespaceURI, elementLocalName, elementName, null); 1990 } 1991 1992 public void startElement(String elementName) throws SAXException 1993 { 1994 startElement(null, null, elementName, null); 1995 } 1996 1997 /** 1998 * Output the doc type declaration. 1999 * 2000 * @param name non-null reference to document type name. 2001 * NEEDSDOC @param closeDecl 2002 * 2003 * @throws java.io.IOException 2004 */ 2005 void outputDocTypeDecl(String name, boolean closeDecl) throws SAXException 2006 { 2007 if (m_cdataTagOpen) 2008 closeCDATA(); 2009 try 2010 { 2011 final java.io.Writer writer = m_writer; 2012 writer.write("<!DOCTYPE "); 2013 writer.write(name); 2014 2015 String doctypePublic = getDoctypePublic(); 2016 if (null != doctypePublic) 2017 { 2018 writer.write(" PUBLIC \""); 2019 writer.write(doctypePublic); 2020 writer.write('\"'); 2021 } 2022 2023 String doctypeSystem = getDoctypeSystem(); 2024 if (null != doctypeSystem) 2025 { 2026 if (null == doctypePublic) 2027 writer.write(" SYSTEM \""); 2028 else 2029 writer.write(" \""); 2030 2031 writer.write(doctypeSystem); 2032 2033 if (closeDecl) 2034 { 2035 writer.write("\">"); 2036 writer.write(m_lineSep, 0, m_lineSepLen); 2037 closeDecl = false; // done closing 2038 } 2039 else 2040 writer.write('\"'); 2041 } 2042 } 2043 catch (IOException e) 2044 { 2045 throw new SAXException(e); 2046 } 2047 } 2048 2049 /** 2050 * Process the attributes, which means to write out the currently 2051 * collected attributes to the writer. The attributes are not 2052 * cleared by this method 2053 * 2054 * @param writer the writer to write processed attributes to. 2055 * @param nAttrs the number of attributes in m_attributes 2056 * to be processed 2057 * 2058 * @throws java.io.IOException 2059 * @throws org.xml.sax.SAXException 2060 */ 2061 public void processAttributes(java.io.Writer writer, int nAttrs) throws IOException, SAXException 2062 { 2063 /* real SAX attributes are not passed in, so process the 2064 * attributes that were collected after the startElement call. 2065 * _attribVector is a "cheap" list for Stream serializer output 2066 * accumulated over a series of calls to attribute(name,value) 2067 */ 2068 2069 String encoding = getEncoding(); 2070 for (int i = 0; i < nAttrs; i++) 2071 { 2072 // elementAt is JDK 1.1.8 2073 final String name = m_attributes.getQName(i); 2074 final String value = m_attributes.getValue(i); 2075 writer.write(' '); 2076 writer.write(name); 2077 writer.write("=\""); 2078 writeAttrString(writer, value, encoding); 2079 writer.write('\"'); 2080 } 2081 } 2082 2083 /** 2084 * Returns the specified <var>string</var> after substituting <VAR>specials</VAR>, 2085 * and UTF-16 surrogates for chracter references <CODE>&#xnn</CODE>. 2086 * 2087 * @param string String to convert to XML format. 2088 * @param encoding CURRENTLY NOT IMPLEMENTED. 2089 * 2090 * @throws java.io.IOException 2091 */ 2092 public void writeAttrString( 2093 Writer writer, 2094 String string, 2095 String encoding) 2096 throws IOException 2097 { 2098 final int len = string.length(); 2099 if (len > m_attrBuff.length) 2100 { 2101 m_attrBuff = new char[len*2 + 1]; 2102 } 2103 string.getChars(0,len, m_attrBuff, 0); 2104 final char[] stringChars = m_attrBuff; 2105 2106 for (int i = 0; i < len; i++) 2107 { 2108 char ch = stringChars[i]; 2109 2110 if (m_charInfo.shouldMapAttrChar(ch)) { 2111 // The character is supposed to be replaced by a String 2112 // e.g. '&' --> "&" 2113 // e.g. '<' --> "<" 2114 accumDefaultEscape(writer, ch, i, stringChars, len, false, true); 2115 } 2116 else { 2117 if (0x0 <= ch && ch <= 0x1F) { 2118 // Range 0x00 through 0x1F inclusive 2119 // This covers the non-whitespace control characters 2120 // in the range 0x1 to 0x1F inclusive. 2121 // It also covers the whitespace control characters in the same way: 2122 // 0x9 TAB 2123 // 0xA NEW LINE 2124 // 0xD CARRIAGE RETURN 2125 // 2126 // We also cover 0x0 ... It isn't valid 2127 // but we will output "�" 2128 2129 // The default will handle this just fine, but this 2130 // is a little performance boost to handle the more 2131 // common TAB, NEW-LINE, CARRIAGE-RETURN 2132 switch (ch) { 2133 2134 case CharInfo.S_HORIZONAL_TAB: 2135 writer.write("	"); 2136 break; 2137 case CharInfo.S_LINEFEED: 2138 writer.write(" "); 2139 break; 2140 case CharInfo.S_CARRIAGERETURN: 2141 writer.write(" "); 2142 break; 2143 default: 2144 writer.write("&#"); 2145 writer.write(Integer.toString(ch)); 2146 writer.write(';'); 2147 break; 2148 2149 } 2150 } 2151 else if (ch < 0x7F) { 2152 // Range 0x20 through 0x7E inclusive 2153 // Normal ASCII chars 2154 writer.write(ch); 2155 } 2156 else if (ch <= 0x9F){ 2157 // Range 0x7F through 0x9F inclusive 2158 // More control characters 2159 writer.write("&#"); 2160 writer.write(Integer.toString(ch)); 2161 writer.write(';'); 2162 } 2163 else if (ch == CharInfo.S_LINE_SEPARATOR) { 2164 // LINE SEPARATOR 2165 writer.write("
"); 2166 } 2167 else if (m_encodingInfo.isInEncoding(ch)) { 2168 // If the character is in the encoding, and 2169 // not in the normal ASCII range, we also 2170 // just write it out 2171 writer.write(ch); 2172 } 2173 else { 2174 // This is a fallback plan, we should never get here 2175 // but if the character wasn't previously handled 2176 // (i.e. isn't in the encoding, etc.) then what 2177 // should we do? We choose to write out a character ref 2178 writer.write("&#"); 2179 writer.write(Integer.toString(ch)); 2180 writer.write(';'); 2181 } 2182 2183 } 2184 } 2185 } 2186 2187 /** 2188 * Receive notification of the end of an element. 2189 * 2190 * 2191 * @param namespaceURI The Namespace URI, or the empty string if the 2192 * element has no Namespace URI or if Namespace 2193 * processing is not being performed. 2194 * @param localName The local name (without prefix), or the 2195 * empty string if Namespace processing is not being 2196 * performed. 2197 * @param name The element type name 2198 * @throws org.xml.sax.SAXException Any SAX exception, possibly 2199 * wrapping another exception. 2200 * 2201 * @throws org.xml.sax.SAXException 2202 */ 2203 public void endElement(String namespaceURI, String localName, String name) 2204 throws org.xml.sax.SAXException 2205 { 2206 if (m_inEntityRef) 2207 return; 2208 2209 // namespaces declared at the current depth are no longer valid 2210 // so get rid of them 2211 m_prefixMap.popNamespaces(m_elemContext.m_currentElemDepth, null); 2212 2213 try 2214 { 2215 final java.io.Writer writer = m_writer; 2216 if (m_elemContext.m_startTagOpen) 2217 { 2218 if (m_tracer != null) 2219 super.fireStartElem(m_elemContext.m_elementName); 2220 int nAttrs = m_attributes.getLength(); 2221 if (nAttrs > 0) 2222 { 2223 processAttributes(m_writer, nAttrs); 2224 // clear attributes object for re-use with next element 2225 m_attributes.clear(); 2226 } 2227 if (m_spaceBeforeClose) 2228 writer.write(" />"); 2229 else 2230 writer.write("/>"); 2231 /* don't need to pop cdataSectionState because 2232 * this element ended so quickly that we didn't get 2233 * to push the state. 2234 */ 2235 2236 } 2237 else 2238 { 2239 if (m_cdataTagOpen) 2240 closeCDATA(); 2241 2242 if (shouldIndent()) 2243 indent(m_elemContext.m_currentElemDepth - 1); 2244 writer.write('<'); 2245 writer.write('/'); 2246 writer.write(name); 2247 writer.write('>'); 2248 } 2249 } 2250 catch (IOException e) 2251 { 2252 throw new SAXException(e); 2253 } 2254 2255 if (!m_elemContext.m_startTagOpen && m_doIndent) 2256 { 2257 m_ispreserve = m_preserves.isEmpty() ? false : m_preserves.pop(); 2258 } 2259 2260 m_isprevtext = false; 2261 2262 // fire off the end element event 2263 if (m_tracer != null) 2264 super.fireEndElem(name); 2265 m_elemContext = m_elemContext.m_prev; 2266 } 2267 2268 /** 2269 * Receive notification of the end of an element. 2270 * @param name The element type name 2271 * @throws org.xml.sax.SAXException Any SAX exception, possibly 2272 * wrapping another exception. 2273 */ 2274 public void endElement(String name) throws org.xml.sax.SAXException 2275 { 2276 endElement(null, null, name); 2277 } 2278 2279 /** 2280 * Begin the scope of a prefix-URI Namespace mapping 2281 * just before another element is about to start. 2282 * This call will close any open tags so that the prefix mapping 2283 * will not apply to the current element, but the up comming child. 2284 * 2285 * @see org.xml.sax.ContentHandler#startPrefixMapping 2286 * 2287 * @param prefix The Namespace prefix being declared. 2288 * @param uri The Namespace URI the prefix is mapped to. 2289 * 2290 * @throws org.xml.sax.SAXException The client may throw 2291 * an exception during processing. 2292 * 2293 */ 2294 public void startPrefixMapping(String prefix, String uri) 2295 throws org.xml.sax.SAXException 2296 { 2297 // the "true" causes the flush of any open tags 2298 startPrefixMapping(prefix, uri, true); 2299 } 2300 2301 /** 2302 * Handle a prefix/uri mapping, which is associated with a startElement() 2303 * that is soon to follow. Need to close any open start tag to make 2304 * sure than any name space attributes due to this event are associated wih 2305 * the up comming element, not the current one. 2306 * @see ExtendedContentHandler#startPrefixMapping 2307 * 2308 * @param prefix The Namespace prefix being declared. 2309 * @param uri The Namespace URI the prefix is mapped to. 2310 * @param shouldFlush true if any open tags need to be closed first, this 2311 * will impact which element the mapping applies to (open parent, or its up 2312 * comming child) 2313 * @return returns true if the call made a change to the current 2314 * namespace information, false if it did not change anything, e.g. if the 2315 * prefix/namespace mapping was already in scope from before. 2316 * 2317 * @throws org.xml.sax.SAXException The client may throw 2318 * an exception during processing. 2319 * 2320 * 2321 */ 2322 public boolean startPrefixMapping( 2323 String prefix, 2324 String uri, 2325 boolean shouldFlush) 2326 throws org.xml.sax.SAXException 2327 { 2328 2329 /* Remember the mapping, and at what depth it was declared 2330 * This is one greater than the current depth because these 2331 * mappings will apply to the next depth. This is in 2332 * consideration that startElement() will soon be called 2333 */ 2334 2335 boolean pushed; 2336 int pushDepth; 2337 if (shouldFlush) 2338 { 2339 flushPending(); 2340 // the prefix mapping applies to the child element (one deeper) 2341 pushDepth = m_elemContext.m_currentElemDepth + 1; 2342 } 2343 else 2344 { 2345 // the prefix mapping applies to the current element 2346 pushDepth = m_elemContext.m_currentElemDepth; 2347 } 2348 pushed = m_prefixMap.pushNamespace(prefix, uri, pushDepth); 2349 2350 if (pushed) 2351 { 2352 /* Brian M.: don't know if we really needto do this. The 2353 * callers of this object should have injected both 2354 * startPrefixMapping and the attributes. We are 2355 * just covering our butt here. 2356 */ 2357 String name; 2358 if (EMPTYSTRING.equals(prefix)) 2359 { 2360 name = "xmlns"; 2361 addAttributeAlways(XMLNS_URI, name, name, "CDATA", uri, false); 2362 } 2363 else 2364 { 2365 if (!EMPTYSTRING.equals(uri)) 2366 // hack for XSLTC attribset16 test 2367 { // that maps ns1 prefix to "" URI 2368 name = "xmlns:" + prefix; 2369 2370 /* for something like xmlns:abc="w3.pretend.org" 2371 * the uri is the value, that is why we pass it in the 2372 * value, or 5th slot of addAttributeAlways() 2373 */ 2374 addAttributeAlways(XMLNS_URI, prefix, name, "CDATA", uri, false); 2375 } 2376 } 2377 } 2378 return pushed; 2379 } 2380 2381 /** 2382 * Receive notification of an XML comment anywhere in the document. This 2383 * callback will be used for comments inside or outside the document 2384 * element, including comments in the external DTD subset (if read). 2385 * @param ch An array holding the characters in the comment. 2386 * @param start The starting position in the array. 2387 * @param length The number of characters to use from the array. 2388 * @throws org.xml.sax.SAXException The application may raise an exception. 2389 */ 2390 public void comment(char ch[], int start, int length) 2391 throws org.xml.sax.SAXException 2392 { 2393 2394 int start_old = start; 2395 if (m_inEntityRef) 2396 return; 2397 if (m_elemContext.m_startTagOpen) 2398 { 2399 closeStartTag(); 2400 m_elemContext.m_startTagOpen = false; 2401 } 2402 else if (m_needToCallStartDocument) 2403 { 2404 startDocumentInternal(); 2405 m_needToCallStartDocument = false; 2406 } 2407 2408 try 2409 { 2410 final int limit = start + length; 2411 boolean wasDash = false; 2412 if (m_cdataTagOpen) 2413 closeCDATA(); 2414 2415 if (shouldIndent()) 2416 indent(); 2417 2418 final java.io.Writer writer = m_writer; 2419 writer.write(COMMENT_BEGIN); 2420 // Detect occurrences of two consecutive dashes, handle as necessary. 2421 for (int i = start; i < limit; i++) 2422 { 2423 if (wasDash && ch[i] == '-') 2424 { 2425 writer.write(ch, start, i - start); 2426 writer.write(" -"); 2427 start = i + 1; 2428 } 2429 wasDash = (ch[i] == '-'); 2430 } 2431 2432 // if we have some chars in the comment 2433 if (length > 0) 2434 { 2435 // Output the remaining characters (if any) 2436 final int remainingChars = (limit - start); 2437 if (remainingChars > 0) 2438 writer.write(ch, start, remainingChars); 2439 // Protect comment end from a single trailing dash 2440 if (ch[limit - 1] == '-') 2441 writer.write(' '); 2442 } 2443 writer.write(COMMENT_END); 2444 } 2445 catch (IOException e) 2446 { 2447 throw new SAXException(e); 2448 } 2449 2450 /* 2451 * Don't write out any indentation whitespace now, 2452 * because there may be non-whitespace text after this. 2453 * 2454 * Simply mark that at this point if we do decide 2455 * to indent that we should 2456 * add a newline on the end of the current line before 2457 * the indentation at the start of the next line. 2458 */ 2459 m_startNewLine = true; 2460 // time to generate comment event 2461 if (m_tracer != null) 2462 super.fireCommentEvent(ch, start_old,length); 2463 } 2464 2465 /** 2466 * Report the end of a CDATA section. 2467 * @throws org.xml.sax.SAXException The application may raise an exception. 2468 * 2469 * @see #startCDATA 2470 */ 2471 public void endCDATA() throws org.xml.sax.SAXException 2472 { 2473 if (m_cdataTagOpen) 2474 closeCDATA(); 2475 m_cdataStartCalled = false; 2476 } 2477 2478 /** 2479 * Report the end of DTD declarations. 2480 * @throws org.xml.sax.SAXException The application may raise an exception. 2481 * @see #startDTD 2482 */ 2483 public void endDTD() throws org.xml.sax.SAXException 2484 { 2485 try 2486 { 2487 if (m_needToOutputDocTypeDecl) 2488 { 2489 outputDocTypeDecl(m_elemContext.m_elementName, false); 2490 m_needToOutputDocTypeDecl = false; 2491 } 2492 final java.io.Writer writer = m_writer; 2493 if (!m_inDoctype) 2494 writer.write("]>"); 2495 else 2496 { 2497 writer.write('>'); 2498 } 2499 2500 writer.write(m_lineSep, 0, m_lineSepLen); 2501 } 2502 catch (IOException e) 2503 { 2504 throw new SAXException(e); 2505 } 2506 2507 } 2508 2509 /** 2510 * End the scope of a prefix-URI Namespace mapping. 2511 * @see org.xml.sax.ContentHandler#endPrefixMapping 2512 * 2513 * @param prefix The prefix that was being mapping. 2514 * @throws org.xml.sax.SAXException The client may throw 2515 * an exception during processing. 2516 */ 2517 public void endPrefixMapping(String prefix) throws org.xml.sax.SAXException 2518 { // do nothing 2519 } 2520 2521 /** 2522 * Receive notification of ignorable whitespace in element content. 2523 * 2524 * Not sure how to get this invoked quite yet. 2525 * 2526 * @param ch The characters from the XML document. 2527 * @param start The start position in the array. 2528 * @param length The number of characters to read from the array. 2529 * @throws org.xml.sax.SAXException Any SAX exception, possibly 2530 * wrapping another exception. 2531 * @see #characters 2532 * 2533 * @throws org.xml.sax.SAXException 2534 */ 2535 public void ignorableWhitespace(char ch[], int start, int length) 2536 throws org.xml.sax.SAXException 2537 { 2538 2539 if (0 == length) 2540 return; 2541 characters(ch, start, length); 2542 } 2543 2544 /** 2545 * Receive notification of a skipped entity. 2546 * @see org.xml.sax.ContentHandler#skippedEntity 2547 * 2548 * @param name The name of the skipped entity. If it is a 2549 * parameter entity, the name will begin with '%', 2550 * and if it is the external DTD subset, it will be the string 2551 * "[dtd]". 2552 * @throws org.xml.sax.SAXException Any SAX exception, possibly wrapping 2553 * another exception. 2554 */ 2555 public void skippedEntity(String name) throws org.xml.sax.SAXException 2556 { // TODO: Should handle 2557 } 2558 2559 /** 2560 * Report the start of a CDATA section. 2561 * 2562 * @throws org.xml.sax.SAXException The application may raise an exception. 2563 * @see #endCDATA 2564 */ 2565 public void startCDATA() throws org.xml.sax.SAXException 2566 { 2567 m_cdataStartCalled = true; 2568 } 2569 2570 /** 2571 * Report the beginning of an entity. 2572 * 2573 * The start and end of the document entity are not reported. 2574 * The start and end of the external DTD subset are reported 2575 * using the pseudo-name "[dtd]". All other events must be 2576 * properly nested within start/end entity events. 2577 * 2578 * @param name The name of the entity. If it is a parameter 2579 * entity, the name will begin with '%'. 2580 * @throws org.xml.sax.SAXException The application may raise an exception. 2581 * @see #endEntity 2582 * @see org.xml.sax.ext.DeclHandler#internalEntityDecl 2583 * @see org.xml.sax.ext.DeclHandler#externalEntityDecl 2584 */ 2585 public void startEntity(String name) throws org.xml.sax.SAXException 2586 { 2587 if (name.equals("[dtd]")) 2588 m_inExternalDTD = true; 2589 2590 if (!m_expandDTDEntities && !m_inExternalDTD) { 2591 /* Only leave the entity as-is if 2592 * we've been told not to expand them 2593 * and this is not the magic [dtd] name. 2594 */ 2595 startNonEscaping(); 2596 characters("&" + name + ';'); 2597 endNonEscaping(); 2598 } 2599 2600 m_inEntityRef = true; 2601 } 2602 2603 /** 2604 * For the enclosing elements starting tag write out 2605 * out any attributes followed by ">" 2606 * 2607 * @throws org.xml.sax.SAXException 2608 */ 2609 protected void closeStartTag() throws SAXException 2610 { 2611 2612 if (m_elemContext.m_startTagOpen) 2613 { 2614 2615 try 2616 { 2617 if (m_tracer != null) 2618 super.fireStartElem(m_elemContext.m_elementName); 2619 int nAttrs = m_attributes.getLength(); 2620 if (nAttrs > 0) 2621 { 2622 processAttributes(m_writer, nAttrs); 2623 // clear attributes object for re-use with next element 2624 m_attributes.clear(); 2625 } 2626 m_writer.write('>'); 2627 } 2628 catch (IOException e) 2629 { 2630 throw new SAXException(e); 2631 } 2632 2633 /* whether Xalan or XSLTC, we have the prefix mappings now, so 2634 * lets determine if the current element is specified in the cdata- 2635 * section-elements list. 2636 */ 2637 if (m_CdataElems != null) 2638 m_elemContext.m_isCdataSection = isCdataSection(); 2639 2640 if (m_doIndent) 2641 { 2642 m_isprevtext = false; 2643 m_preserves.push(m_ispreserve); 2644 } 2645 } 2646 2647 } 2648 2649 /** 2650 * Report the start of DTD declarations, if any. 2651 * 2652 * Any declarations are assumed to be in the internal subset unless 2653 * otherwise indicated. 2654 * 2655 * @param name The document type name. 2656 * @param publicId The declared public identifier for the 2657 * external DTD subset, or null if none was declared. 2658 * @param systemId The declared system identifier for the 2659 * external DTD subset, or null if none was declared. 2660 * @throws org.xml.sax.SAXException The application may raise an 2661 * exception. 2662 * @see #endDTD 2663 * @see #startEntity 2664 */ 2665 public void startDTD(String name, String publicId, String systemId) 2666 throws org.xml.sax.SAXException 2667 { 2668 setDoctypeSystem(systemId); 2669 setDoctypePublic(publicId); 2670 2671 m_elemContext.m_elementName = name; 2672 m_inDoctype = true; 2673 } 2674 2675 /** 2676 * Returns the m_indentAmount. 2677 * @return int 2678 */ 2679 public int getIndentAmount() 2680 { 2681 return m_indentAmount; 2682 } 2683 2684 /** 2685 * Sets the m_indentAmount. 2686 * 2687 * @param m_indentAmount The m_indentAmount to set 2688 */ 2689 public void setIndentAmount(int m_indentAmount) 2690 { 2691 this.m_indentAmount = m_indentAmount; 2692 } 2693 2694 /** 2695 * Tell if, based on space preservation constraints and the doIndent property, 2696 * if an indent should occur. 2697 * 2698 * @return True if an indent should occur. 2699 */ 2700 protected boolean shouldIndent() 2701 { 2702 return m_doIndent && (!m_ispreserve && !m_isprevtext) && m_elemContext.m_currentElemDepth > 0; 2703 } 2704 2705 /** 2706 * Searches for the list of qname properties with the specified key in the 2707 * property list. If the key is not found in this property list, the default 2708 * property list, and its defaults, recursively, are then checked. The 2709 * method returns <code>null</code> if the property is not found. 2710 * 2711 * @param key the property key. 2712 * @param props the list of properties to search in. 2713 * 2714 * Sets the vector of local-name/URI pairs of the cdata section elements 2715 * specified in the cdata-section-elements property. 2716 * 2717 * This method is essentially a copy of getQNameProperties() from 2718 * OutputProperties. Eventually this method should go away and a call 2719 * to setCdataSectionElements(Vector v) should be made directly. 2720 */ 2721 private void setCdataSectionElements(String key, Properties props) 2722 { 2723 2724 String s = props.getProperty(key); 2725 2726 if (null != s) 2727 { 2728 // Vector of URI/LocalName pairs 2729 Vector v = new Vector(); 2730 int l = s.length(); 2731 boolean inCurly = false; 2732 StringBuffer buf = new StringBuffer(); 2733 2734 // parse through string, breaking on whitespaces. I do this instead 2735 // of a tokenizer so I can track whitespace inside of curly brackets, 2736 // which theoretically shouldn't happen if they contain legal URLs. 2737 for (int i = 0; i < l; i++) 2738 { 2739 char c = s.charAt(i); 2740 2741 if (Character.isWhitespace(c)) 2742 { 2743 if (!inCurly) 2744 { 2745 if (buf.length() > 0) 2746 { 2747 addCdataSectionElement(buf.toString(), v); 2748 buf.setLength(0); 2749 } 2750 continue; 2751 } 2752 } 2753 else if ('{' == c) 2754 inCurly = true; 2755 else if ('}' == c) 2756 inCurly = false; 2757 2758 buf.append(c); 2759 } 2760 2761 if (buf.length() > 0) 2762 { 2763 addCdataSectionElement(buf.toString(), v); 2764 buf.setLength(0); 2765 } 2766 // call the official, public method to set the collected names 2767 setCdataSectionElements(v); 2768 } 2769 2770 } 2771 2772 /** 2773 * Adds a URI/LocalName pair of strings to the list. 2774 * 2775 * @param URI_and_localName String of the form "{uri}local" or "local" 2776 * 2777 * @return a QName object 2778 */ 2779 private void addCdataSectionElement(String URI_and_localName, Vector v) 2780 { 2781 2782 StringTokenizer tokenizer = 2783 new StringTokenizer(URI_and_localName, "{}", false); 2784 String s1 = tokenizer.nextToken(); 2785 String s2 = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null; 2786 2787 if (null == s2) 2788 { 2789 // add null URI and the local name 2790 v.addElement(null); 2791 v.addElement(s1); 2792 } 2793 else 2794 { 2795 // add URI, then local name 2796 v.addElement(s1); 2797 v.addElement(s2); 2798 } 2799 } 2800 2801 /** 2802 * Remembers the cdata sections specified in the cdata-section-elements. 2803 * The "official way to set URI and localName pairs. 2804 * This method should be used by both Xalan and XSLTC. 2805 * 2806 * @param URI_and_localNames a vector of pairs of Strings (URI/local) 2807 */ 2808 public void setCdataSectionElements(Vector URI_and_localNames) 2809 { 2810 // convert to the new way. 2811 if (URI_and_localNames != null) 2812 { 2813 final int len = URI_and_localNames.size() - 1; 2814 if (len > 0) 2815 { 2816 final StringBuffer sb = new StringBuffer(); 2817 for (int i = 0; i < len; i += 2) 2818 { 2819 // whitspace separated "{uri1}local1 {uri2}local2 ..." 2820 if (i != 0) 2821 sb.append(' '); 2822 final String uri = (String) URI_and_localNames.elementAt(i); 2823 final String localName = 2824 (String) URI_and_localNames.elementAt(i + 1); 2825 if (uri != null) 2826 { 2827 // If there is no URI don't put this in, just the localName then. 2828 sb.append('{'); 2829 sb.append(uri); 2830 sb.append('}'); 2831 } 2832 sb.append(localName); 2833 } 2834 m_StringOfCDATASections = sb.toString(); 2835 } 2836 } 2837 initCdataElems(m_StringOfCDATASections); 2838 } 2839 2840 /** 2841 * Makes sure that the namespace URI for the given qualified attribute name 2842 * is declared. 2843 * @param ns the namespace URI 2844 * @param rawName the qualified name 2845 * @return returns null if no action is taken, otherwise it returns the 2846 * prefix used in declaring the namespace. 2847 * @throws SAXException 2848 */ 2849 protected String ensureAttributesNamespaceIsDeclared( 2850 String ns, 2851 String localName, 2852 String rawName) 2853 throws org.xml.sax.SAXException 2854 { 2855 2856 if (ns != null && ns.length() > 0) 2857 { 2858 2859 // extract the prefix in front of the raw name 2860 int index = 0; 2861 String prefixFromRawName = 2862 (index = rawName.indexOf(":")) < 0 2863 ? "" 2864 : rawName.substring(0, index); 2865 2866 if (index > 0) 2867 { 2868 // we have a prefix, lets see if it maps to a namespace 2869 String uri = m_prefixMap.lookupNamespace(prefixFromRawName); 2870 if (uri != null && uri.equals(ns)) 2871 { 2872 // the prefix in the raw name is already maps to the given namespace uri 2873 // so we don't need to do anything 2874 return null; 2875 } 2876 else 2877 { 2878 // The uri does not map to the prefix in the raw name, 2879 // so lets make the mapping. 2880 this.startPrefixMapping(prefixFromRawName, ns, false); 2881 this.addAttribute( 2882 "http://www.w3.org/2000/xmlns/", 2883 prefixFromRawName, 2884 "xmlns:" + prefixFromRawName, 2885 "CDATA", 2886 ns, false); 2887 return prefixFromRawName; 2888 } 2889 } 2890 else 2891 { 2892 // we don't have a prefix in the raw name. 2893 // Does the URI map to a prefix already? 2894 String prefix = m_prefixMap.lookupPrefix(ns); 2895 if (prefix == null) 2896 { 2897 // uri is not associated with a prefix, 2898 // so lets generate a new prefix to use 2899 prefix = m_prefixMap.generateNextPrefix(); 2900 this.startPrefixMapping(prefix, ns, false); 2901 this.addAttribute( 2902 "http://www.w3.org/2000/xmlns/", 2903 prefix, 2904 "xmlns:" + prefix, 2905 "CDATA", 2906 ns, false); 2907 } 2908 2909 return prefix; 2910 2911 } 2912 } 2913 return null; 2914 } 2915 2916 void ensurePrefixIsDeclared(String ns, String rawName) 2917 throws org.xml.sax.SAXException 2918 { 2919 2920 if (ns != null && ns.length() > 0) 2921 { 2922 int index; 2923 final boolean no_prefix = ((index = rawName.indexOf(":")) < 0); 2924 String prefix = (no_prefix) ? "" : rawName.substring(0, index); 2925 2926 if (null != prefix) 2927 { 2928 String foundURI = m_prefixMap.lookupNamespace(prefix); 2929 2930 if ((null == foundURI) || !foundURI.equals(ns)) 2931 { 2932 this.startPrefixMapping(prefix, ns); 2933 2934 // Bugzilla1133: Generate attribute as well as namespace event. 2935 // SAX does expect both. 2936 2937 this.addAttributeAlways( 2938 "http://www.w3.org/2000/xmlns/", 2939 no_prefix ? "xmlns" : prefix, // local name 2940 no_prefix ? "xmlns" : ("xmlns:"+ prefix), // qname 2941 "CDATA", 2942 ns, 2943 false); 2944 } 2945 2946 } 2947 } 2948 } 2949 2950 /** 2951 * This method flushes any pending events, which can be startDocument() 2952 * closing the opening tag of an element, or closing an open CDATA section. 2953 */ 2954 public void flushPending() throws SAXException 2955 { 2956 if (m_needToCallStartDocument) 2957 { 2958 startDocumentInternal(); 2959 m_needToCallStartDocument = false; 2960 } 2961 if (m_elemContext.m_startTagOpen) 2962 { 2963 closeStartTag(); 2964 m_elemContext.m_startTagOpen = false; 2965 } 2966 2967 if (m_cdataTagOpen) 2968 { 2969 closeCDATA(); 2970 m_cdataTagOpen = false; 2971 } 2972 if (m_writer != null) { 2973 try { 2974 m_writer.flush(); 2975 } 2976 catch(IOException e) { 2977 // what? me worry? 2978 } 2979 } 2980 } 2981 2982 public void setContentHandler(ContentHandler ch) 2983 { 2984 // this method is really only useful in the ToSAXHandler classes but it is 2985 // in the interface. If the method defined here is ever called 2986 // we are probably in trouble. 2987 } 2988 2989 /** 2990 * Adds the given attribute to the set of attributes, even if there is 2991 * no currently open element. This is useful if a SAX startPrefixMapping() 2992 * should need to add an attribute before the element name is seen. 2993 * 2994 * This method is a copy of its super classes method, except that some 2995 * tracing of events is done. This is so the tracing is only done for 2996 * stream serializers, not for SAX ones. 2997 * 2998 * @param uri the URI of the attribute 2999 * @param localName the local name of the attribute 3000 * @param rawName the qualified name of the attribute 3001 * @param type the type of the attribute (probably CDATA) 3002 * @param value the value of the attribute 3003 * @param xslAttribute true if this attribute is coming from an xsl:attribute element. 3004 * @return true if the attribute value was added, 3005 * false if the attribute already existed and the value was 3006 * replaced with the new value. 3007 */ 3008 public boolean addAttributeAlways( 3009 String uri, 3010 String localName, 3011 String rawName, 3012 String type, 3013 String value, 3014 boolean xslAttribute) 3015 { 3016 boolean was_added; 3017 int index; 3018 if (uri == null || localName == null || uri.length() == 0) 3019 index = m_attributes.getIndex(rawName); 3020 else { 3021 index = m_attributes.getIndex(uri, localName); 3022 } 3023 3024 if (index >= 0) 3025 { 3026 String old_value = null; 3027 if (m_tracer != null) 3028 { 3029 old_value = m_attributes.getValue(index); 3030 if (value.equals(old_value)) 3031 old_value = null; 3032 } 3033 3034 /* We've seen the attribute before. 3035 * We may have a null uri or localName, but all we really 3036 * want to re-set is the value anyway. 3037 */ 3038 m_attributes.setValue(index, value); 3039 was_added = false; 3040 if (old_value != null) 3041 firePseudoAttributes(); 3042 3043 } 3044 else 3045 { 3046 // the attribute doesn't exist yet, create it 3047 if (xslAttribute) 3048 { 3049 /* 3050 * This attribute is from an xsl:attribute element so we take some care in 3051 * adding it, e.g. 3052 * <elem1 foo:attr1="1" xmlns:foo="uri1"> 3053 * <xsl:attribute name="foo:attr2">2</xsl:attribute> 3054 * </elem1> 3055 * 3056 * We are adding attr1 and attr2 both as attributes of elem1, 3057 * and this code is adding attr2 (the xsl:attribute ). 3058 * We could have a collision with the prefix like in the example above. 3059 */ 3060 3061 // In the example above, is there a prefix like foo ? 3062 final int colonIndex = rawName.indexOf(':'); 3063 if (colonIndex > 0) 3064 { 3065 String prefix = rawName.substring(0,colonIndex); 3066 NamespaceMappings.MappingRecord existing_mapping = m_prefixMap.getMappingFromPrefix(prefix); 3067 3068 /* Before adding this attribute (foo:attr2), 3069 * is the prefix for it (foo) already mapped at the current depth? 3070 */ 3071 if (existing_mapping != null 3072 && existing_mapping.m_declarationDepth == m_elemContext.m_currentElemDepth 3073 && !existing_mapping.m_uri.equals(uri)) 3074 { 3075 /* 3076 * There is an existing mapping of this prefix, 3077 * it differs from the one we need, 3078 * and unfortunately it is at the current depth so we 3079 * can not over-ride it. 3080 */ 3081 3082 /* 3083 * Are we lucky enough that an existing other prefix maps to this URI ? 3084 */ 3085 prefix = m_prefixMap.lookupPrefix(uri); 3086 if (prefix == null) 3087 { 3088 /* Unfortunately there is no existing prefix that happens to map to ours, 3089 * so to avoid a prefix collision we must generated a new prefix to use. 3090 * This is OK because the prefix URI mapping 3091 * defined in the xsl:attribute is short in scope, 3092 * just the xsl:attribute element itself, 3093 * and at this point in serialization the body of the 3094 * xsl:attribute, if any, is just a String. Right? 3095 * . . . I sure hope so - Brian M. 3096 */ 3097 prefix = m_prefixMap.generateNextPrefix(); 3098 } 3099 3100 rawName = prefix + ':' + localName; 3101 } 3102 } 3103 3104 try 3105 { 3106 /* This is our last chance to make sure the namespace for this 3107 * attribute is declared, especially if we just generated an alternate 3108 * prefix to avoid a collision (the new prefix/rawName will go out of scope 3109 * soon and be lost ... last chance here. 3110 */ 3111 String prefixUsed = 3112 ensureAttributesNamespaceIsDeclared( 3113 uri, 3114 localName, 3115 rawName); 3116 } 3117 catch (SAXException e) 3118 { 3119 // TODO Auto-generated catch block 3120 e.printStackTrace(); 3121 } 3122 } 3123 m_attributes.addAttribute(uri, localName, rawName, type, value); 3124 was_added = true; 3125 if (m_tracer != null) 3126 firePseudoAttributes(); 3127 } 3128 return was_added; 3129 } 3130 3131 /** 3132 * To fire off the pseudo characters of attributes, as they currently 3133 * exist. This method should be called everytime an attribute is added, 3134 * or when an attribute value is changed, or an element is created. 3135 */ 3136 3137 protected void firePseudoAttributes() 3138 { 3139 if (m_tracer != null) 3140 { 3141 try 3142 { 3143 // flush out the "<elemName" if not already flushed 3144 m_writer.flush(); 3145 3146 // make a StringBuffer to write the name="value" pairs to. 3147 StringBuffer sb = new StringBuffer(); 3148 int nAttrs = m_attributes.getLength(); 3149 if (nAttrs > 0) 3150 { 3151 // make a writer that internally appends to the same 3152 // StringBuffer 3153 java.io.Writer writer = 3154 new ToStream.WritertoStringBuffer(sb); 3155 3156 processAttributes(writer, nAttrs); 3157 // Don't clear the attributes! 3158 // We only want to see what would be written out 3159 // at this point, we don't want to loose them. 3160 } 3161 sb.append('>'); // the potential > after the attributes. 3162 // convert the StringBuffer to a char array and 3163 // emit the trace event that these characters "might" 3164 // be written 3165 char ch[] = sb.toString().toCharArray(); 3166 m_tracer.fireGenerateEvent( 3167 SerializerTrace.EVENTTYPE_OUTPUT_PSEUDO_CHARACTERS, 3168 ch, 3169 0, 3170 ch.length); 3171 } 3172 catch (IOException ioe) 3173 { 3174 // ignore ? 3175 } 3176 catch (SAXException se) 3177 { 3178 // ignore ? 3179 } 3180 } 3181 } 3182 3183 /** 3184 * This inner class is used only to collect attribute values 3185 * written by the method writeAttrString() into a string buffer. 3186 * In this manner trace events, and the real writing of attributes will use 3187 * the same code. 3188 */ 3189 private static class WritertoStringBuffer extends java.io.Writer 3190 { 3191 final private StringBuffer m_stringbuf; 3192 /** 3193 * @see java.io.Writer#write(char[], int, int) 3194 */ 3195 WritertoStringBuffer(StringBuffer sb) 3196 { 3197 m_stringbuf = sb; 3198 } 3199 3200 public void write(char[] arg0, int arg1, int arg2) throws IOException 3201 { 3202 m_stringbuf.append(arg0, arg1, arg2); 3203 } 3204 /** 3205 * @see java.io.Writer#flush() 3206 */ 3207 public void flush() throws IOException 3208 { 3209 } 3210 /** 3211 * @see java.io.Writer#close() 3212 */ 3213 public void close() throws IOException 3214 { 3215 } 3216 3217 public void write(int i) 3218 { 3219 m_stringbuf.append((char) i); 3220 } 3221 3222 public void write(String s) 3223 { 3224 m_stringbuf.append(s); 3225 } 3226 } 3227 3228 /** 3229 * @see SerializationHandler#setTransformer(Transformer) 3230 */ 3231 public void setTransformer(Transformer transformer) { 3232 super.setTransformer(transformer); 3233 if (m_tracer != null 3234 && !(m_writer instanceof SerializerTraceWriter) ) 3235 setWriterInternal(new SerializerTraceWriter(m_writer, m_tracer), false); 3236 3237 3238 } 3239 /** 3240 * Try's to reset the super class and reset this class for 3241 * re-use, so that you don't need to create a new serializer 3242 * (mostly for performance reasons). 3243 * 3244 * @return true if the class was successfuly reset. 3245 */ 3246 public boolean reset() 3247 { 3248 boolean wasReset = false; 3249 if (super.reset()) 3250 { 3251 resetToStream(); 3252 wasReset = true; 3253 } 3254 return wasReset; 3255 } 3256 3257 /** 3258 * Reset all of the fields owned by ToStream class 3259 * 3260 */ 3261 private void resetToStream() 3262 { 3263 this.m_cdataStartCalled = false; 3264 /* The stream is being reset. It is one of 3265 * ToXMLStream, ToHTMLStream ... and this type can't be changed 3266 * so neither should m_charInfo which is associated with the 3267 * type of Stream. Just leave m_charInfo as-is for the next re-use. 3268 * 3269 */ 3270 // this.m_charInfo = null; // don't set to null 3271 this.m_disableOutputEscapingStates.clear(); 3272 // this.m_encodingInfo = null; // don't set to null 3273 3274 this.m_escaping = true; 3275 // Leave m_format alone for now - Brian M. 3276 // this.m_format = null; 3277 this.m_expandDTDEntities = true; 3278 this.m_inDoctype = false; 3279 this.m_ispreserve = false; 3280 this.m_isprevtext = false; 3281 this.m_isUTF8 = false; // ?? used anywhere ?? 3282 this.m_lineSep = s_systemLineSep; 3283 this.m_lineSepLen = s_systemLineSep.length; 3284 this.m_lineSepUse = true; 3285 // this.m_outputStream = null; // Don't reset it may be re-used 3286 this.m_preserves.clear(); 3287 this.m_shouldFlush = true; 3288 this.m_spaceBeforeClose = false; 3289 this.m_startNewLine = false; 3290 this.m_writer_set_by_user = false; 3291 } 3292 3293 /** 3294 * Sets the character encoding coming from the xsl:output encoding stylesheet attribute. 3295 * @param encoding the character encoding 3296 */ 3297 public void setEncoding(String encoding) 3298 { 3299 setOutputProperty(OutputKeys.ENCODING,encoding); 3300 } 3301 3302 /** 3303 * Simple stack for boolean values. 3304 * 3305 * This class is a copy of the one in org.apache.xml.utils. 3306 * It exists to cut the serializers dependancy on that package. 3307 * A minor changes from that package are: 3308 * doesn't implement Clonable 3309 * 3310 * @xsl.usage internal 3311 */ 3312 static final class BoolStack 3313 { 3314 3315 /** Array of boolean values */ 3316 private boolean m_values[]; 3317 3318 /** Array size allocated */ 3319 private int m_allocatedSize; 3320 3321 /** Index into the array of booleans */ 3322 private int m_index; 3323 3324 /** 3325 * Default constructor. Note that the default 3326 * block size is very small, for small lists. 3327 */ 3328 public BoolStack() 3329 { 3330 this(32); 3331 } 3332 3333 /** 3334 * Construct a IntVector, using the given block size. 3335 * 3336 * @param size array size to allocate 3337 */ 3338 public BoolStack(int size) 3339 { 3340 3341 m_allocatedSize = size; 3342 m_values = new boolean[size]; 3343 m_index = -1; 3344 } 3345 3346 /** 3347 * Get the length of the list. 3348 * 3349 * @return Current length of the list 3350 */ 3351 public final int size() 3352 { 3353 return m_index + 1; 3354 } 3355 3356 /** 3357 * Clears the stack. 3358 * 3359 */ 3360 public final void clear() 3361 { 3362 m_index = -1; 3363 } 3364 3365 /** 3366 * Pushes an item onto the top of this stack. 3367 * 3368 * 3369 * @param val the boolean to be pushed onto this stack. 3370 * @return the <code>item</code> argument. 3371 */ 3372 public final boolean push(boolean val) 3373 { 3374 3375 if (m_index == m_allocatedSize - 1) 3376 grow(); 3377 3378 return (m_values[++m_index] = val); 3379 } 3380 3381 /** 3382 * Removes the object at the top of this stack and returns that 3383 * object as the value of this function. 3384 * 3385 * @return The object at the top of this stack. 3386 * @throws EmptyStackException if this stack is empty. 3387 */ 3388 public final boolean pop() 3389 { 3390 return m_values[m_index--]; 3391 } 3392 3393 /** 3394 * Removes the object at the top of this stack and returns the 3395 * next object at the top as the value of this function. 3396 * 3397 * 3398 * @return Next object to the top or false if none there 3399 */ 3400 public final boolean popAndTop() 3401 { 3402 3403 m_index--; 3404 3405 return (m_index >= 0) ? m_values[m_index] : false; 3406 } 3407 3408 /** 3409 * Set the item at the top of this stack 3410 * 3411 * 3412 * @param b Object to set at the top of this stack 3413 */ 3414 public final void setTop(boolean b) 3415 { 3416 m_values[m_index] = b; 3417 } 3418 3419 /** 3420 * Looks at the object at the top of this stack without removing it 3421 * from the stack. 3422 * 3423 * @return the object at the top of this stack. 3424 * @throws EmptyStackException if this stack is empty. 3425 */ 3426 public final boolean peek() 3427 { 3428 return m_values[m_index]; 3429 } 3430 3431 /** 3432 * Looks at the object at the top of this stack without removing it 3433 * from the stack. If the stack is empty, it returns false. 3434 * 3435 * @return the object at the top of this stack. 3436 */ 3437 public final boolean peekOrFalse() 3438 { 3439 return (m_index > -1) ? m_values[m_index] : false; 3440 } 3441 3442 /** 3443 * Looks at the object at the top of this stack without removing it 3444 * from the stack. If the stack is empty, it returns true. 3445 * 3446 * @return the object at the top of this stack. 3447 */ 3448 public final boolean peekOrTrue() 3449 { 3450 return (m_index > -1) ? m_values[m_index] : true; 3451 } 3452 3453 /** 3454 * Tests if this stack is empty. 3455 * 3456 * @return <code>true</code> if this stack is empty; 3457 * <code>false</code> otherwise. 3458 */ 3459 public boolean isEmpty() 3460 { 3461 return (m_index == -1); 3462 } 3463 3464 /** 3465 * Grows the size of the stack 3466 * 3467 */ 3468 private void grow() 3469 { 3470 3471 m_allocatedSize *= 2; 3472 3473 boolean newVector[] = new boolean[m_allocatedSize]; 3474 3475 System.arraycopy(m_values, 0, newVector, 0, m_index + 1); 3476 3477 m_values = newVector; 3478 } 3479 } 3480 3481 // Implement DTDHandler 3482 /** 3483 * If this method is called, the serializer is used as a 3484 * DTDHandler, which changes behavior how the serializer 3485 * handles document entities. 3486 * @see org.xml.sax.DTDHandler#notationDecl(java.lang.String, java.lang.String, java.lang.String) 3487 */ 3488 public void notationDecl(String name, String pubID, String sysID) throws SAXException { 3489 // TODO Auto-generated method stub 3490 try { 3491 DTDprolog(); 3492 3493 m_writer.write("<!NOTATION "); 3494 m_writer.write(name); 3495 if (pubID != null) { 3496 m_writer.write(" PUBLIC \""); 3497 m_writer.write(pubID); 3498 3499 } 3500 else { 3501 m_writer.write(" SYSTEM \""); 3502 m_writer.write(sysID); 3503 } 3504 m_writer.write("\" >"); 3505 m_writer.write(m_lineSep, 0, m_lineSepLen); 3506 } catch (IOException e) { 3507 // TODO Auto-generated catch block 3508 e.printStackTrace(); 3509 } 3510 } 3511 3512 /** 3513 * If this method is called, the serializer is used as a 3514 * DTDHandler, which changes behavior how the serializer 3515 * handles document entities. 3516 * @see org.xml.sax.DTDHandler#unparsedEntityDecl(java.lang.String, java.lang.String, java.lang.String, java.lang.String) 3517 */ 3518 public void unparsedEntityDecl(String name, String pubID, String sysID, String notationName) throws SAXException { 3519 // TODO Auto-generated method stub 3520 try { 3521 DTDprolog(); 3522 3523 m_writer.write("<!ENTITY "); 3524 m_writer.write(name); 3525 if (pubID != null) { 3526 m_writer.write(" PUBLIC \""); 3527 m_writer.write(pubID); 3528 3529 } 3530 else { 3531 m_writer.write(" SYSTEM \""); 3532 m_writer.write(sysID); 3533 } 3534 m_writer.write("\" NDATA "); 3535 m_writer.write(notationName); 3536 m_writer.write(" >"); 3537 m_writer.write(m_lineSep, 0, m_lineSepLen); 3538 } catch (IOException e) { 3539 // TODO Auto-generated catch block 3540 e.printStackTrace(); 3541 } 3542 } 3543 3544 /** 3545 * A private helper method to output the 3546 * @throws SAXException 3547 * @throws IOException 3548 */ 3549 private void DTDprolog() throws SAXException, IOException { 3550 final java.io.Writer writer = m_writer; 3551 if (m_needToOutputDocTypeDecl) 3552 { 3553 outputDocTypeDecl(m_elemContext.m_elementName, false); 3554 m_needToOutputDocTypeDecl = false; 3555 } 3556 if (m_inDoctype) 3557 { 3558 writer.write(" ["); 3559 writer.write(m_lineSep, 0, m_lineSepLen); 3560 m_inDoctype = false; 3561 } 3562 } 3563 3564 /** 3565 * If set to false the serializer does not expand DTD entities, 3566 * but leaves them as is, the default value is true; 3567 */ 3568 public void setDTDEntityExpansion(boolean expand) { 3569 m_expandDTDEntities = expand; 3570 } 3571 3572 /** 3573 * Sets the end of line characters to be used during serialization 3574 * @param eolChars A character array corresponding to the characters to be used. 3575 */ 3576 public void setNewLine (char[] eolChars) { 3577 m_lineSep = eolChars; 3578 m_lineSepLen = eolChars.length; 3579 } 3580 3581 /** 3582 * Remembers the cdata sections specified in the cdata-section-elements by appending the given 3583 * cdata section elements to the list. This method can be called multiple times, but once an 3584 * element is put in the list of cdata section elements it can not be removed. 3585 * This method should be used by both Xalan and XSLTC. 3586 * 3587 * @param URI_and_localNames a whitespace separated list of element names, each element 3588 * is a URI in curly braces (optional) and a local name. An example of such a parameter is: 3589 * "{http://company.com}price {myURI2}book chapter" 3590 */ 3591 public void addCdataSectionElements(String URI_and_localNames) 3592 { 3593 if (URI_and_localNames != null) 3594 initCdataElems(URI_and_localNames); 3595 if (m_StringOfCDATASections == null) 3596 m_StringOfCDATASections = URI_and_localNames; 3597 else 3598 m_StringOfCDATASections += (" " + URI_and_localNames); 3599 } 3600 }