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