View Javadoc

1   /*
2    * Copyright 2002,2004 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.apache.commons.jelly;
18  
19  import java.io.IOException;
20  import java.io.OutputStream;
21  import java.io.UnsupportedEncodingException;
22  import java.io.Writer;
23  
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  import org.dom4j.io.XMLWriter;
27  import org.xml.sax.Attributes;
28  import org.xml.sax.ContentHandler;
29  import org.xml.sax.Locator;
30  import org.xml.sax.SAXException;
31  import org.xml.sax.XMLReader;
32  import org.xml.sax.ext.LexicalHandler;
33  import org.xml.sax.helpers.AttributesImpl;
34  import org.xml.sax.helpers.DefaultHandler;
35  
36  /*** <p><code>XMLOutput</code> is used to output XML events
37    * in a SAX-like manner. This also allows pipelining to be done
38    * such as in the <a href="http://xml.apache.org/cocoon/">Cocoon</a> project.</p>
39    *
40    * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
41    * @version $Revision: 1.18 $
42    */
43  
44  public class XMLOutput implements ContentHandler, LexicalHandler {
45  
46      protected static final String[] LEXICAL_HANDLER_NAMES =
47          {
48              "http://xml.org/sax/properties/lexical-handler",
49              "http://xml.org/sax/handlers/LexicalHandler" };
50  
51      /*** empty attributes */
52      private static final Attributes EMPTY_ATTRIBUTES = new AttributesImpl();
53  
54      /*** The SAX ContentHandler that output goes to */
55      private ContentHandler contentHandler;
56  
57      /*** The SAX LexicalHandler that output goes to */
58      private LexicalHandler lexicalHandler;
59  
60      /*** The Log to which logging calls will be made. */
61      private static final Log log = LogFactory.getLog(XMLOutput.class);
62  
63      /*** the default for escaping of text */
64      private static final boolean DEFAULT_ESCAPE_TEXT = false;
65  
66      public XMLOutput() {
67      }
68  
69      public XMLOutput(ContentHandler contentHandler) {
70          this.contentHandler = contentHandler;
71          // often classes will implement LexicalHandler as well
72          if (contentHandler instanceof LexicalHandler) {
73              this.lexicalHandler = (LexicalHandler) contentHandler;
74          }
75      }
76  
77      public XMLOutput(
78          ContentHandler contentHandler,
79          LexicalHandler lexicalHandler) {
80          this.contentHandler = contentHandler;
81          this.lexicalHandler = lexicalHandler;
82      }
83  
84      public String toString() {
85          return super.toString()
86              + "[contentHandler="
87              + contentHandler
88              + ";lexicalHandler="
89              + lexicalHandler
90              + "]";
91      }
92  
93      /***
94       * Provides a useful hook that implementations can use to close the
95       * underlying OutputStream or Writer
96       */
97      public void close() throws IOException {
98      }
99  
100     public void flush() throws IOException {
101         if( contentHandler instanceof XMLWriter )
102         {
103             ((XMLWriter)contentHandler).flush();
104         }
105     }
106 
107     // Static helper methods
108     //-------------------------------------------------------------------------
109 
110     /***
111      * Creates an XMLOutput from an existing SAX XMLReader
112      */
113     public static XMLOutput createXMLOutput(XMLReader xmlReader) {
114         XMLOutput output = new XMLOutput(xmlReader.getContentHandler());
115 
116         // isn't it lovely what we've got to do to find the LexicalHandler... ;-)
117         for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
118             try {
119                 Object value = xmlReader.getProperty(LEXICAL_HANDLER_NAMES[i]);
120                 if (value instanceof LexicalHandler) {
121                     output.setLexicalHandler((LexicalHandler) value);
122                     break;
123                 }
124             }
125             catch (Exception e) {
126                 // ignore any unsupported-operation exceptions
127                 if (log.isDebugEnabled()) log.debug("error setting lexical handler properties", e);
128             }
129         }
130         return output;
131     }
132 
133     /***
134      * Creates a text based XMLOutput which converts all XML events into
135      * text and writes to the underlying Writer.
136      */
137     public static XMLOutput createXMLOutput(Writer writer) {
138         return createXMLOutput(writer, DEFAULT_ESCAPE_TEXT);
139     }
140 
141     /***
142      * Creates a text based XMLOutput which converts all XML events into
143      * text and writes to the underlying Writer.
144      *
145      * @param writer is the writer to output to
146      * @param escapeText is whether or not text output will be escaped. This must be true
147      * if the underlying output is XML or could be false if the underlying output is textual.
148      */
149     public static XMLOutput createXMLOutput(Writer writer, boolean escapeText)
150     {
151         XMLWriter xmlWriter = new XMLWriter(writer);
152         xmlWriter.setEscapeText(escapeText);
153         return createXMLOutput(xmlWriter);
154     }
155 
156     /***
157      * Creates a text based XMLOutput which converts all XML events into
158      * text and writes to the underlying OutputStream.
159      */
160     public static XMLOutput createXMLOutput(OutputStream out) throws UnsupportedEncodingException {
161         return createXMLOutput(out, DEFAULT_ESCAPE_TEXT);
162     }
163 
164     /***
165      * Creates a text based XMLOutput which converts all XML events into
166      * text and writes to the underlying OutputStream.
167      *
168      * @param out is the output stream to write
169      * @param escapeText is whether or not text output will be escaped. This must be true
170      * if the underlying output is XML or could be false if the underlying output is textual.
171      */
172     public static XMLOutput createXMLOutput(OutputStream out, boolean escapeText) throws UnsupportedEncodingException {
173         XMLWriter xmlWriter = new XMLWriter(out);
174         xmlWriter.setEscapeText(escapeText);
175         return createXMLOutput(xmlWriter);
176     }
177 
178     /***
179      * returns an XMLOutput object that will discard all
180      * tag-generated XML events.  Useful when tag output is not expected
181      * or not significant.
182      *
183      * @return a no-op XMLOutput
184      */
185     public static XMLOutput createDummyXMLOutput() {
186         return new XMLOutput(new DefaultHandler());
187     }
188 
189     // Extra helper methods provided for tag authors
190     //-------------------------------------------------------------------------
191 
192     /***
193      * Outputs the given String as a piece of valid text in the
194      * XML event stream.
195      * Any special XML characters should be properly escaped.
196      */
197     public void write(String text) throws SAXException {
198         char[] ch = text.toCharArray();
199         characters(ch, 0, ch.length);
200     }
201 
202     /***
203      * Outputs the given String as a piece of CDATA in the
204      * XML event stream.
205      */
206     public void writeCDATA(String text) throws SAXException {
207         startCDATA();
208         char[] ch = text.toCharArray();
209         characters(ch, 0, ch.length);
210         endCDATA();
211     }
212 
213     /***
214      * Outputs a comment to the XML stream
215      */
216     public void writeComment(String text) throws SAXException {
217         char[] ch = text.toCharArray();
218         comment(ch, 0, ch.length);
219     }
220 
221     /***
222      * Helper method for outputting a start element event for an element in no namespace
223      */
224     public void startElement(String localName) throws SAXException {
225         startElement("", localName, localName, EMPTY_ATTRIBUTES);
226     }
227 
228     /***
229      * Helper method for outputting a start element event for an element in no namespace
230      */
231     public void startElement(String localName, Attributes attributes) throws SAXException {
232         startElement("", localName, localName, attributes);
233     }
234 
235     /***
236      * Helper method for outputting an end element event for an element in no namespace
237      */
238     public void endElement(String localName) throws SAXException {
239         endElement("", localName, localName);
240     }
241 
242 
243     // ContentHandler interface
244     //-------------------------------------------------------------------------
245 
246     /***
247      * Receive an object for locating the origin of SAX document events.
248      *
249      * <p>SAX parsers are strongly encouraged (though not absolutely
250      * required) to supply a locator: if it does so, it must supply
251      * the locator to the application by invoking this method before
252      * invoking any of the other methods in the ContentHandler
253      * interface.</p>
254      *
255      * <p>The locator allows the application to determine the end
256      * position of any document-related event, even if the parser is
257      * not reporting an error.  Typically, the application will
258      * use this information for reporting its own errors (such as
259      * character content that does not match an application's
260      * business rules).  The information returned by the locator
261      * is probably not sufficient for use with a search engine.</p>
262      *
263      * <p>Note that the locator will return correct information only
264      * during the invocation of the events in this interface.  The
265      * application should not attempt to use it at any other time.</p>
266      *
267      * @param locator An object that can return the location of
268      *                any SAX document event.
269      * @see org.xml.sax.Locator
270      */
271     public void setDocumentLocator(Locator locator) {
272         contentHandler.setDocumentLocator(locator);
273     }
274 
275     /***
276      * Receive notification of the beginning of a document.
277      *
278      * <p>The SAX parser will invoke this method only once, before any
279      * other event callbacks (except for {@link #setDocumentLocator
280      * setDocumentLocator}).</p>
281      *
282      * @exception org.xml.sax.SAXException Any SAX exception, possibly
283      *            wrapping another exception.
284      * @see #endDocument
285      */
286     public void startDocument() throws SAXException {
287         contentHandler.startDocument();
288     }
289 
290     /***
291      * Receive notification of the end of a document.
292      *
293      * <p>The SAX parser will invoke this method only once, and it will
294      * be the last method invoked during the parse.  The parser shall
295      * not invoke this method until it has either abandoned parsing
296      * (because of an unrecoverable error) or reached the end of
297      * input.</p>
298      *
299      * @exception org.xml.sax.SAXException Any SAX exception, possibly
300      *            wrapping another exception.
301      * @see #startDocument
302      */
303     public void endDocument() throws SAXException {
304         contentHandler.endDocument();
305     }
306 
307     /***
308      * Begin the scope of a prefix-URI Namespace mapping.
309      *
310      * <p>The information from this event is not necessary for
311      * normal Namespace processing: the SAX XML reader will
312      * automatically replace prefixes for element and attribute
313      * names when the <code>http://xml.org/sax/features/namespaces</code>
314      * feature is <var>true</var> (the default).</p>
315      *
316      * <p>There are cases, however, when applications need to
317      * use prefixes in character data or in attribute values,
318      * where they cannot safely be expanded automatically; the
319      * start/endPrefixMapping event supplies the information
320      * to the application to expand prefixes in those contexts
321      * itself, if necessary.</p>
322      *
323      * <p>Note that start/endPrefixMapping events are not
324      * guaranteed to be properly nested relative to each other:
325      * all startPrefixMapping events will occur immediately before the
326      * corresponding {@link #startElement startElement} event,
327      * and all {@link #endPrefixMapping endPrefixMapping}
328      * events will occur immediately after the corresponding
329      * {@link #endElement endElement} event,
330      * but their order is not otherwise
331      * guaranteed.</p>
332      *
333      * <p>There should never be start/endPrefixMapping events for the
334      * "xml" prefix, since it is predeclared and immutable.</p>
335      *
336      * @param prefix The Namespace prefix being declared.
337      *  An empty string is used for the default element namespace,
338      *  which has no prefix.
339      * @param uri The Namespace URI the prefix is mapped to.
340      * @exception org.xml.sax.SAXException The client may throw
341      *            an exception during processing.
342      * @see #endPrefixMapping
343      * @see #startElement
344      */
345     public void startPrefixMapping(String prefix, String uri) throws SAXException {
346         contentHandler.startPrefixMapping(prefix, uri);
347     }
348 
349     /***
350      * End the scope of a prefix-URI mapping.
351      *
352      * <p>See {@link #startPrefixMapping startPrefixMapping} for
353      * details.  These events will always occur immediately after the
354      * corresponding {@link #endElement endElement} event, but the order of
355      * {@link #endPrefixMapping endPrefixMapping} events is not otherwise
356      * guaranteed.</p>
357      *
358      * @param prefix The prefix that was being mapped.
359      *  This is the empty string when a default mapping scope ends.
360      * @exception org.xml.sax.SAXException The client may throw
361      *            an exception during processing.
362      * @see #startPrefixMapping
363      * @see #endElement
364      */
365     public void endPrefixMapping(String prefix) throws SAXException {
366         contentHandler.endPrefixMapping(prefix);
367     }
368 
369     /***
370      * Receive notification of the beginning of an element.
371      *
372      * <p>The Parser will invoke this method at the beginning of every
373      * element in the XML document; there will be a corresponding
374      * {@link #endElement endElement} event for every startElement event
375      * (even when the element is empty). All of the element's content will be
376      * reported, in order, before the corresponding endElement
377      * event.</p>
378      *
379      * <p>This event allows up to three name components for each
380      * element:</p>
381      *
382      * <ol>
383      * <li>the Namespace URI;</li>
384      * <li>the local name; and</li>
385      * <li>the qualified (prefixed) name.</li>
386      * </ol>
387      *
388      * <p>Any or all of these may be provided, depending on the
389      * values of the <var>http://xml.org/sax/features/namespaces</var>
390      * and the <var>http://xml.org/sax/features/namespace-prefixes</var>
391      * properties:</p>
392      *
393      * <ul>
394      * <li>the Namespace URI and local name are required when
395      * the namespaces property is <var>true</var> (the default), and are
396      * optional when the namespaces property is <var>false</var> (if one is
397      * specified, both must be);</li>
398      * <li>the qualified name is required when the namespace-prefixes property
399      * is <var>true</var>, and is optional when the namespace-prefixes property
400      * is <var>false</var> (the default).</li>
401      * </ul>
402      *
403      * <p>Note that the attribute list provided will contain only
404      * attributes with explicit values (specified or defaulted):
405      * #IMPLIED attributes will be omitted.  The attribute list
406      * will contain attributes used for Namespace declarations
407      * (xmlns* attributes) only if the
408      * <code>http://xml.org/sax/features/namespace-prefixes</code>
409      * property is true (it is false by default, and support for a
410      * true value is optional).</p>
411      *
412      * <p>Like {@link #characters characters()}, attribute values may have
413      * characters that need more than one <code>char</code> value.  </p>
414      *
415      * @param uri The Namespace URI, or the empty string if the
416      *        element has no Namespace URI or if Namespace
417      *        processing is not being performed.
418      * @param localName The local name (without prefix), or the
419      *        empty string if Namespace processing is not being
420      *        performed.
421      * @param qName The qualified name (with prefix), or the
422      *        empty string if qualified names are not available.
423      * @param atts The attributes attached to the element.  If
424      *        there are no attributes, it shall be an empty
425      *        Attributes object.
426      * @exception org.xml.sax.SAXException Any SAX exception, possibly
427      *            wrapping another exception.
428      * @see #endElement
429      * @see org.xml.sax.Attributes
430      */
431     public void startElement(
432         String uri,
433         String localName,
434         String qName,
435         Attributes atts)
436         throws SAXException {
437         contentHandler.startElement(uri, localName, qName, atts);
438     }
439 
440     /***
441      * Receive notification of the end of an element.
442      *
443      * <p>The SAX parser will invoke this method at the end of every
444      * element in the XML document; there will be a corresponding
445      * {@link #startElement startElement} event for every endElement
446      * event (even when the element is empty).</p>
447      *
448      * <p>For information on the names, see startElement.</p>
449      *
450      * @param uri The Namespace URI, or the empty string if the
451      *        element has no Namespace URI or if Namespace
452      *        processing is not being performed.
453      * @param localName The local name (without prefix), or the
454      *        empty string if Namespace processing is not being
455      *        performed.
456      * @param qName The qualified XML 1.0 name (with prefix), or the
457      *        empty string if qualified names are not available.
458      * @exception org.xml.sax.SAXException Any SAX exception, possibly
459      *            wrapping another exception.
460      */
461     public void endElement(String uri, String localName, String qName)
462         throws SAXException {
463         contentHandler.endElement(uri, localName, qName);
464     }
465 
466     /***
467      * Receive notification of character data.
468      *
469      * <p>The Parser will call this method to report each chunk of
470      * character data.  SAX parsers may return all contiguous character
471      * data in a single chunk, or they may split it into several
472      * chunks; however, all of the characters in any single event
473      * must come from the same external entity so that the Locator
474      * provides useful information.</p>
475      *
476      * <p>The application must not attempt to read from the array
477      * outside of the specified range.</p>
478      *
479      * <p>Individual characters may consist of more than one Java
480      * <code>char</code> value.  There are two important cases where this
481      * happens, because characters can't be represented in just sixteen bits.
482      * In one case, characters are represented in a <em>Surrogate Pair</em>,
483      * using two special Unicode values. Such characters are in the so-called
484      * "Astral Planes", with a code point above U+FFFF.  A second case involves
485      * composite characters, such as a base character combining with one or
486      * more accent characters. </p>
487      *
488      * <p> Your code should not assume that algorithms using
489      * <code>char</code>-at-a-time idioms will be working in character
490      * units; in some cases they will split characters.  This is relevant
491      * wherever XML permits arbitrary characters, such as attribute values,
492      * processing instruction data, and comments as well as in data reported
493      * from this method.  It's also generally relevant whenever Java code
494      * manipulates internationalized text; the issue isn't unique to XML.</p>
495      *
496      * <p>Note that some parsers will report whitespace in element
497      * content using the {@link #ignorableWhitespace ignorableWhitespace}
498      * method rather than this one (validating parsers <em>must</em>
499      * do so).</p>
500      *
501      * @param ch The characters from the XML document.
502      * @param start The start position in the array.
503      * @param length The number of characters to read from the array.
504      * @exception org.xml.sax.SAXException Any SAX exception, possibly
505      *            wrapping another exception.
506      * @see #ignorableWhitespace
507      * @see org.xml.sax.Locator
508      */
509     public void characters(char ch[], int start, int length) throws SAXException {
510         contentHandler.characters(ch, start, length);
511     }
512 
513     /***
514      * Receive notification of ignorable whitespace in element content.
515      *
516      * <p>Validating Parsers must use this method to report each chunk
517      * of whitespace in element content (see the W3C XML 1.0 recommendation,
518      * section 2.10): non-validating parsers may also use this method
519      * if they are capable of parsing and using content models.</p>
520      *
521      * <p>SAX parsers may return all contiguous whitespace in a single
522      * chunk, or they may split it into several chunks; however, all of
523      * the characters in any single event must come from the same
524      * external entity, so that the Locator provides useful
525      * information.</p>
526      *
527      * <p>The application must not attempt to read from the array
528      * outside of the specified range.</p>
529      *
530      * @param ch The characters from the XML document.
531      * @param start The start position in the array.
532      * @param length The number of characters to read from the array.
533      * @exception org.xml.sax.SAXException Any SAX exception, possibly
534      *            wrapping another exception.
535      * @see #characters
536      */
537     public void ignorableWhitespace(char ch[], int start, int length)
538         throws SAXException {
539         contentHandler.ignorableWhitespace(ch, start, length);
540     }
541 
542     /***
543      * Receive notification of a processing instruction.
544      *
545      * <p>The Parser will invoke this method once for each processing
546      * instruction found: note that processing instructions may occur
547      * before or after the main document element.</p>
548      *
549      * <p>A SAX parser must never report an XML declaration (XML 1.0,
550      * section 2.8) or a text declaration (XML 1.0, section 4.3.1)
551      * using this method.</p>
552      *
553      * <p>Like {@link #characters characters()}, processing instruction
554      * data may have characters that need more than one <code>char</code>
555      * value. </p>
556      *
557      * @param target The processing instruction target.
558      * @param data The processing instruction data, or null if
559      *        none was supplied.  The data does not include any
560      *        whitespace separating it from the target.
561      * @exception org.xml.sax.SAXException Any SAX exception, possibly
562      *            wrapping another exception.
563      */
564     public void processingInstruction(String target, String data)
565         throws SAXException {
566         contentHandler.processingInstruction(target, data);
567     }
568 
569     /***
570      * Receive notification of a skipped entity.
571      * This is not called for entity references within markup constructs
572      * such as element start tags or markup declarations.  (The XML
573      * recommendation requires reporting skipped external entities.
574      * SAX also reports internal entity expansion/non-expansion, except
575      * within markup constructs.)
576      *
577      * <p>The Parser will invoke this method each time the entity is
578      * skipped.  Non-validating processors may skip entities if they
579      * have not seen the declarations (because, for example, the
580      * entity was declared in an external DTD subset).  All processors
581      * may skip external entities, depending on the values of the
582      * <code>http://xml.org/sax/features/external-general-entities</code>
583      * and the
584      * <code>http://xml.org/sax/features/external-parameter-entities</code>
585      * properties.</p>
586      *
587      * @param name The name of the skipped entity.  If it is a
588      *        parameter entity, the name will begin with '%', and if
589      *        it is the external DTD subset, it will be the string
590      *        "[dtd]".
591      * @exception org.xml.sax.SAXException Any SAX exception, possibly
592      *            wrapping another exception.
593      */
594     public void skippedEntity(String name) throws SAXException {
595         contentHandler.skippedEntity(name);
596     }
597 
598 
599     // Lexical Handler interface
600     //-------------------------------------------------------------------------
601 
602     /***
603      * Report the start of DTD declarations, if any.
604      *
605      * <p>This method is intended to report the beginning of the
606      * DOCTYPE declaration; if the document has no DOCTYPE declaration,
607      * this method will not be invoked.</p>
608      *
609      * <p>All declarations reported through
610      * {@link org.xml.sax.DTDHandler DTDHandler} or
611      * {@link org.xml.sax.ext.DeclHandler DeclHandler} events must appear
612      * between the startDTD and {@link #endDTD endDTD} events.
613      * Declarations are assumed to belong to the internal DTD subset
614      * unless they appear between {@link #startEntity startEntity}
615      * and {@link #endEntity endEntity} events.  Comments and
616      * processing instructions from the DTD should also be reported
617      * between the startDTD and endDTD events, in their original
618      * order of (logical) occurrence; they are not required to
619      * appear in their correct locations relative to DTDHandler
620      * or DeclHandler events, however.</p>
621      *
622      * <p>Note that the start/endDTD events will appear within
623      * the start/endDocument events from ContentHandler and
624      * before the first
625      * {@link org.xml.sax.ContentHandler#startElement startElement}
626      * event.</p>
627      *
628      * @param name The document type name.
629      * @param publicId The declared public identifier for the
630      *        external DTD subset, or null if none was declared.
631      * @param systemId The declared system identifier for the
632      *        external DTD subset, or null if none was declared.
633      *        (Note that this is not resolved against the document
634      *        base URI.)
635      * @exception SAXException The application may raise an
636      *            exception.
637      * @see #endDTD
638      * @see #startEntity
639      */
640     public void startDTD(String name, String publicId, String systemId)
641         throws SAXException {
642         if (lexicalHandler != null) {
643             lexicalHandler.startDTD(name, publicId, systemId);
644         }
645     }
646 
647     /***
648      * Report the end of DTD declarations.
649      *
650      * <p>This method is intended to report the end of the
651      * DOCTYPE declaration; if the document has no DOCTYPE declaration,
652      * this method will not be invoked.</p>
653      *
654      * @exception SAXException The application may raise an exception.
655      * @see #startDTD
656      */
657     public void endDTD() throws SAXException {
658         if (lexicalHandler != null) {
659             lexicalHandler.endDTD();
660         }
661     }
662 
663     /***
664      * Report the beginning of some internal and external XML entities.
665      *
666      * <p>The reporting of parameter entities (including
667      * the external DTD subset) is optional, and SAX2 drivers that
668      * report LexicalHandler events may not implement it; you can use the
669      * <code
670      * >http://xml.org/sax/features/lexical-handler/parameter-entities</code>
671      * feature to query or control the reporting of parameter entities.</p>
672      *
673      * <p>General entities are reported with their regular names,
674      * parameter entities have '%' prepended to their names, and
675      * the external DTD subset has the pseudo-entity name "[dtd]".</p>
676      *
677      * <p>When a SAX2 driver is providing these events, all other
678      * events must be properly nested within start/end entity
679      * events.  There is no additional requirement that events from
680      * {@link org.xml.sax.ext.DeclHandler DeclHandler} or
681      * {@link org.xml.sax.DTDHandler DTDHandler} be properly ordered.</p>
682      *
683      * <p>Note that skipped entities will be reported through the
684      * {@link org.xml.sax.ContentHandler#skippedEntity skippedEntity}
685      * event, which is part of the ContentHandler interface.</p>
686      *
687      * <p>Because of the streaming event model that SAX uses, some
688      * entity boundaries cannot be reported under any
689      * circumstances:</p>
690      *
691      * <ul>
692      * <li>general entities within attribute values</li>
693      * <li>parameter entities within declarations</li>
694      * </ul>
695      *
696      * <p>These will be silently expanded, with no indication of where
697      * the original entity boundaries were.</p>
698      *
699      * <p>Note also that the boundaries of character references (which
700      * are not really entities anyway) are not reported.</p>
701      *
702      * <p>All start/endEntity events must be properly nested.
703      *
704      * @param name The name of the entity.  If it is a parameter
705      *        entity, the name will begin with '%', and if it is the
706      *        external DTD subset, it will be "[dtd]".
707      * @exception SAXException The application may raise an exception.
708      * @see #endEntity
709      * @see org.xml.sax.ext.DeclHandler#internalEntityDecl
710      * @see org.xml.sax.ext.DeclHandler#externalEntityDecl
711      */
712     public void startEntity(String name) throws SAXException {
713         if (lexicalHandler != null) {
714             lexicalHandler.startEntity(name);
715         }
716     }
717 
718     /***
719      * Report the end of an entity.
720      *
721      * @param name The name of the entity that is ending.
722      * @exception SAXException The application may raise an exception.
723      * @see #startEntity
724      */
725     public void endEntity(String name) throws SAXException {
726         if (lexicalHandler != null) {
727             lexicalHandler.endEntity(name);
728         }
729     }
730 
731     /***
732      * Report the start of a CDATA section.
733      *
734      * <p>The contents of the CDATA section will be reported through
735      * the regular {@link org.xml.sax.ContentHandler#characters
736      * characters} event; this event is intended only to report
737      * the boundary.</p>
738      *
739      * @exception SAXException The application may raise an exception.
740      * @see #endCDATA
741      */
742     public void startCDATA() throws SAXException {
743         if (lexicalHandler != null) {
744             lexicalHandler.startCDATA();
745         }
746     }
747 
748     /***
749      * Report the end of a CDATA section.
750      *
751      * @exception SAXException The application may raise an exception.
752      * @see #startCDATA
753      */
754     public void endCDATA() throws SAXException {
755         if (lexicalHandler != null) {
756             lexicalHandler.endCDATA();
757         }
758     }
759 
760     /***
761      * Report an XML comment anywhere in the document.
762      *
763      * <p>This callback will be used for comments inside or outside the
764      * document element, including comments in the external DTD
765      * subset (if read).  Comments in the DTD must be properly
766      * nested inside start/endDTD and start/endEntity events (if
767      * used).</p>
768      *
769      * @param ch An array holding the characters in the comment.
770      * @param start The starting position in the array.
771      * @param length The number of characters to use from the array.
772      * @exception SAXException The application may raise an exception.
773      */
774     public void comment(char ch[], int start, int length) throws SAXException {
775         if (lexicalHandler != null) {
776             lexicalHandler.comment(ch, start, length);
777         }
778     }
779 
780     // Properties
781     //-------------------------------------------------------------------------
782     /***
783      * @return the SAX ContentHandler to use to pipe SAX events into
784      */
785     public ContentHandler getContentHandler() {
786         return contentHandler;
787     }
788 
789     /***
790      * Sets the SAX ContentHandler to pipe SAX events into
791      *
792      * @param contentHandler is the new ContentHandler to use.
793      *      This value cannot be null.
794      */
795     public void setContentHandler(ContentHandler contentHandler) {
796         if (contentHandler == null) {
797             throw new NullPointerException("ContentHandler cannot be null!");
798         }
799         this.contentHandler = contentHandler;
800     }
801 
802     /***
803      * @return the SAX LexicalHandler to use to pipe SAX events into
804      */
805     public LexicalHandler getLexicalHandler() {
806         return lexicalHandler;
807     }
808 
809     /***
810      * Sets the SAX LexicalHandler to pipe SAX events into
811      *
812      * @param lexicalHandler is the new LexicalHandler to use.
813      *      This value can be null.
814      */
815     public void setLexicalHandler(LexicalHandler lexicalHandler) {
816         this.lexicalHandler = lexicalHandler;
817     }
818 
819     // Implementation methods
820     //-------------------------------------------------------------------------
821     /***
822      * Factory method to create a new XMLOutput from an XMLWriter
823      */
824     protected static XMLOutput createXMLOutput(final XMLWriter xmlWriter) {
825         XMLOutput answer = new XMLOutput() {
826             public void close() throws IOException {
827                 xmlWriter.close();
828             }
829         };
830         answer.setContentHandler(xmlWriter);
831         answer.setLexicalHandler(xmlWriter);
832         return answer;
833     }
834 
835 }