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