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