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