1 /*
   2  * reserved comment block
   3  * DO NOT REMOVE OR ALTER!
   4  */
   5 /*
   6  * Licensed to the Apache Software Foundation (ASF) under one or more
   7  * contributor license agreements.  See the NOTICE file distributed with
   8  * this work for additional information regarding copyright ownership.
   9  * The ASF licenses this file to You under the Apache License, Version 2.0
  10  * (the "License"); you may not use this file except in compliance with
  11  * the License.  You may obtain a copy of the License at
  12  *
  13  *      http://www.apache.org/licenses/LICENSE-2.0
  14  *
  15  * Unless required by applicable law or agreed to in writing, software
  16  * distributed under the License is distributed on an "AS IS" BASIS,
  17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18  * See the License for the specific language governing permissions and
  19  * limitations under the License.
  20  */
  21 
  22 package com.sun.org.apache.xml.internal.serializer;
  23 
  24 import java.io.IOException;
  25 
  26 import com.sun.org.apache.xml.internal.serializer.utils.MsgKey;
  27 import com.sun.org.apache.xml.internal.serializer.utils.Utils;
  28 import org.xml.sax.Attributes;
  29 import org.xml.sax.SAXException;
  30 
  31 /**
  32  * This class is not a public API.
  33  * It is only public because it is used in other packages.
  34  * This class converts SAX or SAX-like calls to a
  35  * serialized document for xsl:output method of "text".
  36  * @xsl.usage internal
  37  */
  38 public final class ToTextStream extends ToStream
  39 {
  40 
  41 
  42   /**
  43    * Default constructor.
  44    */
  45   public ToTextStream()
  46   {
  47     super();
  48   }
  49 
  50 
  51 
  52   /**
  53    * Receive notification of the beginning of a document.
  54    *
  55    * <p>The SAX parser will invoke this method only once, before any
  56    * other methods in this interface or in DTDHandler (except for
  57    * setDocumentLocator).</p>
  58    *
  59    * @throws org.xml.sax.SAXException Any SAX exception, possibly
  60    *            wrapping another exception.
  61    *
  62    * @throws org.xml.sax.SAXException
  63    */
  64   protected void startDocumentInternal() throws org.xml.sax.SAXException
  65   {
  66     super.startDocumentInternal();
  67 
  68     m_needToCallStartDocument = false;
  69 
  70     // No action for the moment.
  71   }
  72 
  73   /**
  74    * Receive notification of the end of a document.
  75    *
  76    * <p>The SAX parser will invoke this method only once, and it will
  77    * be the last method invoked during the parse.  The parser shall
  78    * not invoke this method until it has either abandoned parsing
  79    * (because of an unrecoverable error) or reached the end of
  80    * input.</p>
  81    *
  82    * @throws org.xml.sax.SAXException Any SAX exception, possibly
  83    *            wrapping another exception.
  84    *
  85    * @throws org.xml.sax.SAXException
  86    */
  87   public void endDocument() throws org.xml.sax.SAXException
  88   {
  89     flushPending();
  90     flushWriter();
  91     if (m_tracer != null)
  92         super.fireEndDoc();
  93   }
  94 
  95   /**
  96    * Receive notification of the beginning of an element.
  97    *
  98    * <p>The Parser will invoke this method at the beginning of every
  99    * element in the XML document; there will be a corresponding
 100    * endElement() event for every startElement() event (even when the
 101    * element is empty). All of the element's content will be
 102    * reported, in order, before the corresponding endElement()
 103    * event.</p>
 104    *
 105    * <p>If the element name has a namespace prefix, the prefix will
 106    * still be attached.  Note that the attribute list provided will
 107    * contain only attributes with explicit values (specified or
 108    * defaulted): #IMPLIED attributes will be omitted.</p>
 109    *
 110    *
 111    * @param namespaceURI The Namespace URI, or the empty string if the
 112    *        element has no Namespace URI or if Namespace
 113    *        processing is not being performed.
 114    * @param localName The local name (without prefix), or the
 115    *        empty string if Namespace processing is not being
 116    *        performed.
 117    * @param name The qualified name (with prefix), or the
 118    *        empty string if qualified names are not available.
 119    * @param atts The attributes attached to the element, if any.
 120    * @throws org.xml.sax.SAXException Any SAX exception, possibly
 121    *            wrapping another exception.
 122    * @see #endElement
 123    * @see org.xml.sax.AttributeList
 124    *
 125    * @throws org.xml.sax.SAXException
 126    */
 127   public void startElement(
 128           String namespaceURI, String localName, String name, Attributes atts)
 129             throws org.xml.sax.SAXException
 130   {
 131     // time to fire off startElement event
 132     if (m_tracer != null) {
 133         super.fireStartElem(name);
 134         this.firePseudoAttributes();
 135     }
 136     return;
 137   }
 138 
 139   /**
 140    * Receive notification of the end of an element.
 141    *
 142    * <p>The SAX parser will invoke this method at the end of every
 143    * element in the XML document; there will be a corresponding
 144    * startElement() event for every endElement() event (even when the
 145    * element is empty).</p>
 146    *
 147    * <p>If the element name has a namespace prefix, the prefix will
 148    * still be attached to the name.</p>
 149    *
 150    *
 151    * @param namespaceURI The Namespace URI, or the empty string if the
 152    *        element has no Namespace URI or if Namespace
 153    *        processing is not being performed.
 154    * @param localName The local name (without prefix), or the
 155    *        empty string if Namespace processing is not being
 156    *        performed.
 157    * @param name The qualified name (with prefix), or the
 158    *        empty string if qualified names are not available.
 159    * @throws org.xml.sax.SAXException Any SAX exception, possibly
 160    *            wrapping another exception.
 161    *
 162    * @throws org.xml.sax.SAXException
 163    */
 164   public void endElement(String namespaceURI, String localName, String name)
 165           throws org.xml.sax.SAXException
 166   {
 167         if (m_tracer != null)
 168             super.fireEndElem(name);
 169   }
 170 
 171   /**
 172    * Receive notification of character data.
 173    *
 174    * <p>The Parser will call this method to report each chunk of
 175    * character data.  SAX parsers may return all contiguous character
 176    * data in a single chunk, or they may split it into several
 177    * chunks; however, all of the characters in any single event
 178    * must come from the same external entity, so that the Locator
 179    * provides useful information.</p>
 180    *
 181    * <p>The application must not attempt to read from the array
 182    * outside of the specified range.</p>
 183    *
 184    * <p>Note that some parsers will report whitespace using the
 185    * ignorableWhitespace() method rather than this one (validating
 186    * parsers must do so).</p>
 187    *
 188    * @param ch The characters from the XML document.
 189    * @param start The start position in the array.
 190    * @param length The number of characters to read from the array.
 191    * @throws org.xml.sax.SAXException Any SAX exception, possibly
 192    *            wrapping another exception.
 193    * @see #ignorableWhitespace
 194    * @see org.xml.sax.Locator
 195    */
 196   public void characters(char ch[], int start, int length)
 197           throws org.xml.sax.SAXException
 198   {
 199 
 200     flushPending();
 201 
 202     try
 203     {
 204         if (inTemporaryOutputState()) {
 205             /* leave characters un-processed as we are
 206              * creating temporary output, the output generated by
 207              * this serializer will be input to a final serializer
 208              * later on and it will do the processing in final
 209              * output state (not temporary output state).
 210              *
 211              * A "temporary" ToTextStream serializer is used to
 212              * evaluate attribute value templates (for example),
 213              * and the result of evaluating such a thing
 214              * is fed into a final serializer later on.
 215              */
 216             m_writer.write(ch, start, length);
 217         }
 218         else {
 219             // In final output state we do process the characters!
 220             writeNormalizedChars(ch, start, length, m_lineSepUse);
 221         }
 222 
 223         if (m_tracer != null)
 224             super.fireCharEvent(ch, start, length);
 225     }
 226     catch(IOException ioe)
 227     {
 228       throw new SAXException(ioe);
 229     }
 230   }
 231 
 232   /**
 233    * If available, when the disable-output-escaping attribute is used,
 234    * output raw text without escaping.
 235    *
 236    * @param ch The characters from the XML document.
 237    * @param start The start position in the array.
 238    * @param length The number of characters to read from the array.
 239    *
 240    * @throws org.xml.sax.SAXException Any SAX exception, possibly
 241    *            wrapping another exception.
 242    */
 243   public void charactersRaw(char ch[], int start, int length)
 244           throws org.xml.sax.SAXException
 245   {
 246 
 247     try
 248     {
 249       writeNormalizedChars(ch, start, length, m_lineSepUse);
 250     }
 251     catch(IOException ioe)
 252     {
 253       throw new SAXException(ioe);
 254     }
 255   }
 256 
 257     /**
 258      * Normalize the characters, but don't escape.  Different from
 259      * SerializerToXML#writeNormalizedChars because it does not attempt to do
 260      * XML escaping at all.
 261      *
 262      * @param ch The characters from the XML document.
 263      * @param start The start position in the array.
 264      * @param length The number of characters to read from the array.
 265      * @param useLineSep true if the operating systems
 266      * end-of-line separator should be output rather than a new-line character.
 267      *
 268      * @throws IOException
 269      * @throws org.xml.sax.SAXException
 270      */
 271     void writeNormalizedChars(
 272         final char ch[],
 273             final int start,
 274             final int length,
 275             final boolean useLineSep)
 276             throws IOException, org.xml.sax.SAXException
 277     {
 278         final String encoding = getEncoding();
 279         final java.io.Writer writer = m_writer;
 280         final int end = start + length;
 281 
 282         /* copy a few "constants" before the loop for performance */
 283         final char S_LINEFEED = CharInfo.S_LINEFEED;
 284 
 285         // This for() loop always increments i by one at the end
 286         // of the loop.  Additional increments of i adjust for when
 287         // two input characters (a high/low UTF16 surrogate pair)
 288         // are processed.
 289         for (int i = start; i < end; i++) {
 290             final char c = ch[i];
 291 
 292             if (S_LINEFEED == c && useLineSep) {
 293                 writer.write(m_lineSep, 0, m_lineSepLen);
 294                 // one input char processed
 295             } else if (m_encodingInfo.isInEncoding(c)) {
 296                 writer.write(c);
 297                 // one input char processed
 298             } else if (Encodings.isHighUTF16Surrogate(c)) {
 299                 final int codePoint = writeUTF16Surrogate(c, ch, i, end);
 300                 if (codePoint != 0) {
 301                     // I think we can just emit the message,
 302                     // not crash and burn.
 303                     final String integralValue = Integer.toString(codePoint);
 304                     final String msg = Utils.messages.createMessage(
 305                         MsgKey.ER_ILLEGAL_CHARACTER,
 306                         new Object[] { integralValue, encoding });
 307 
 308                     //Older behavior was to throw the message,
 309                     //but newer gentler behavior is to write a message to System.err
 310                     //throw new SAXException(msg);
 311                     System.err.println(msg);
 312 
 313                 }
 314                 i++; // two input chars processed
 315             } else {
 316                 // Don't know what to do with this char, it is
 317                 // not in the encoding and not a high char in
 318                 // a surrogate pair, so write out as an entity ref
 319                 if (encoding != null) {
 320                     /* The output encoding is known,
 321                      * so somthing is wrong.
 322                      */
 323 
 324                     // not in the encoding, so write out a character reference
 325                     writer.write('&');
 326                     writer.write('#');
 327                     writer.write(Integer.toString(c));
 328                     writer.write(';');
 329 
 330                     // I think we can just emit the message,
 331                     // not crash and burn.
 332                     final String integralValue = Integer.toString(c);
 333                     final String msg = Utils.messages.createMessage(
 334                         MsgKey.ER_ILLEGAL_CHARACTER,
 335                         new Object[] { integralValue, encoding });
 336 
 337                     //Older behavior was to throw the message,
 338                     //but newer gentler behavior is to write a message to System.err
 339                     //throw new SAXException(msg);
 340                     System.err.println(msg);
 341                 } else {
 342                     /* The output encoding is not known,
 343                      * so just write it out as-is.
 344                      */
 345                     writer.write(c);
 346                 }
 347 
 348                 // one input char was processed
 349             }
 350         }
 351     }
 352 
 353   /**
 354    * Receive notification of cdata.
 355    *
 356    * <p>The Parser will call this method to report each chunk of
 357    * character data.  SAX parsers may return all contiguous character
 358    * data in a single chunk, or they may split it into several
 359    * chunks; however, all of the characters in any single event
 360    * must come from the same external entity, so that the Locator
 361    * provides useful information.</p>
 362    *
 363    * <p>The application must not attempt to read from the array
 364    * outside of the specified range.</p>
 365    *
 366    * <p>Note that some parsers will report whitespace using the
 367    * ignorableWhitespace() method rather than this one (validating
 368    * parsers must do so).</p>
 369    *
 370    * @param ch The characters from the XML document.
 371    * @param start The start position in the array.
 372    * @param length The number of characters to read from the array.
 373    * @throws org.xml.sax.SAXException Any SAX exception, possibly
 374    *            wrapping another exception.
 375    * @see #ignorableWhitespace
 376    * @see org.xml.sax.Locator
 377    */
 378   public void cdata(char ch[], int start, int length)
 379           throws org.xml.sax.SAXException
 380   {
 381     try
 382     {
 383         writeNormalizedChars(ch, start, length, m_lineSepUse);
 384         if (m_tracer != null)
 385             super.fireCDATAEvent(ch, start, length);
 386     }
 387     catch(IOException ioe)
 388     {
 389       throw new SAXException(ioe);
 390     }
 391   }
 392 
 393   /**
 394    * Receive notification of ignorable whitespace in element content.
 395    *
 396    * <p>Validating Parsers must use this method to report each chunk
 397    * of ignorable whitespace (see the W3C XML 1.0 recommendation,
 398    * section 2.10): non-validating parsers may also use this method
 399    * if they are capable of parsing and using content models.</p>
 400    *
 401    * <p>SAX parsers may return all contiguous whitespace in a single
 402    * chunk, or they may split it into several chunks; however, all of
 403    * the characters in any single event must come from the same
 404    * external entity, so that the Locator provides useful
 405    * information.</p>
 406    *
 407    * <p>The application must not attempt to read from the array
 408    * outside of the specified range.</p>
 409    *
 410    * @param ch The characters from the XML document.
 411    * @param start The start position in the array.
 412    * @param length The number of characters to read from the array.
 413    * @throws org.xml.sax.SAXException Any SAX exception, possibly
 414    *            wrapping another exception.
 415    * @see #characters
 416    *
 417    * @throws org.xml.sax.SAXException
 418    */
 419   public void ignorableWhitespace(char ch[], int start, int length)
 420           throws org.xml.sax.SAXException
 421   {
 422 
 423     try
 424     {
 425       writeNormalizedChars(ch, start, length, m_lineSepUse);
 426     }
 427     catch(IOException ioe)
 428     {
 429       throw new SAXException(ioe);
 430     }
 431   }
 432 
 433   /**
 434    * Receive notification of a processing instruction.
 435    *
 436    * <p>The Parser will invoke this method once for each processing
 437    * instruction found: note that processing instructions may occur
 438    * before or after the main document element.</p>
 439    *
 440    * <p>A SAX parser should never report an XML declaration (XML 1.0,
 441    * section 2.8) or a text declaration (XML 1.0, section 4.3.1)
 442    * using this method.</p>
 443    *
 444    * @param target The processing instruction target.
 445    * @param data The processing instruction data, or null if
 446    *        none was supplied.
 447    * @throws org.xml.sax.SAXException Any SAX exception, possibly
 448    *            wrapping another exception.
 449    *
 450    * @throws org.xml.sax.SAXException
 451    */
 452   public void processingInstruction(String target, String data)
 453           throws org.xml.sax.SAXException
 454   {
 455     // flush anything pending first
 456     flushPending();
 457 
 458     if (m_tracer != null)
 459         super.fireEscapingEvent(target, data);
 460   }
 461 
 462   /**
 463    * Called when a Comment is to be constructed.
 464    * Note that Xalan will normally invoke the other version of this method.
 465    * %REVIEW% In fact, is this one ever needed, or was it a mistake?
 466    *
 467    * @param   data  The comment data.
 468    * @throws org.xml.sax.SAXException Any SAX exception, possibly
 469    *            wrapping another exception.
 470    */
 471   public void comment(String data) throws org.xml.sax.SAXException
 472   {
 473       final int length = data.length();
 474       if (length > m_charsBuff.length)
 475       {
 476           m_charsBuff = new char[length*2 + 1];
 477       }
 478       data.getChars(0, length, m_charsBuff, 0);
 479       comment(m_charsBuff, 0, length);
 480   }
 481 
 482   /**
 483    * Report an XML comment anywhere in the document.
 484    *
 485    * This callback will be used for comments inside or outside the
 486    * document element, including comments in the external DTD
 487    * subset (if read).
 488    *
 489    * @param ch An array holding the characters in the comment.
 490    * @param start The starting position in the array.
 491    * @param length The number of characters to use from the array.
 492    * @throws org.xml.sax.SAXException The application may raise an exception.
 493    */
 494   public void comment(char ch[], int start, int length)
 495           throws org.xml.sax.SAXException
 496   {
 497 
 498     flushPending();
 499     if (m_tracer != null)
 500         super.fireCommentEvent(ch, start, length);
 501   }
 502 
 503   /**
 504    * Receive notivication of a entityReference.
 505    *
 506    * @param name non-null reference to the name of the entity.
 507    *
 508    * @throws org.xml.sax.SAXException
 509    */
 510   public void entityReference(String name) throws org.xml.sax.SAXException
 511   {
 512         if (m_tracer != null)
 513             super.fireEntityReference(name);
 514   }
 515 
 516     /**
 517      * @see ExtendedContentHandler#addAttribute(String, String, String, String, String)
 518      */
 519     public void addAttribute(
 520         String uri,
 521         String localName,
 522         String rawName,
 523         String type,
 524         String value,
 525         boolean XSLAttribute)
 526     {
 527         // do nothing, just forget all about the attribute
 528     }
 529 
 530     /**
 531      * @see org.xml.sax.ext.LexicalHandler#endCDATA()
 532      */
 533     public void endCDATA() throws SAXException
 534     {
 535         // do nothing
 536     }
 537 
 538     /**
 539      * @see ExtendedContentHandler#endElement(String)
 540      */
 541     public void endElement(String elemName) throws SAXException
 542     {
 543         if (m_tracer != null)
 544             super.fireEndElem(elemName);
 545     }
 546 
 547     /**
 548      * From XSLTC
 549      */
 550     public void startElement(
 551     String elementNamespaceURI,
 552     String elementLocalName,
 553     String elementName)
 554     throws SAXException
 555     {
 556         if (m_needToCallStartDocument)
 557             startDocumentInternal();
 558         // time to fire off startlement event.
 559         if (m_tracer != null) {
 560             super.fireStartElem(elementName);
 561             this.firePseudoAttributes();
 562         }
 563 
 564         return;
 565     }
 566 
 567 
 568     /**
 569      * From XSLTC
 570      */
 571     public void characters(String characters)
 572     throws SAXException
 573     {
 574         final int length = characters.length();
 575         if (length > m_charsBuff.length)
 576         {
 577             m_charsBuff = new char[length*2 + 1];
 578         }
 579         characters.getChars(0, length, m_charsBuff, 0);
 580         characters(m_charsBuff, 0, length);
 581     }
 582 
 583 
 584     /**
 585      * From XSLTC
 586      */
 587     public void addAttribute(String name, String value)
 588     {
 589         // do nothing, forget about the attribute
 590     }
 591 
 592     /**
 593      * Add a unique attribute
 594      */
 595     public void addUniqueAttribute(String qName, String value, int flags)
 596         throws SAXException
 597     {
 598         // do nothing, forget about the attribute
 599     }
 600 
 601     public boolean startPrefixMapping(
 602         String prefix,
 603         String uri,
 604         boolean shouldFlush)
 605         throws SAXException
 606     {
 607         // no namespace support for HTML
 608         return false;
 609     }
 610 
 611 
 612     public void startPrefixMapping(String prefix, String uri)
 613         throws org.xml.sax.SAXException
 614     {
 615         // no namespace support for HTML
 616     }
 617 
 618 
 619     public void namespaceAfterStartElement(
 620         final String prefix,
 621         final String uri)
 622         throws SAXException
 623     {
 624         // no namespace support for HTML
 625     }
 626 
 627     public void flushPending() throws org.xml.sax.SAXException
 628     {
 629             if (m_needToCallStartDocument)
 630             {
 631                 startDocumentInternal();
 632                 m_needToCallStartDocument = false;
 633             }
 634     }
 635 }