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