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 final int limit = start + length; 2397 boolean wasDash = false; 2398 if (m_cdataTagOpen) 2399 closeCDATA(); 2400 2401 if (shouldIndent()) 2402 indent(); 2403 2404 final java.io.Writer writer = m_writer; 2405 writer.write(COMMENT_BEGIN); 2406 // Detect occurrences of two consecutive dashes, handle as necessary. 2407 for (int i = start; i < limit; i++) 2408 { 2409 if (wasDash && ch[i] == '-') 2410 { 2411 writer.write(ch, start, i - start); 2412 writer.write(" -"); 2413 start = i + 1; 2414 } 2415 wasDash = (ch[i] == '-'); 2416 } 2417 2418 // if we have some chars in the comment 2419 if (length > 0) 2420 { 2421 // Output the remaining characters (if any) 2422 final int remainingChars = (limit - start); 2423 if (remainingChars > 0) 2424 writer.write(ch, start, remainingChars); 2425 // Protect comment end from a single trailing dash 2426 if (ch[limit - 1] == '-') 2427 writer.write(' '); 2428 } 2429 writer.write(COMMENT_END); 2430 } 2431 catch (IOException e) 2432 { 2433 throw new SAXException(e); 2434 } 2435 2436 /* 2437 * Don't write out any indentation whitespace now, 2438 * because there may be non-whitespace text after this. 2439 * 2440 * Simply mark that at this point if we do decide 2441 * to indent that we should 2442 * add a newline on the end of the current line before 2443 * the indentation at the start of the next line. 2444 */ 2445 m_startNewLine = true; 2446 // time to generate comment event 2447 if (m_tracer != null) 2448 super.fireCommentEvent(ch, start_old,length); 2449 } 2450 2451 /** 2452 * Report the end of a CDATA section. 2453 * @throws org.xml.sax.SAXException The application may raise an exception. 2454 * 2455 * @see #startCDATA 2456 */ 2457 public void endCDATA() throws org.xml.sax.SAXException 2458 { 2459 if (m_cdataTagOpen) 2460 closeCDATA(); 2461 m_cdataStartCalled = false; 2462 } 2463 2464 /** 2465 * Report the end of DTD declarations. 2466 * @throws org.xml.sax.SAXException The application may raise an exception. 2467 * @see #startDTD 2468 */ 2469 public void endDTD() throws org.xml.sax.SAXException 2470 { 2471 try 2472 { 2473 // Don't output doctype declaration until startDocumentInternal 2474 // has been called. Otherwise, it can appear before XML decl. 2475 if (m_needToCallStartDocument) { 2476 return; 2477 } 2478 2479 if (m_needToOutputDocTypeDecl) 2480 { 2481 outputDocTypeDecl(m_elemContext.m_elementName, false); 2482 m_needToOutputDocTypeDecl = false; 2483 } 2484 final java.io.Writer writer = m_writer; 2485 if (!m_inDoctype) 2486 writer.write("]>"); 2487 else 2488 { 2489 writer.write('>'); 2490 } 2491 2492 writer.write(m_lineSep, 0, m_lineSepLen); 2493 } 2494 catch (IOException e) 2495 { 2496 throw new SAXException(e); 2497 } 2498 2499 } 2500 2501 /** 2502 * End the scope of a prefix-URI Namespace mapping. 2503 * @see org.xml.sax.ContentHandler#endPrefixMapping 2504 * 2505 * @param prefix The prefix that was being mapping. 2506 * @throws org.xml.sax.SAXException The client may throw 2507 * an exception during processing. 2508 */ 2509 public void endPrefixMapping(String prefix) throws org.xml.sax.SAXException 2510 { // do nothing 2511 } 2512 2513 /** 2514 * Receive notification of ignorable whitespace in element content. 2515 * 2516 * Not sure how to get this invoked quite yet. 2517 * 2518 * @param ch The characters from the XML document. 2519 * @param start The start position in the array. 2520 * @param length The number of characters to read from the array. 2521 * @throws org.xml.sax.SAXException Any SAX exception, possibly 2522 * wrapping another exception. 2523 * @see #characters 2524 * 2525 * @throws org.xml.sax.SAXException 2526 */ 2527 public void ignorableWhitespace(char ch[], int start, int length) 2528 throws org.xml.sax.SAXException 2529 { 2530 2531 if (0 == length) 2532 return; 2533 characters(ch, start, length); 2534 } 2535 2536 /** 2537 * Receive notification of a skipped entity. 2538 * @see org.xml.sax.ContentHandler#skippedEntity 2539 * 2540 * @param name The name of the skipped entity. If it is a 2541 * parameter entity, the name will begin with '%', 2542 * and if it is the external DTD subset, it will be the string 2543 * "[dtd]". 2544 * @throws org.xml.sax.SAXException Any SAX exception, possibly wrapping 2545 * another exception. 2546 */ 2547 public void skippedEntity(String name) throws org.xml.sax.SAXException 2548 { // TODO: Should handle 2549 } 2550 2551 /** 2552 * Report the start of a CDATA section. 2553 * 2554 * @throws org.xml.sax.SAXException The application may raise an exception. 2555 * @see #endCDATA 2556 */ 2557 public void startCDATA() throws org.xml.sax.SAXException 2558 { 2559 m_cdataStartCalled = true; 2560 } 2561 2562 /** 2563 * Report the beginning of an entity. 2564 * 2565 * The start and end of the document entity are not reported. 2566 * The start and end of the external DTD subset are reported 2567 * using the pseudo-name "[dtd]". All other events must be 2568 * properly nested within start/end entity events. 2569 * 2570 * @param name The name of the entity. If it is a parameter 2571 * entity, the name will begin with '%'. 2572 * @throws org.xml.sax.SAXException The application may raise an exception. 2573 * @see #endEntity 2574 * @see org.xml.sax.ext.DeclHandler#internalEntityDecl 2575 * @see org.xml.sax.ext.DeclHandler#externalEntityDecl 2576 */ 2577 public void startEntity(String name) throws org.xml.sax.SAXException 2578 { 2579 if (name.equals("[dtd]")) 2580 m_inExternalDTD = true; 2581 2582 if (!m_expandDTDEntities && !m_inExternalDTD) { 2583 /* Only leave the entity as-is if 2584 * we've been told not to expand them 2585 * and this is not the magic [dtd] name. 2586 */ 2587 startNonEscaping(); 2588 characters("&" + name + ';'); 2589 endNonEscaping(); 2590 } 2591 2592 m_inEntityRef = true; 2593 } 2594 2595 /** 2596 * For the enclosing elements starting tag write out 2597 * out any attributes followed by ">" 2598 * 2599 * @throws org.xml.sax.SAXException 2600 */ 2601 protected void closeStartTag() throws SAXException 2602 { 2603 if (m_elemContext.m_startTagOpen) 2604 { 2605 2606 try 2607 { 2608 if (m_tracer != null) 2609 super.fireStartElem(m_elemContext.m_elementName); 2610 int nAttrs = m_attributes.getLength(); 2611 if (nAttrs > 0) 2612 { 2613 processAttributes(m_writer, nAttrs); 2614 // clear attributes object for re-use with next element 2615 m_attributes.clear(); 2616 } 2617 m_writer.write('>'); 2618 } 2619 catch (IOException e) 2620 { 2621 throw new SAXException(e); 2622 } 2623 2624 /* whether Xalan or XSLTC, we have the prefix mappings now, so 2625 * lets determine if the current element is specified in the cdata- 2626 * section-elements list. 2627 */ 2628 if (m_cdataSectionElements != null) 2629 m_elemContext.m_isCdataSection = isCdataSection(); 2630 2631 if (m_doIndent) 2632 { 2633 m_isprevtext = false; 2634 m_preserves.push(m_ispreserve); 2635 } 2636 } 2637 2638 } 2639 2640 /** 2641 * Report the start of DTD declarations, if any. 2642 * 2643 * Any declarations are assumed to be in the internal subset unless 2644 * otherwise indicated. 2645 * 2646 * @param name The document type name. 2647 * @param publicId The declared public identifier for the 2648 * external DTD subset, or null if none was declared. 2649 * @param systemId The declared system identifier for the 2650 * external DTD subset, or null if none was declared. 2651 * @throws org.xml.sax.SAXException The application may raise an 2652 * exception. 2653 * @see #endDTD 2654 * @see #startEntity 2655 */ 2656 public void startDTD(String name, String publicId, String systemId) 2657 throws org.xml.sax.SAXException 2658 { 2659 setDoctypeSystem(systemId); 2660 setDoctypePublic(publicId); 2661 2662 m_elemContext.m_elementName = name; 2663 m_inDoctype = true; 2664 } 2665 2666 /** 2667 * Returns the m_indentAmount. 2668 * @return int 2669 */ 2670 public int getIndentAmount() 2671 { 2672 return m_indentAmount; 2673 } 2674 2675 /** 2676 * Sets the m_indentAmount. 2677 * 2678 * @param m_indentAmount The m_indentAmount to set 2679 */ 2680 public void setIndentAmount(int m_indentAmount) 2681 { 2682 this.m_indentAmount = m_indentAmount; 2683 } 2684 2685 /** 2686 * Tell if, based on space preservation constraints and the doIndent property, 2687 * if an indent should occur. 2688 * 2689 * @return True if an indent should occur. 2690 */ 2691 protected boolean shouldIndent() 2692 { 2693 return m_doIndent && (!m_ispreserve && !m_isprevtext) && m_elemContext.m_currentElemDepth > 0; 2694 } 2695 2696 /** 2697 * Searches for the list of qname properties with the specified key in the 2698 * property list. If the key is not found in this property list, the default 2699 * property list, and its defaults, recursively, are then checked. The 2700 * method returns <code>null</code> if the property is not found. 2701 * 2702 * @param key the property key. 2703 * @param props the list of properties to search in. 2704 * 2705 * Sets the vector of local-name/URI pairs of the cdata section elements 2706 * specified in the cdata-section-elements property. 2707 * 2708 * This method is essentially a copy of getQNameProperties() from 2709 * OutputProperties. Eventually this method should go away and a call 2710 * to setCdataSectionElements(Vector v) should be made directly. 2711 */ 2712 private void setCdataSectionElements(String key, Properties props) 2713 { 2714 2715 String s = props.getProperty(key); 2716 2717 if (null != s) 2718 { 2719 // Vector of URI/LocalName pairs 2720 Vector v = new Vector(); 2721 int l = s.length(); 2722 boolean inCurly = false; 2723 StringBuffer buf = new StringBuffer(); 2724 2725 // parse through string, breaking on whitespaces. I do this instead 2726 // of a tokenizer so I can track whitespace inside of curly brackets, 2727 // which theoretically shouldn't happen if they contain legal URLs. 2728 for (int i = 0; i < l; i++) 2729 { 2730 char c = s.charAt(i); 2731 2732 if (Character.isWhitespace(c)) 2733 { 2734 if (!inCurly) 2735 { 2736 if (buf.length() > 0) 2737 { 2738 addCdataSectionElement(buf.toString(), v); 2739 buf.setLength(0); 2740 } 2741 continue; 2742 } 2743 } 2744 else if ('{' == c) 2745 inCurly = true; 2746 else if ('}' == c) 2747 inCurly = false; 2748 2749 buf.append(c); 2750 } 2751 2752 if (buf.length() > 0) 2753 { 2754 addCdataSectionElement(buf.toString(), v); 2755 buf.setLength(0); 2756 } 2757 // call the official, public method to set the collected names 2758 setCdataSectionElements(v); 2759 } 2760 2761 } 2762 2763 /** 2764 * Adds a URI/LocalName pair of strings to the list. 2765 * 2766 * @param URI_and_localName String of the form "{uri}local" or "local" 2767 * 2768 * @return a QName object 2769 */ 2770 private void addCdataSectionElement(String URI_and_localName, Vector v) 2771 { 2772 2773 StringTokenizer tokenizer = 2774 new StringTokenizer(URI_and_localName, "{}", false); 2775 String s1 = tokenizer.nextToken(); 2776 String s2 = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null; 2777 2778 if (null == s2) 2779 { 2780 // add null URI and the local name 2781 v.addElement(null); 2782 v.addElement(s1); 2783 } 2784 else 2785 { 2786 // add URI, then local name 2787 v.addElement(s1); 2788 v.addElement(s2); 2789 } 2790 } 2791 2792 /** 2793 * Remembers the cdata sections specified in the cdata-section-elements. 2794 * The "official way to set URI and localName pairs. 2795 * This method should be used by both Xalan and XSLTC. 2796 * 2797 * @param URI_and_localNames a vector of pairs of Strings (URI/local) 2798 */ 2799 public void setCdataSectionElements(Vector URI_and_localNames) 2800 { 2801 m_cdataSectionElements = URI_and_localNames; 2802 } 2803 2804 /** 2805 * Makes sure that the namespace URI for the given qualified attribute name 2806 * is declared. 2807 * @param ns the namespace URI 2808 * @param rawName the qualified name 2809 * @return returns null if no action is taken, otherwise it returns the 2810 * prefix used in declaring the namespace. 2811 * @throws SAXException 2812 */ 2813 protected String ensureAttributesNamespaceIsDeclared( 2814 String ns, 2815 String localName, 2816 String rawName) 2817 throws org.xml.sax.SAXException 2818 { 2819 2820 if (ns != null && ns.length() > 0) 2821 { 2822 2823 // extract the prefix in front of the raw name 2824 int index = 0; 2825 String prefixFromRawName = 2826 (index = rawName.indexOf(":")) < 0 2827 ? "" 2828 : rawName.substring(0, index); 2829 2830 if (index > 0) 2831 { 2832 // we have a prefix, lets see if it maps to a namespace 2833 String uri = m_prefixMap.lookupNamespace(prefixFromRawName); 2834 if (uri != null && uri.equals(ns)) 2835 { 2836 // the prefix in the raw name is already maps to the given namespace uri 2837 // so we don't need to do anything 2838 return null; 2839 } 2840 else 2841 { 2842 // The uri does not map to the prefix in the raw name, 2843 // so lets make the mapping. 2844 this.startPrefixMapping(prefixFromRawName, ns, false); 2845 this.addAttribute( 2846 "http://www.w3.org/2000/xmlns/", 2847 prefixFromRawName, 2848 "xmlns:" + prefixFromRawName, 2849 "CDATA", 2850 ns, false); 2851 return prefixFromRawName; 2852 } 2853 } 2854 else 2855 { 2856 // we don't have a prefix in the raw name. 2857 // Does the URI map to a prefix already? 2858 String prefix = m_prefixMap.lookupPrefix(ns); 2859 if (prefix == null) 2860 { 2861 // uri is not associated with a prefix, 2862 // so lets generate a new prefix to use 2863 prefix = m_prefixMap.generateNextPrefix(); 2864 this.startPrefixMapping(prefix, ns, false); 2865 this.addAttribute( 2866 "http://www.w3.org/2000/xmlns/", 2867 prefix, 2868 "xmlns:" + prefix, 2869 "CDATA", 2870 ns, false); 2871 } 2872 2873 return prefix; 2874 2875 } 2876 } 2877 return null; 2878 } 2879 2880 void ensurePrefixIsDeclared(String ns, String rawName) 2881 throws org.xml.sax.SAXException 2882 { 2883 2884 if (ns != null && ns.length() > 0) 2885 { 2886 int index; 2887 final boolean no_prefix = ((index = rawName.indexOf(":")) < 0); 2888 String prefix = (no_prefix) ? "" : rawName.substring(0, index); 2889 2890 if (null != prefix) 2891 { 2892 String foundURI = m_prefixMap.lookupNamespace(prefix); 2893 2894 if ((null == foundURI) || !foundURI.equals(ns)) 2895 { 2896 this.startPrefixMapping(prefix, ns); 2897 2898 // Bugzilla1133: Generate attribute as well as namespace event. 2899 // SAX does expect both. 2900 2901 this.addAttributeAlways( 2902 "http://www.w3.org/2000/xmlns/", 2903 no_prefix ? "xmlns" : prefix, // local name 2904 no_prefix ? "xmlns" : ("xmlns:"+ prefix), // qname 2905 "CDATA", 2906 ns, 2907 false); 2908 } 2909 2910 } 2911 } 2912 } 2913 2914 /** 2915 * This method flushes any pending events, which can be startDocument() 2916 * closing the opening tag of an element, or closing an open CDATA section. 2917 */ 2918 public void flushPending() throws SAXException 2919 { 2920 if (m_needToCallStartDocument) 2921 { 2922 startDocumentInternal(); 2923 m_needToCallStartDocument = false; 2924 } 2925 if (m_elemContext.m_startTagOpen) 2926 { 2927 closeStartTag(); 2928 m_elemContext.m_startTagOpen = false; 2929 } 2930 2931 if (m_cdataTagOpen) 2932 { 2933 closeCDATA(); 2934 m_cdataTagOpen = false; 2935 } 2936 if (m_writer != null) { 2937 try { 2938 m_writer.flush(); 2939 } 2940 catch(IOException e) { 2941 // what? me worry? 2942 } 2943 } 2944 } 2945 2946 public void setContentHandler(ContentHandler ch) 2947 { 2948 // this method is really only useful in the ToSAXHandler classes but it is 2949 // in the interface. If the method defined here is ever called 2950 // we are probably in trouble. 2951 } 2952 2953 /** 2954 * Adds the given attribute to the set of attributes, even if there is 2955 * no currently open element. This is useful if a SAX startPrefixMapping() 2956 * should need to add an attribute before the element name is seen. 2957 * 2958 * This method is a copy of its super classes method, except that some 2959 * tracing of events is done. This is so the tracing is only done for 2960 * stream serializers, not for SAX ones. 2961 * 2962 * @param uri the URI of the attribute 2963 * @param localName the local name of the attribute 2964 * @param rawName the qualified name of the attribute 2965 * @param type the type of the attribute (probably CDATA) 2966 * @param value the value of the attribute 2967 * @param xslAttribute true if this attribute is coming from an xsl:attribute element. 2968 * @return true if the attribute value was added, 2969 * false if the attribute already existed and the value was 2970 * replaced with the new value. 2971 */ 2972 public boolean addAttributeAlways( 2973 String uri, 2974 String localName, 2975 String rawName, 2976 String type, 2977 String value, 2978 boolean xslAttribute) 2979 { 2980 boolean was_added; 2981 int index; 2982 //if (uri == null || localName == null || uri.length() == 0) 2983 index = m_attributes.getIndex(rawName); 2984 // Don't use 'localName' as it gives incorrect value, rely only on 'rawName' 2985 /*else { 2986 index = m_attributes.getIndex(uri, localName); 2987 }*/ 2988 if (index >= 0) 2989 { 2990 String old_value = null; 2991 if (m_tracer != null) 2992 { 2993 old_value = m_attributes.getValue(index); 2994 if (value.equals(old_value)) 2995 old_value = null; 2996 } 2997 2998 /* We've seen the attribute before. 2999 * We may have a null uri or localName, but all we really 3000 * want to re-set is the value anyway. 3001 */ 3002 m_attributes.setValue(index, value); 3003 was_added = false; 3004 if (old_value != null){ 3005 firePseudoAttributes(); 3006 } 3007 3008 } 3009 else 3010 { 3011 // the attribute doesn't exist yet, create it 3012 if (xslAttribute) 3013 { 3014 /* 3015 * This attribute is from an xsl:attribute element so we take some care in 3016 * adding it, e.g. 3017 * <elem1 foo:attr1="1" xmlns:foo="uri1"> 3018 * <xsl:attribute name="foo:attr2">2</xsl:attribute> 3019 * </elem1> 3020 * 3021 * We are adding attr1 and attr2 both as attributes of elem1, 3022 * and this code is adding attr2 (the xsl:attribute ). 3023 * We could have a collision with the prefix like in the example above. 3024 */ 3025 3026 // In the example above, is there a prefix like foo ? 3027 final int colonIndex = rawName.indexOf(':'); 3028 if (colonIndex > 0) 3029 { 3030 String prefix = rawName.substring(0,colonIndex); 3031 NamespaceMappings.MappingRecord existing_mapping = m_prefixMap.getMappingFromPrefix(prefix); 3032 3033 /* Before adding this attribute (foo:attr2), 3034 * is the prefix for it (foo) already mapped at the current depth? 3035 */ 3036 if (existing_mapping != null 3037 && existing_mapping.m_declarationDepth == m_elemContext.m_currentElemDepth 3038 && !existing_mapping.m_uri.equals(uri)) 3039 { 3040 /* 3041 * There is an existing mapping of this prefix, 3042 * it differs from the one we need, 3043 * and unfortunately it is at the current depth so we 3044 * can not over-ride it. 3045 */ 3046 3047 /* 3048 * Are we lucky enough that an existing other prefix maps to this URI ? 3049 */ 3050 prefix = m_prefixMap.lookupPrefix(uri); 3051 if (prefix == null) 3052 { 3053 /* Unfortunately there is no existing prefix that happens to map to ours, 3054 * so to avoid a prefix collision we must generated a new prefix to use. 3055 * This is OK because the prefix URI mapping 3056 * defined in the xsl:attribute is short in scope, 3057 * just the xsl:attribute element itself, 3058 * and at this point in serialization the body of the 3059 * xsl:attribute, if any, is just a String. Right? 3060 * . . . I sure hope so - Brian M. 3061 */ 3062 prefix = m_prefixMap.generateNextPrefix(); 3063 } 3064 3065 rawName = prefix + ':' + localName; 3066 } 3067 } 3068 3069 try 3070 { 3071 /* This is our last chance to make sure the namespace for this 3072 * attribute is declared, especially if we just generated an alternate 3073 * prefix to avoid a collision (the new prefix/rawName will go out of scope 3074 * soon and be lost ... last chance here. 3075 */ 3076 String prefixUsed = 3077 ensureAttributesNamespaceIsDeclared( 3078 uri, 3079 localName, 3080 rawName); 3081 } 3082 catch (SAXException e) 3083 { 3084 // TODO Auto-generated catch block 3085 e.printStackTrace(); 3086 } 3087 } 3088 m_attributes.addAttribute(uri, localName, rawName, type, value); 3089 was_added = true; 3090 if (m_tracer != null){ 3091 firePseudoAttributes(); 3092 } 3093 } 3094 return was_added; 3095 } 3096 3097 /** 3098 * To fire off the pseudo characters of attributes, as they currently 3099 * exist. This method should be called everytime an attribute is added, 3100 * or when an attribute value is changed, or an element is created. 3101 */ 3102 3103 protected void firePseudoAttributes() 3104 { 3105 if (m_tracer != null) 3106 { 3107 try 3108 { 3109 // flush out the "<elemName" if not already flushed 3110 m_writer.flush(); 3111 3112 // make a StringBuffer to write the name="value" pairs to. 3113 StringBuffer sb = new StringBuffer(); 3114 int nAttrs = m_attributes.getLength(); 3115 if (nAttrs > 0) 3116 { 3117 // make a writer that internally appends to the same 3118 // StringBuffer 3119 java.io.Writer writer = 3120 new ToStream.WritertoStringBuffer(sb); 3121 3122 processAttributes(writer, nAttrs); 3123 // Don't clear the attributes! 3124 // We only want to see what would be written out 3125 // at this point, we don't want to loose them. 3126 } 3127 sb.append('>'); // the potential > after the attributes. 3128 // convert the StringBuffer to a char array and 3129 // emit the trace event that these characters "might" 3130 // be written 3131 char ch[] = sb.toString().toCharArray(); 3132 m_tracer.fireGenerateEvent( 3133 SerializerTrace.EVENTTYPE_OUTPUT_PSEUDO_CHARACTERS, 3134 ch, 3135 0, 3136 ch.length); 3137 } 3138 catch (IOException ioe) 3139 { 3140 // ignore ? 3141 } 3142 catch (SAXException se) 3143 { 3144 // ignore ? 3145 } 3146 } 3147 } 3148 3149 /** 3150 * This inner class is used only to collect attribute values 3151 * written by the method writeAttrString() into a string buffer. 3152 * In this manner trace events, and the real writing of attributes will use 3153 * the same code. 3154 */ 3155 private class WritertoStringBuffer extends java.io.Writer 3156 { 3157 final private StringBuffer m_stringbuf; 3158 /** 3159 * @see java.io.Writer#write(char[], int, int) 3160 */ 3161 WritertoStringBuffer(StringBuffer sb) 3162 { 3163 m_stringbuf = sb; 3164 } 3165 3166 public void write(char[] arg0, int arg1, int arg2) throws IOException 3167 { 3168 m_stringbuf.append(arg0, arg1, arg2); 3169 } 3170 /** 3171 * @see java.io.Writer#flush() 3172 */ 3173 public void flush() throws IOException 3174 { 3175 } 3176 /** 3177 * @see java.io.Writer#close() 3178 */ 3179 public void close() throws IOException 3180 { 3181 } 3182 3183 public void write(int i) 3184 { 3185 m_stringbuf.append((char) i); 3186 } 3187 3188 public void write(String s) 3189 { 3190 m_stringbuf.append(s); 3191 } 3192 } 3193 3194 /** 3195 * @see SerializationHandler#setTransformer(Transformer) 3196 */ 3197 public void setTransformer(Transformer transformer) { 3198 super.setTransformer(transformer); 3199 if (m_tracer != null 3200 && !(m_writer instanceof SerializerTraceWriter) ) 3201 m_writer = new SerializerTraceWriter(m_writer, m_tracer); 3202 3203 3204 } 3205 /** 3206 * Try's to reset the super class and reset this class for 3207 * re-use, so that you don't need to create a new serializer 3208 * (mostly for performance reasons). 3209 * 3210 * @return true if the class was successfuly reset. 3211 */ 3212 public boolean reset() 3213 { 3214 boolean wasReset = false; 3215 if (super.reset()) 3216 { 3217 resetToStream(); 3218 wasReset = true; 3219 } 3220 return wasReset; 3221 } 3222 3223 /** 3224 * Reset all of the fields owned by ToStream class 3225 * 3226 */ 3227 private void resetToStream() 3228 { 3229 this.m_cdataStartCalled = false; 3230 /* The stream is being reset. It is one of 3231 * ToXMLStream, ToHTMLStream ... and this type can't be changed 3232 * so neither should m_charInfo which is associated with the 3233 * type of Stream. Just leave m_charInfo as-is for the next re-use. 3234 * 3235 */ 3236 // this.m_charInfo = null; // don't set to null 3237 3238 this.m_disableOutputEscapingStates.clear(); 3239 3240 this.m_escaping = true; 3241 // Leave m_format alone for now - Brian M. 3242 // this.m_format = null; 3243 this.m_inDoctype = false; 3244 this.m_ispreserve = false; 3245 this.m_ispreserve = false; 3246 this.m_isprevtext = false; 3247 this.m_isUTF8 = false; // ?? used anywhere ?? 3248 this.m_preserves.clear(); 3249 this.m_shouldFlush = true; 3250 this.m_spaceBeforeClose = false; 3251 this.m_startNewLine = false; 3252 this.m_lineSepUse = true; 3253 // DON'T SET THE WRITER TO NULL, IT MAY BE REUSED !! 3254 // this.m_writer = null; 3255 this.m_expandDTDEntities = true; 3256 3257 } 3258 3259 /** 3260 * Sets the character encoding coming from the xsl:output encoding stylesheet attribute. 3261 * @param encoding the character encoding 3262 */ 3263 public void setEncoding(String encoding) 3264 { 3265 String old = getEncoding(); 3266 super.setEncoding(encoding); 3267 if (old == null || !old.equals(encoding)) { 3268 // If we have changed the setting of the 3269 m_encodingInfo = Encodings.getEncodingInfo(encoding); 3270 3271 if (encoding != null && m_encodingInfo.name == null) { 3272 // We tried to get an EncodingInfo for Object for the given 3273 // encoding, but it came back with an internall null name 3274 // so the encoding is not supported by the JDK, issue a message. 3275 String msg = Utils.messages.createMessage( 3276 MsgKey.ER_ENCODING_NOT_SUPPORTED,new Object[]{ encoding }); 3277 try 3278 { 3279 // Prepare to issue the warning message 3280 Transformer tran = super.getTransformer(); 3281 if (tran != null) { 3282 ErrorListener errHandler = tran.getErrorListener(); 3283 // Issue the warning message 3284 if (null != errHandler && m_sourceLocator != null) 3285 errHandler.warning(new TransformerException(msg, m_sourceLocator)); 3286 else 3287 System.out.println(msg); 3288 } 3289 else 3290 System.out.println(msg); 3291 } 3292 catch (Exception e){} 3293 } 3294 } 3295 return; 3296 } 3297 3298 /** 3299 * Simple stack for boolean values. 3300 * 3301 * This class is a copy of the one in com.sun.org.apache.xml.internal.utils. 3302 * It exists to cut the serializers dependancy on that package. 3303 * A minor changes from that package are: 3304 * doesn't implement Clonable 3305 * 3306 * @xsl.usage internal 3307 */ 3308 static final class BoolStack 3309 { 3310 3311 /** Array of boolean values */ 3312 private boolean m_values[]; 3313 3314 /** Array size allocated */ 3315 private int m_allocatedSize; 3316 3317 /** Index into the array of booleans */ 3318 private int m_index; 3319 3320 /** 3321 * Default constructor. Note that the default 3322 * block size is very small, for small lists. 3323 */ 3324 public BoolStack() 3325 { 3326 this(32); 3327 } 3328 3329 /** 3330 * Construct a IntVector, using the given block size. 3331 * 3332 * @param size array size to allocate 3333 */ 3334 public BoolStack(int size) 3335 { 3336 3337 m_allocatedSize = size; 3338 m_values = new boolean[size]; 3339 m_index = -1; 3340 } 3341 3342 /** 3343 * Get the length of the list. 3344 * 3345 * @return Current length of the list 3346 */ 3347 public final int size() 3348 { 3349 return m_index + 1; 3350 } 3351 3352 /** 3353 * Clears the stack. 3354 * 3355 */ 3356 public final void clear() 3357 { 3358 m_index = -1; 3359 } 3360 3361 /** 3362 * Pushes an item onto the top of this stack. 3363 * 3364 * 3365 * @param val the boolean to be pushed onto this stack. 3366 * @return the <code>item</code> argument. 3367 */ 3368 public final boolean push(boolean val) 3369 { 3370 3371 if (m_index == m_allocatedSize - 1) 3372 grow(); 3373 3374 return (m_values[++m_index] = val); 3375 } 3376 3377 /** 3378 * Removes the object at the top of this stack and returns that 3379 * object as the value of this function. 3380 * 3381 * @return The object at the top of this stack. 3382 * @throws EmptyStackException if this stack is empty. 3383 */ 3384 public final boolean pop() 3385 { 3386 return m_values[m_index--]; 3387 } 3388 3389 /** 3390 * Removes the object at the top of this stack and returns the 3391 * next object at the top as the value of this function. 3392 * 3393 * 3394 * @return Next object to the top or false if none there 3395 */ 3396 public final boolean popAndTop() 3397 { 3398 3399 m_index--; 3400 3401 return (m_index >= 0) ? m_values[m_index] : false; 3402 } 3403 3404 /** 3405 * Set the item at the top of this stack 3406 * 3407 * 3408 * @param b Object to set at the top of this stack 3409 */ 3410 public final void setTop(boolean b) 3411 { 3412 m_values[m_index] = b; 3413 } 3414 3415 /** 3416 * Looks at the object at the top of this stack without removing it 3417 * from the stack. 3418 * 3419 * @return the object at the top of this stack. 3420 * @throws EmptyStackException if this stack is empty. 3421 */ 3422 public final boolean peek() 3423 { 3424 return m_values[m_index]; 3425 } 3426 3427 /** 3428 * Looks at the object at the top of this stack without removing it 3429 * from the stack. If the stack is empty, it returns false. 3430 * 3431 * @return the object at the top of this stack. 3432 */ 3433 public final boolean peekOrFalse() 3434 { 3435 return (m_index > -1) ? m_values[m_index] : false; 3436 } 3437 3438 /** 3439 * Looks at the object at the top of this stack without removing it 3440 * from the stack. If the stack is empty, it returns true. 3441 * 3442 * @return the object at the top of this stack. 3443 */ 3444 public final boolean peekOrTrue() 3445 { 3446 return (m_index > -1) ? m_values[m_index] : true; 3447 } 3448 3449 /** 3450 * Tests if this stack is empty. 3451 * 3452 * @return <code>true</code> if this stack is empty; 3453 * <code>false</code> otherwise. 3454 */ 3455 public boolean isEmpty() 3456 { 3457 return (m_index == -1); 3458 } 3459 3460 /** 3461 * Grows the size of the stack 3462 * 3463 */ 3464 private void grow() 3465 { 3466 3467 m_allocatedSize *= 2; 3468 3469 boolean newVector[] = new boolean[m_allocatedSize]; 3470 3471 System.arraycopy(m_values, 0, newVector, 0, m_index + 1); 3472 3473 m_values = newVector; 3474 } 3475 } 3476 3477 // Implement DTDHandler 3478 /** 3479 * If this method is called, the serializer is used as a 3480 * DTDHandler, which changes behavior how the serializer 3481 * handles document entities. 3482 * @see org.xml.sax.DTDHandler#notationDecl(java.lang.String, java.lang.String, java.lang.String) 3483 */ 3484 public void notationDecl(String name, String pubID, String sysID) throws SAXException { 3485 // TODO Auto-generated method stub 3486 try { 3487 DTDprolog(); 3488 3489 m_writer.write("<!NOTATION "); 3490 m_writer.write(name); 3491 if (pubID != null) { 3492 m_writer.write(" PUBLIC \""); 3493 m_writer.write(pubID); 3494 3495 } 3496 else { 3497 m_writer.write(" SYSTEM \""); 3498 m_writer.write(sysID); 3499 } 3500 m_writer.write("\" >"); 3501 m_writer.write(m_lineSep, 0, m_lineSepLen); 3502 } catch (IOException e) { 3503 // TODO Auto-generated catch block 3504 e.printStackTrace(); 3505 } 3506 } 3507 3508 /** 3509 * If this method is called, the serializer is used as a 3510 * DTDHandler, which changes behavior how the serializer 3511 * handles document entities. 3512 * @see org.xml.sax.DTDHandler#unparsedEntityDecl(java.lang.String, java.lang.String, java.lang.String, java.lang.String) 3513 */ 3514 public void unparsedEntityDecl(String name, String pubID, String sysID, String notationName) throws SAXException { 3515 // TODO Auto-generated method stub 3516 try { 3517 DTDprolog(); 3518 3519 m_writer.write("<!ENTITY "); 3520 m_writer.write(name); 3521 if (pubID != null) { 3522 m_writer.write(" PUBLIC \""); 3523 m_writer.write(pubID); 3524 3525 } 3526 else { 3527 m_writer.write(" SYSTEM \""); 3528 m_writer.write(sysID); 3529 } 3530 m_writer.write("\" NDATA "); 3531 m_writer.write(notationName); 3532 m_writer.write(" >"); 3533 m_writer.write(m_lineSep, 0, m_lineSepLen); 3534 } catch (IOException e) { 3535 // TODO Auto-generated catch block 3536 e.printStackTrace(); 3537 } 3538 } 3539 3540 /** 3541 * A private helper method to output the 3542 * @throws SAXException 3543 * @throws IOException 3544 */ 3545 private void DTDprolog() throws SAXException, IOException { 3546 final java.io.Writer writer = m_writer; 3547 if (m_needToOutputDocTypeDecl) 3548 { 3549 outputDocTypeDecl(m_elemContext.m_elementName, false); 3550 m_needToOutputDocTypeDecl = false; 3551 } 3552 if (m_inDoctype) 3553 { 3554 writer.write(" ["); 3555 writer.write(m_lineSep, 0, m_lineSepLen); 3556 m_inDoctype = false; 3557 } 3558 } 3559 3560 /** 3561 * If set to false the serializer does not expand DTD entities, 3562 * but leaves them as is, the default value is true; 3563 */ 3564 public void setDTDEntityExpansion(boolean expand) { 3565 m_expandDTDEntities = expand; 3566 } 3567 }