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