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