001: /*--
002:
003: Copyright (C) 2000 Brett McLaughlin & Jason Hunter.
004: All rights reserved.
005:
006: Redistribution and use in source and binary forms, with or without
007: modification, are permitted provided that the following conditions
008: are met:
009:
010: 1. Redistributions of source code must retain the above copyright
011: notice, this list of conditions, and the following disclaimer.
012:
013: 2. Redistributions in binary form must reproduce the above copyright
014: notice, this list of conditions, and the disclaimer that follows
015: these conditions in the documentation and/or other materials
016: provided with the distribution.
017:
018: 3. The name "JDOM" must not be used to endorse or promote products
019: derived from this software without prior written permission. For
020: written permission, please contact license@jdom.org.
021:
022: 4. Products derived from this software may not be called "JDOM", nor
023: may "JDOM" appear in their name, without prior written permission
024: from the JDOM Project Management (pm@jdom.org).
025:
026: In addition, we request (but do not require) that you include in the
027: end-user documentation provided with the redistribution and/or in the
028: software itself an acknowledgement equivalent to the following:
029: "This product includes software developed by the
030: JDOM Project (http://www.jdom.org/)."
031: Alternatively, the acknowledgment may be graphical using the logos
032: available at http://www.jdom.org/images/logos.
033:
034: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
035: WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
036: OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
037: DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT
038: CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
039: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
040: LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
041: USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
042: ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
043: OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
044: OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
045: SUCH DAMAGE.
046:
047: This software consists of voluntary contributions made by many
048: individuals on behalf of the JDOM Project and was originally
049: created by Brett McLaughlin <brett@jdom.org> and
050: Jason Hunter <jhunter@jdom.org>. For more information on the
051: JDOM Project, please see <http://www.jdom.org/>.
052:
053: */
054: package sax;
055:
056: import java.io.IOException;
057:
058: import org.xml.sax.Attributes;
059: import org.xml.sax.InputSource;
060: import org.xml.sax.SAXException;
061: import org.xml.sax.SAXNotRecognizedException;
062: import org.xml.sax.SAXNotSupportedException;
063: import org.xml.sax.XMLReader;
064: import org.xml.sax.ext.LexicalHandler;
065: import org.xml.sax.helpers.AttributesImpl;
066: import org.xml.sax.helpers.XMLFilterImpl;
067:
068: /**
069: * Adds convenience methods and lexical event filtering to base
070: * SAX2 Filter implementation.
071: *
072: * <i>Code and comments adapted from XMLWriter-0.2, written
073: * by David Megginson and released into the public domain,
074: * without warranty.</i>
075: *
076: * <p>The convenience methods are provided so that clients do not have to
077: * create empty attribute lists or provide empty strings as parameters;
078: * for example, the method invocation</p>
079: *
080: * <pre>
081: * w.startElement("foo");
082: * </pre>
083: *
084: * <p>is equivalent to the regular SAX2 ContentHandler method</p>
085: *
086: * <pre>
087: * w.startElement("", "foo", "", new AttributesImpl());
088: * </pre>
089: *
090: * <p>Except that it is more efficient because it does not allocate
091: * a new empty attribute list each time.</p>
092: *
093: * <p>In fact, there is an even simpler convenience method,
094: * <var>dataElement</var>, designed for writing elements that
095: * contain only character data.</p>
096: *
097: * <pre>
098: * w.dataElement("greeting", "Hello, world!");
099: * </pre>
100: *
101: * <p>is equivalent to</p>
102: *
103: * <pre>
104: * w.startElement("greeting");
105: * w.characters("Hello, world!");
106: * w.endElement("greeting");
107: * </pre>
108: *
109: * @see org.xml.sax.helpers.XMLFilterImpl
110: */
111: public class XMLFilterBase extends XMLFilterImpl implements
112: LexicalHandler {
113:
114: ////////////////////////////////////////////////////////////////////
115: // Constructors.
116: ////////////////////////////////////////////////////////////////////
117:
118: /**
119: * Construct an XML filter with no parent.
120: *
121: * <p>This filter will have no parent: you must assign a parent
122: * before you start a parse or do any configuration with
123: * setFeature or setProperty.</p>
124: *
125: * @see org.xml.sax.XMLReader#setFeature
126: * @see org.xml.sax.XMLReader#setProperty
127: */
128: public XMLFilterBase() {
129: }
130:
131: /**
132: * Create an XML filter with the specified parent.
133: *
134: * <p>Use the XMLReader provided as the source of events.</p>
135: *
136: * @param xmlreader The parent in the filter chain.
137: */
138: public XMLFilterBase(XMLReader parent) {
139: super (parent);
140: }
141:
142: ////////////////////////////////////////////////////////////////////
143: // Convenience methods.
144: ////////////////////////////////////////////////////////////////////
145:
146: /**
147: * Start a new element without a qname or attributes.
148: *
149: * <p>This method will provide a default empty attribute
150: * list and an empty string for the qualified name. It invokes
151: * {@link #startElement(String, String, String, Attributes)}
152: * directly.</p>
153: *
154: * @param uri The element's Namespace URI.
155: * @param localName The element's local name.
156: * @exception org.xml.sax.SAXException If a filter
157: * further down the chain raises an exception.
158: * @see org.xml.sax.ContentHandler#startElement
159: */
160: public void startElement(String uri, String localName)
161: throws SAXException {
162: startElement(uri, localName, "", EMPTY_ATTS);
163: }
164:
165: /**
166: * Start a new element without a Namespace URI or qname.
167: *
168: * <p>This method will provide an empty string for the
169: * Namespace URI, and empty string for the qualified name.
170: * It invokes
171: * {@link #startElement(String, String, String, Attributes)}
172: * directly.</p>
173: *
174: * @param localName The element's local name.
175: * @param atts The element's attribute list.
176: * @exception org.xml.sax.SAXException If a filter
177: * further down the chain raises an exception.
178: * @see org.xml.sax.ContentHandler#startElement
179: */
180: public void startElement(String localName, Attributes atts)
181: throws SAXException {
182: startElement("", localName, "", atts);
183: }
184:
185: /**
186: * Start a new element without a Namespace URI, qname, or attributes.
187: *
188: * <p>This method will provide an empty string for the
189: * Namespace URI, and empty string for the qualified name,
190: * and a default empty attribute list. It invokes
191: * {@link #startElement(String, String, String, Attributes)}
192: * directly.</p>
193: *
194: * @param localName The element's local name.
195: * @exception org.xml.sax.SAXException If a filter
196: * further down the chain raises an exception.
197: * @see org.xml.sax.ContentHandler#startElement
198: */
199: public void startElement(String localName) throws SAXException {
200: startElement("", localName, "", EMPTY_ATTS);
201: }
202:
203: /**
204: * End an element without a qname.
205: *
206: * <p>This method will supply an empty string for the qName.
207: * It invokes {@link #endElement(String, String, String)}
208: * directly.</p>
209: *
210: * @param uri The element's Namespace URI.
211: * @param localName The element's local name.
212: * @exception org.xml.sax.SAXException If a filter
213: * further down the chain raises an exception.
214: * @see org.xml.sax.ContentHandler#endElement
215: */
216: public void endElement(String uri, String localName)
217: throws SAXException {
218: endElement(uri, localName, "");
219: }
220:
221: /**
222: * End an element without a Namespace URI or qname.
223: *
224: * <p>This method will supply an empty string for the qName
225: * and an empty string for the Namespace URI.
226: * It invokes {@link #endElement(String, String, String)}
227: * directly.</p>
228: *
229: * @param localName The element's local name.
230: * @exception org.xml.sax.SAXException If a filter
231: * further down the chain raises an exception.
232: * @see org.xml.sax.ContentHandler#endElement
233: */
234: public void endElement(String localName) throws SAXException {
235: endElement("", localName, "");
236: }
237:
238: /**
239: * Add an empty element.
240: *
241: * Both a {@link #startElement startElement} and an
242: * {@link #endElement endElement} event will be passed on down
243: * the filter chain.
244: *
245: * @param uri The element's Namespace URI, or the empty string
246: * if the element has no Namespace or if Namespace
247: * processing is not being performed.
248: * @param localName The element's local name (without prefix). This
249: * parameter must be provided.
250: * @param qName The element's qualified name (with prefix), or
251: * the empty string if none is available. This parameter
252: * is strictly advisory: the writer may or may not use
253: * the prefix attached.
254: * @param atts The element's attribute list.
255: * @exception org.xml.sax.SAXException If a filter
256: * further down the chain raises an exception.
257: * @see org.xml.sax.ContentHandler#startElement
258: * @see org.xml.sax.ContentHandler#endElement
259: */
260: public void emptyElement(String uri, String localName,
261: String qName, Attributes atts) throws SAXException {
262: startElement(uri, localName, qName, atts);
263: endElement(uri, localName, qName);
264: }
265:
266: /**
267: * Add an empty element without a qname or attributes.
268: *
269: * <p>This method will supply an empty string for the qname
270: * and an empty attribute list. It invokes
271: * {@link #emptyElement(String, String, String, Attributes)}
272: * directly.</p>
273: *
274: * @param uri The element's Namespace URI.
275: * @param localName The element's local name.
276: * @exception org.xml.sax.SAXException If a filter
277: * further down the chain raises an exception.
278: * @see #emptyElement(String, String, String, Attributes)
279: */
280: public void emptyElement(String uri, String localName)
281: throws SAXException {
282: emptyElement(uri, localName, "", EMPTY_ATTS);
283: }
284:
285: /**
286: * Add an empty element without a Namespace URI or qname.
287: *
288: * <p>This method will provide an empty string for the
289: * Namespace URI, and empty string for the qualified name.
290: * It invokes
291: * {@link #emptyElement(String, String, String, Attributes)}
292: * directly.</p>
293: *
294: * @param localName The element's local name.
295: * @param atts The element's attribute list.
296: * @exception org.xml.sax.SAXException If a filter
297: * further down the chain raises an exception.
298: * @see org.xml.sax.ContentHandler#startElement
299: */
300: public void emptyElement(String localName, Attributes atts)
301: throws SAXException {
302: emptyElement("", localName, "", atts);
303: }
304:
305: /**
306: * Add an empty element without a Namespace URI, qname or attributes.
307: *
308: * <p>This method will supply an empty string for the qname,
309: * and empty string for the Namespace URI, and an empty
310: * attribute list. It invokes
311: * {@link #emptyElement(String, String, String, Attributes)}
312: * directly.</p>
313: *
314: * @param localName The element's local name.
315: * @exception org.xml.sax.SAXException If a filter
316: * further down the chain raises an exception.
317: * @see #emptyElement(String, String, String, Attributes)
318: */
319: public void emptyElement(String localName) throws SAXException {
320: emptyElement("", localName, "", EMPTY_ATTS);
321: }
322:
323: /**
324: * Add an element with character data content.
325: *
326: * <p>This is a convenience method to add a complete element
327: * with character data content, including the start tag
328: * and end tag.</p>
329: *
330: * <p>This method invokes
331: * {@link @see org.xml.sax.ContentHandler#startElement},
332: * followed by
333: * {@link #characters(String)}, followed by
334: * {@link @see org.xml.sax.ContentHandler#endElement}.</p>
335: *
336: * @param uri The element's Namespace URI.
337: * @param localName The element's local name.
338: * @param qName The element's default qualified name.
339: * @param atts The element's attributes.
340: * @param content The character data content.
341: * @exception org.xml.sax.SAXException If a filter
342: * further down the chain raises an exception.
343: * @see org.xml.sax.ContentHandler#startElement
344: * @see #characters(String)
345: * @see org.xml.sax.ContentHandler#endElement
346: */
347: public void dataElement(String uri, String localName, String qName,
348: Attributes atts, String content) throws SAXException {
349: startElement(uri, localName, qName, atts);
350: characters(content);
351: endElement(uri, localName, qName);
352: }
353:
354: /**
355: * Add an element with character data content but no qname or attributes.
356: *
357: * <p>This is a convenience method to add a complete element
358: * with character data content, including the start tag
359: * and end tag. This method provides an empty string
360: * for the qname and an empty attribute list. It invokes
361: * {@link #dataElement(String, String, String, Attributes, String)}}
362: * directly.</p>
363: *
364: * @param uri The element's Namespace URI.
365: * @param localName The element's local name.
366: * @param content The character data content.
367: * @exception org.xml.sax.SAXException If a filter
368: * further down the chain raises an exception.
369: * @see org.xml.sax.ContentHandler#startElement
370: * @see #characters(String)
371: * @see org.xml.sax.ContentHandler#endElement
372: */
373: public void dataElement(String uri, String localName, String content)
374: throws SAXException {
375: dataElement(uri, localName, "", EMPTY_ATTS, content);
376: }
377:
378: /**
379: * Add an element with character data content but no Namespace URI or qname.
380: *
381: * <p>This is a convenience method to add a complete element
382: * with character data content, including the start tag
383: * and end tag. The method provides an empty string for the
384: * Namespace URI, and empty string for the qualified name. It invokes
385: * {@link #dataElement(String, String, String, Attributes, String)}}
386: * directly.</p>
387: *
388: * @param localName The element's local name.
389: * @param atts The element's attributes.
390: * @param content The character data content.
391: * @exception org.xml.sax.SAXException If a filter
392: * further down the chain raises an exception.
393: * @see org.xml.sax.ContentHandler#startElement
394: * @see #characters(String)
395: * @see org.xml.sax.ContentHandler#endElement
396: */
397: public void dataElement(String localName, Attributes atts,
398: String content) throws SAXException {
399: dataElement("", localName, "", atts, content);
400: }
401:
402: /**
403: * Add an element with character data content but no attributes
404: * or Namespace URI.
405: *
406: * <p>This is a convenience method to add a complete element
407: * with character data content, including the start tag
408: * and end tag. The method provides an empty string for the
409: * Namespace URI, and empty string for the qualified name,
410: * and an empty attribute list. It invokes
411: * {@link #dataElement(String, String, String, Attributes, String)}}
412: * directly.</p>
413: *
414: * @param localName The element's local name.
415: * @param content The character data content.
416: * @exception org.xml.sax.SAXException If a filter
417: * further down the chain raises an exception.
418: * @see org.xml.sax.ContentHandler#startElement
419: * @see #characters(String)
420: * @see org.xml.sax.ContentHandler#endElement
421: */
422: public void dataElement(String localName, String content)
423: throws SAXException {
424: dataElement("", localName, "", EMPTY_ATTS, content);
425: }
426:
427: /**
428: * Add a string of character data, with XML escaping.
429: *
430: * <p>This is a convenience method that takes an XML
431: * String, converts it to a character array, then invokes
432: * {@link @see org.xml.sax.ContentHandler#characters}.</p>
433: *
434: * @param data The character data.
435: * @exception org.xml.sax.SAXException If a filter
436: * further down the chain raises an exception.
437: * @see @see org.xml.sax.ContentHandler#characters
438: */
439: public void characters(String data) throws SAXException {
440: char ch[] = data.toCharArray();
441: characters(ch, 0, ch.length);
442: }
443:
444: ////////////////////////////////////////////////////////////////////
445: // Override org.xml.sax.helpers.XMLFilterImpl methods.
446: ////////////////////////////////////////////////////////////////////
447:
448: /**
449: * Set the value of a property.
450: *
451: * <p>This will always fail if the parent is null.</p>
452: *
453: * @param name The property name.
454: * @param state The requested property value.
455: * @exception org.xml.sax.SAXNotRecognizedException When the
456: * XMLReader does not recognize the property name.
457: * @exception org.xml.sax.SAXNotSupportedException When the
458: * XMLReader recognizes the property name but
459: * cannot set the requested value.
460: * @see org.xml.sax.XMLReader#setProperty
461: */
462: public void setProperty(String name, Object value)
463: throws SAXNotRecognizedException, SAXNotSupportedException {
464: for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
465: if (LEXICAL_HANDLER_NAMES[i].equals(name)) {
466: setLexicalHandler((LexicalHandler) value);
467: return;
468: }
469: }
470: super .setProperty(name, value);
471: }
472:
473: /**
474: * Look up the value of a property.
475: *
476: * @param name The property name.
477: * @return The current value of the property.
478: * @exception org.xml.sax.SAXNotRecognizedException When the
479: * XMLReader does not recognize the feature name.
480: * @exception org.xml.sax.SAXNotSupportedException When the
481: * XMLReader recognizes the property name but
482: * cannot determine its value at this time.
483: * @see org.xml.sax.XMLReader#setFeature
484: */
485: public Object getProperty(String name)
486: throws SAXNotRecognizedException, SAXNotSupportedException {
487: for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
488: if (LEXICAL_HANDLER_NAMES[i].equals(name)) {
489: return getLexicalHandler();
490: }
491: }
492: return super .getProperty(name);
493: }
494:
495: /**
496: * Parse a document.
497: *
498: * @param input The input source for the document entity.
499: * @exception org.xml.sax.SAXException Any SAX exception, possibly
500: * wrapping another exception.
501: * @exception java.io.IOException An IO exception from the parser,
502: * possibly from a byte stream or character stream
503: * supplied by the application.
504: * @see org.xml.sax.XMLReader#parse(org.xml.sax.InputSource)
505: */
506: public void parse(InputSource input) throws SAXException,
507: IOException {
508: installLexicalHandler();
509: super .parse(input);
510: }
511:
512: ////////////////////////////////////////////////////////////////////
513: // Registration of org.xml.sax.ext.LexicalHandler.
514: ////////////////////////////////////////////////////////////////////
515:
516: /**
517: * Set the lexical handler.
518: *
519: * @param handler The new lexical handler.
520: * @exception java.lang.NullPointerException If the handler
521: * is null.
522: */
523: public void setLexicalHandler(LexicalHandler handler) {
524: if (handler == null) {
525: throw new NullPointerException("Null lexical handler");
526: } else {
527: lexicalHandler = handler;
528: }
529: }
530:
531: /**
532: * Get the current lexical handler.
533: *
534: * @return The current lexical handler, or null if none was set.
535: */
536: public LexicalHandler getLexicalHandler() {
537: return lexicalHandler;
538: }
539:
540: ////////////////////////////////////////////////////////////////////
541: // Implementation of org.xml.sax.ext.LexicalHandler.
542: ////////////////////////////////////////////////////////////////////
543:
544: /**
545: * Filter a start DTD event.
546: *
547: * @param name The document type name.
548: * @param publicId The declared public identifier for the
549: * external DTD subset, or null if none was declared.
550: * @param systemId The declared system identifier for the
551: * external DTD subset, or null if none was declared.
552: * @exception org.xml.sax.SAXException If a filter
553: * further down the chain raises an exception.
554: * @see org.xml.sax.ext.LexicalHandler#startDTD
555: */
556: public void startDTD(String name, String publicId, String systemId)
557: throws SAXException {
558: if (lexicalHandler != null) {
559: lexicalHandler.startDTD(name, publicId, systemId);
560: }
561: }
562:
563: /**
564: * Filter a end DTD event.
565: *
566: * @exception org.xml.sax.SAXException If a filter
567: * further down the chain raises an exception.
568: * @see org.xml.sax.ext.LexicalHandler#endDTD
569: */
570: public void endDTD() throws SAXException {
571: if (lexicalHandler != null) {
572: lexicalHandler.endDTD();
573: }
574: }
575:
576: /*
577: * Filter a start entity event.
578: *
579: * @param name The name of the entity. If it is a parameter
580: * entity, the name will begin with '%', and if it is the
581: * external DTD subset, it will be "[dtd]".
582: * @exception org.xml.sax.SAXException If a filter
583: * further down the chain raises an exception.
584: * @see org.xml.sax.ext.LexicalHandler#startEntity
585: */
586: public void startEntity(String name) throws SAXException {
587: if (lexicalHandler != null) {
588: lexicalHandler.startEntity(name);
589: }
590: }
591:
592: /*
593: * Filter a end entity event.
594: *
595: * @param name The name of the entity that is ending.
596: * @exception org.xml.sax.SAXException If a filter
597: * further down the chain raises an exception.
598: * @see org.xml.sax.ext.LexicalHandler#endEntity
599: */
600: public void endEntity(String name) throws SAXException {
601: if (lexicalHandler != null) {
602: lexicalHandler.endEntity(name);
603: }
604: }
605:
606: /*
607: * Filter a start CDATA event.
608: *
609: * @exception org.xml.sax.SAXException If a filter
610: * further down the chain raises an exception.
611: * @see org.xml.sax.ext.LexicalHandler#startCDATA
612: */
613: public void startCDATA() throws SAXException {
614: if (lexicalHandler != null) {
615: lexicalHandler.startCDATA();
616: }
617: }
618:
619: /*
620: * Filter a end CDATA event.
621: *
622: * @exception org.xml.sax.SAXException If a filter
623: * further down the chain raises an exception.
624: * @see org.xml.sax.ext.LexicalHandler#endCDATA
625: */
626: public void endCDATA() throws SAXException {
627: if (lexicalHandler != null) {
628: lexicalHandler.endCDATA();
629: }
630: }
631:
632: /*
633: * Filter a comment event.
634: *
635: * @param ch An array holding the characters in the comment.
636: * @param start The starting position in the array.
637: * @param length The number of characters to use from the array.
638: * @exception org.xml.sax.SAXException If a filter
639: * further down the chain raises an exception.
640: * @see org.xml.sax.ext.LexicalHandler#comment
641: */
642: public void comment(char[] ch, int start, int length)
643: throws SAXException {
644: if (lexicalHandler != null) {
645: lexicalHandler.comment(ch, start, length);
646: }
647: }
648:
649: ////////////////////////////////////////////////////////////////////
650: // Internal methods.
651: ////////////////////////////////////////////////////////////////////
652:
653: /**
654: * Installs lexical handler before a parse.
655: *
656: * <p>Before every parse, check whether the parent is
657: * non-null, and re-register the filter for the lexical
658: * events.</p>
659: */
660: private void installLexicalHandler() {
661: XMLReader parent = getParent();
662: if (parent == null) {
663: throw new NullPointerException("No parent for filter");
664: }
665: // try to register for lexical events
666: for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
667: try {
668: parent.setProperty(LEXICAL_HANDLER_NAMES[i], this );
669: break;
670: } catch (SAXNotRecognizedException ex) {
671: // ignore
672: } catch (SAXNotSupportedException ex) {
673: // ignore
674: }
675: }
676: }
677:
678: ////////////////////////////////////////////////////////////////////
679: // Internal state.
680: ////////////////////////////////////////////////////////////////////
681:
682: private LexicalHandler lexicalHandler = null;
683:
684: ////////////////////////////////////////////////////////////////////
685: // Constants.
686: ////////////////////////////////////////////////////////////////////
687:
688: protected static final Attributes EMPTY_ATTS = new AttributesImpl();
689:
690: protected static final String[] LEXICAL_HANDLER_NAMES = {
691: "http://xml.org/sax/properties/lexical-handler",
692: "http://xml.org/sax/handlers/LexicalHandler" };
693:
694: }
695:
696: // end of XMLFilterBase.java
|