1 // ParserAdapter.java - adapt a SAX1 Parser to a SAX2 XMLReader.
2 // http://www.saxproject.org
3 // Written by David Megginson
4 // NO WARRANTY! This class is in the public domain.
5 // $Id: ParserAdapter.java,v 1.16 2004/04/26 17:34:35 dmegginson Exp $
7 package org.xml.sax.helpers;
9 import java.io.IOException;
10 import java.util.Enumeration;
11 import java.util.Vector;
13 import org.xml.sax.Parser; // deprecated
14 import org.xml.sax.InputSource;
15 import org.xml.sax.Locator;
16 import org.xml.sax.AttributeList; // deprecated
17 import org.xml.sax.EntityResolver;
18 import org.xml.sax.DTDHandler;
19 import org.xml.sax.DocumentHandler; // deprecated
20 import org.xml.sax.ErrorHandler;
21 import org.xml.sax.SAXException;
22 import org.xml.sax.SAXParseException;
24 import org.xml.sax.XMLReader;
25 import org.xml.sax.Attributes;
26 import org.xml.sax.ContentHandler;
27 import org.xml.sax.SAXNotRecognizedException;
28 import org.xml.sax.SAXNotSupportedException;
32 * Adapt a SAX1 Parser as a SAX2 XMLReader.
35 * <em>This module, both source code and documentation, is in the
36 * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
37 * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
38 * for further information.
41 * <p>This class wraps a SAX1 {@link org.xml.sax.Parser Parser}
42 * and makes it act as a SAX2 {@link org.xml.sax.XMLReader XMLReader},
43 * with feature, property, and Namespace support. Note
44 * that it is not possible to report {@link org.xml.sax.ContentHandler#skippedEntity
45 * skippedEntity} events, since SAX1 does not make that information available.</p>
47 * <p>This adapter does not test for duplicate Namespace-qualified
48 * attribute names.</p>
51 * @author David Megginson
52 * @version 2.0.1 (sax2r2)
53 * @see org.xml.sax.helpers.XMLReaderAdapter
54 * @see org.xml.sax.XMLReader
55 * @see org.xml.sax.Parser
57 public class ParserAdapter implements XMLReader, DocumentHandler
61 ////////////////////////////////////////////////////////////////////
63 ////////////////////////////////////////////////////////////////////
67 * Construct a new parser adapter.
69 * <p>Use the "org.xml.sax.parser" property to locate the
70 * embedded SAX1 driver.</p>
72 * @exception SAXException If the embedded driver
73 * cannot be instantiated or if the
74 * org.xml.sax.parser property is not specified.
76 public ParserAdapter ()
81 String driver = System.getProperty("org.xml.sax.parser");
84 setup(ParserFactory.makeParser());
85 } catch (ClassNotFoundException e1) {
87 SAXException("Cannot find SAX1 driver class " +
89 } catch (IllegalAccessException e2) {
91 SAXException("SAX1 driver class " +
93 " found but cannot be loaded", e2);
94 } catch (InstantiationException e3) {
96 SAXException("SAX1 driver class " +
98 " loaded but cannot be instantiated", e3);
99 } catch (ClassCastException e4) {
101 SAXException("SAX1 driver class " +
103 " does not implement org.xml.sax.Parser");
104 } catch (NullPointerException e5) {
106 SAXException("System property org.xml.sax.parser not specified");
112 * Construct a new parser adapter.
114 * <p>Note that the embedded parser cannot be changed once the
115 * adapter is created; to embed a different parser, allocate
116 * a new ParserAdapter.</p>
118 * @param parser The SAX1 parser to embed.
119 * @exception java.lang.NullPointerException If the parser parameter
122 public ParserAdapter (Parser parser)
130 * Internal setup method.
132 * @param parser The embedded parser.
133 * @exception java.lang.NullPointerException If the parser parameter
136 private void setup (Parser parser)
138 if (parser == null) {
140 NullPointerException("Parser argument must not be null");
142 this.parser = parser;
143 atts = new AttributesImpl();
144 nsSupport = new NamespaceSupport();
145 attAdapter = new AttributeListAdapter();
150 ////////////////////////////////////////////////////////////////////
151 // Implementation of org.xml.sax.XMLReader.
152 ////////////////////////////////////////////////////////////////////
156 // Internal constants for the sake of convenience.
158 private final static String FEATURES = "http://xml.org/sax/features/";
159 private final static String NAMESPACES = FEATURES + "namespaces";
160 private final static String NAMESPACE_PREFIXES = FEATURES + "namespace-prefixes";
161 private final static String XMLNS_URIs = FEATURES + "xmlns-uris";
165 * Set a feature flag for the parser.
167 * <p>The only features recognized are namespaces and
168 * namespace-prefixes.</p>
170 * @param name The feature name, as a complete URI.
171 * @param value The requested feature value.
172 * @exception SAXNotRecognizedException If the feature
173 * can't be assigned or retrieved.
174 * @exception SAXNotSupportedException If the feature
175 * can't be assigned that value.
176 * @see org.xml.sax.XMLReader#setFeature
179 public void setFeature (String name, boolean value)
180 throws SAXNotRecognizedException, SAXNotSupportedException
182 if (name.equals(NAMESPACES)) {
183 checkNotParsing("feature", name);
185 if (!namespaces && !prefixes) {
188 } else if (name.equals(NAMESPACE_PREFIXES)) {
189 checkNotParsing("feature", name);
191 if (!prefixes && !namespaces) {
194 } else if (name.equals(XMLNS_URIs)) {
195 checkNotParsing("feature", name);
198 throw new SAXNotRecognizedException("Feature: " + name);
204 * Check a parser feature flag.
206 * <p>The only features recognized are namespaces and
207 * namespace-prefixes.</p>
209 * @param name The feature name, as a complete URI.
210 * @return The current feature value.
211 * @exception SAXNotRecognizedException If the feature
212 * value can't be assigned or retrieved.
213 * @exception SAXNotSupportedException If the
214 * feature is not currently readable.
215 * @see org.xml.sax.XMLReader#setFeature
218 public boolean getFeature (String name)
219 throws SAXNotRecognizedException, SAXNotSupportedException
221 if (name.equals(NAMESPACES)) {
223 } else if (name.equals(NAMESPACE_PREFIXES)) {
225 } else if (name.equals(XMLNS_URIs)) {
228 throw new SAXNotRecognizedException("Feature: " + name);
234 * Set a parser property.
236 * <p>No properties are currently recognized.</p>
238 * @param name The property name.
239 * @param value The property value.
240 * @exception SAXNotRecognizedException If the property
241 * value can't be assigned or retrieved.
242 * @exception SAXNotSupportedException If the property
243 * can't be assigned that value.
244 * @see org.xml.sax.XMLReader#setProperty
247 public void setProperty (String name, Object value)
248 throws SAXNotRecognizedException, SAXNotSupportedException
250 throw new SAXNotRecognizedException("Property: " + name);
255 * Get a parser property.
257 * <p>No properties are currently recognized.</p>
259 * @param name The property name.
260 * @return The property value.
261 * @exception SAXNotRecognizedException If the property
262 * value can't be assigned or retrieved.
263 * @exception SAXNotSupportedException If the property
264 * value is not currently readable.
265 * @see org.xml.sax.XMLReader#getProperty
268 public Object getProperty (String name)
269 throws SAXNotRecognizedException, SAXNotSupportedException
271 throw new SAXNotRecognizedException("Property: " + name);
276 * Set the entity resolver.
278 * @param resolver The new entity resolver.
279 * @see org.xml.sax.XMLReader#setEntityResolver
282 public void setEntityResolver (EntityResolver resolver)
284 entityResolver = resolver;
289 * Return the current entity resolver.
291 * @return The current entity resolver, or null if none was supplied.
292 * @see org.xml.sax.XMLReader#getEntityResolver
295 public EntityResolver getEntityResolver ()
297 return entityResolver;
302 * Set the DTD handler.
304 * @param handler the new DTD handler
305 * @see org.xml.sax.XMLReader#setEntityResolver
308 public void setDTDHandler (DTDHandler handler)
310 dtdHandler = handler;
315 * Return the current DTD handler.
317 * @return the current DTD handler, or null if none was supplied
318 * @see org.xml.sax.XMLReader#getEntityResolver
321 public DTDHandler getDTDHandler ()
328 * Set the content handler.
330 * @param handler the new content handler
331 * @see org.xml.sax.XMLReader#setEntityResolver
334 public void setContentHandler (ContentHandler handler)
336 contentHandler = handler;
341 * Return the current content handler.
343 * @return The current content handler, or null if none was supplied.
344 * @see org.xml.sax.XMLReader#getEntityResolver
347 public ContentHandler getContentHandler ()
349 return contentHandler;
354 * Set the error handler.
356 * @param handler The new error handler.
357 * @see org.xml.sax.XMLReader#setEntityResolver
360 public void setErrorHandler (ErrorHandler handler)
362 errorHandler = handler;
367 * Return the current error handler.
369 * @return The current error handler, or null if none was supplied.
370 * @see org.xml.sax.XMLReader#getEntityResolver
373 public ErrorHandler getErrorHandler ()
380 * Parse an XML document.
382 * @param systemId The absolute URL of the document.
383 * @exception java.io.IOException If there is a problem reading
384 * the raw content of the document.
385 * @exception SAXException If there is a problem
386 * processing the document.
387 * @see #parse(org.xml.sax.InputSource)
388 * @see org.xml.sax.Parser#parse(java.lang.String)
391 public void parse (String systemId)
392 throws IOException, SAXException
394 parse(new InputSource(systemId));
399 * Parse an XML document.
401 * @param input An input source for the document.
402 * @exception java.io.IOException If there is a problem reading
403 * the raw content of the document.
404 * @exception SAXException If there is a problem
405 * processing the document.
406 * @see #parse(java.lang.String)
407 * @see org.xml.sax.Parser#parse(org.xml.sax.InputSource)
410 public void parse (InputSource input)
411 throws IOException, SAXException
414 throw new SAXException("Parser is already in use");
428 ////////////////////////////////////////////////////////////////////
429 // Implementation of org.xml.sax.DocumentHandler.
430 ////////////////////////////////////////////////////////////////////
434 * Adapter implementation method; do not call.
435 * Adapt a SAX1 document locator event.
437 * @param locator A document locator.
438 * @see org.xml.sax.ContentHandler#setDocumentLocator
441 public void setDocumentLocator (Locator locator)
443 this.locator = locator;
444 if (contentHandler != null) {
445 contentHandler.setDocumentLocator(locator);
451 * Adapter implementation method; do not call.
452 * Adapt a SAX1 start document event.
454 * @exception SAXException The client may raise a
455 * processing exception.
456 * @see org.xml.sax.DocumentHandler#startDocument
459 public void startDocument ()
462 if (contentHandler != null) {
463 contentHandler.startDocument();
469 * Adapter implementation method; do not call.
470 * Adapt a SAX1 end document event.
472 * @exception SAXException The client may raise a
473 * processing exception.
474 * @see org.xml.sax.DocumentHandler#endDocument
477 public void endDocument ()
480 if (contentHandler != null) {
481 contentHandler.endDocument();
487 * Adapter implementation method; do not call.
488 * Adapt a SAX1 startElement event.
490 * <p>If necessary, perform Namespace processing.</p>
492 * @param qName The qualified (prefixed) name.
493 * @param qAtts The XML attribute list (with qnames).
494 * @exception SAXException The client may raise a
495 * processing exception.
498 public void startElement (String qName, AttributeList qAtts)
501 // These are exceptions from the
502 // first pass; they should be
503 // ignored if there's a second pass,
504 // but reported otherwise.
505 Vector exceptions = null;
507 // If we're not doing Namespace
508 // processing, dispatch this quickly.
510 if (contentHandler != null) {
511 attAdapter.setAttributeList(qAtts);
512 contentHandler.startElement("", "", qName.intern(),
519 // OK, we're doing Namespace processing.
520 nsSupport.pushContext();
521 int length = qAtts.getLength();
523 // First pass: handle NS decls
524 for (int i = 0; i < length; i++) {
525 String attQName = qAtts.getName(i);
527 if (!attQName.startsWith("xmlns"))
529 // Could be a declaration...
531 int n = attQName.indexOf(':');
534 if (n == -1 && attQName.length () == 5) {
537 // XML namespaces spec doesn't discuss "xmlnsf:oo"
538 // (and similarly named) attributes ... at most, warn
540 } else // xmlns:foo=...
541 prefix = attQName.substring(n+1);
543 String value = qAtts.getValue(i);
544 if (!nsSupport.declarePrefix(prefix, value)) {
545 reportError("Illegal Namespace prefix: " + prefix);
548 if (contentHandler != null)
549 contentHandler.startPrefixMapping(prefix, value);
552 // Second pass: copy all relevant
553 // attributes into the SAX2 AttributeList
554 // using updated prefix bindings
556 for (int i = 0; i < length; i++) {
557 String attQName = qAtts.getName(i);
558 String type = qAtts.getType(i);
559 String value = qAtts.getValue(i);
562 if (attQName.startsWith("xmlns")) {
564 int n = attQName.indexOf(':');
566 if (n == -1 && attQName.length () == 5) {
569 // XML namespaces spec doesn't discuss "xmlnsf:oo"
570 // (and similarly named) attributes ... ignore
573 prefix = attQName.substring(6);
575 // Yes, decl: report or prune
576 if (prefix != null) {
579 // note funky case: localname can be null
580 // when declaring the default prefix, and
581 // yet the uri isn't null.
582 atts.addAttribute (nsSupport.XMLNS, prefix,
583 attQName.intern(), type, value);
585 atts.addAttribute ("", "",
586 attQName.intern(), type, value);
592 // Not a declaration -- report
594 String attName[] = processName(attQName, true, true);
595 atts.addAttribute(attName[0], attName[1], attName[2],
597 } catch (SAXException e) {
598 if (exceptions == null)
599 exceptions = new Vector();
600 exceptions.addElement(e);
601 atts.addAttribute("", attQName, attQName, type, value);
605 // now handle the deferred exception reports
606 if (exceptions != null && errorHandler != null) {
607 for (int i = 0; i < exceptions.size(); i++)
608 errorHandler.error((SAXParseException)
609 (exceptions.elementAt(i)));
612 // OK, finally report the event.
613 if (contentHandler != null) {
614 String name[] = processName(qName, false, false);
615 contentHandler.startElement(name[0], name[1], name[2], atts);
621 * Adapter implementation method; do not call.
622 * Adapt a SAX1 end element event.
624 * @param qName The qualified (prefixed) name.
625 * @exception SAXException The client may raise a
626 * processing exception.
627 * @see org.xml.sax.DocumentHandler#endElement
630 public void endElement (String qName)
633 // If we're not doing Namespace
634 // processing, dispatch this quickly.
636 if (contentHandler != null) {
637 contentHandler.endElement("", "", qName.intern());
643 String names[] = processName(qName, false, false);
644 if (contentHandler != null) {
645 contentHandler.endElement(names[0], names[1], names[2]);
646 Enumeration prefixes = nsSupport.getDeclaredPrefixes();
647 while (prefixes.hasMoreElements()) {
648 String prefix = (String)prefixes.nextElement();
649 contentHandler.endPrefixMapping(prefix);
652 nsSupport.popContext();
657 * Adapter implementation method; do not call.
658 * Adapt a SAX1 characters event.
660 * @param ch An array of characters.
661 * @param start The starting position in the array.
662 * @param length The number of characters to use.
663 * @exception SAXException The client may raise a
664 * processing exception.
665 * @see org.xml.sax.DocumentHandler#characters
668 public void characters (char ch[], int start, int length)
671 if (contentHandler != null) {
672 contentHandler.characters(ch, start, length);
678 * Adapter implementation method; do not call.
679 * Adapt a SAX1 ignorable whitespace event.
681 * @param ch An array of characters.
682 * @param start The starting position in the array.
683 * @param length The number of characters to use.
684 * @exception SAXException The client may raise a
685 * processing exception.
686 * @see org.xml.sax.DocumentHandler#ignorableWhitespace
689 public void ignorableWhitespace (char ch[], int start, int length)
692 if (contentHandler != null) {
693 contentHandler.ignorableWhitespace(ch, start, length);
699 * Adapter implementation method; do not call.
700 * Adapt a SAX1 processing instruction event.
702 * @param target The processing instruction target.
703 * @param data The remainder of the processing instruction
704 * @exception SAXException The client may raise a
705 * processing exception.
706 * @see org.xml.sax.DocumentHandler#processingInstruction
709 public void processingInstruction (String target, String data)
712 if (contentHandler != null) {
713 contentHandler.processingInstruction(target, data);
719 ////////////////////////////////////////////////////////////////////
720 // Internal utility methods.
721 ////////////////////////////////////////////////////////////////////
725 * Initialize the parser before each run.
727 private void setupParser ()
729 // catch an illegal "nonsense" state.
730 if (!prefixes && !namespaces)
731 throw new IllegalStateException ();
735 nsSupport.setNamespaceDeclUris (true);
737 if (entityResolver != null) {
738 parser.setEntityResolver(entityResolver);
740 if (dtdHandler != null) {
741 parser.setDTDHandler(dtdHandler);
743 if (errorHandler != null) {
744 parser.setErrorHandler(errorHandler);
746 parser.setDocumentHandler(this);
752 * Process a qualified (prefixed) name.
754 * <p>If the name has an undeclared prefix, use only the qname
755 * and make an ErrorHandler.error callback in case the app is
758 * @param qName The qualified (prefixed) name.
759 * @param isAttribute true if this is an attribute name.
760 * @return The name split into three parts.
761 * @exception SAXException The client may throw
762 * an exception if there is an error callback.
764 private String [] processName (String qName, boolean isAttribute,
765 boolean useException)
768 String parts[] = nsSupport.processName(qName, nameParts,
772 throw makeException("Undeclared prefix: " + qName);
773 reportError("Undeclared prefix: " + qName);
774 parts = new String[3];
775 parts[0] = parts[1] = "";
776 parts[2] = qName.intern();
783 * Report a non-fatal error.
785 * @param message The error message.
786 * @exception SAXException The client may throw
789 void reportError (String message)
792 if (errorHandler != null)
793 errorHandler.error(makeException(message));
798 * Construct an exception for the current context.
800 * @param message The error message.
802 private SAXParseException makeException (String message)
804 if (locator != null) {
805 return new SAXParseException(message, locator);
807 return new SAXParseException(message, null, null, -1, -1);
813 * Throw an exception if we are parsing.
815 * <p>Use this method to detect illegal feature or
816 * property changes.</p>
818 * @param type The type of thing (feature or property).
819 * @param name The feature or property name.
820 * @exception SAXNotSupportedException If a
821 * document is currently being parsed.
823 private void checkNotParsing (String type, String name)
824 throws SAXNotSupportedException
827 throw new SAXNotSupportedException("Cannot change " +
829 name + " while parsing");
836 ////////////////////////////////////////////////////////////////////
838 ////////////////////////////////////////////////////////////////////
840 private NamespaceSupport nsSupport;
841 private AttributeListAdapter attAdapter;
843 private boolean parsing = false;
844 private String nameParts[] = new String[3];
846 private Parser parser = null;
848 private AttributesImpl atts = null;
851 private boolean namespaces = true;
852 private boolean prefixes = false;
853 private boolean uris = false;
860 EntityResolver entityResolver = null;
861 DTDHandler dtdHandler = null;
862 ContentHandler contentHandler = null;
863 ErrorHandler errorHandler = null;
867 ////////////////////////////////////////////////////////////////////
868 // Inner class to wrap an AttributeList when not doing NS proc.
869 ////////////////////////////////////////////////////////////////////
873 * Adapt a SAX1 AttributeList as a SAX2 Attributes object.
875 * <p>This class is in the Public Domain, and comes with NO
876 * WARRANTY of any kind.</p>
878 * <p>This wrapper class is used only when Namespace support
879 * is disabled -- it provides pretty much a direct mapping
880 * from SAX1 to SAX2, except that names and types are
881 * interned whenever requested.</p>
883 final class AttributeListAdapter implements Attributes
887 * Construct a new adapter.
889 AttributeListAdapter ()
895 * Set the embedded AttributeList.
897 * <p>This method must be invoked before any of the others
900 * @param The SAX1 attribute list (with qnames).
902 void setAttributeList (AttributeList qAtts)
909 * Return the length of the attribute list.
911 * @return The number of attributes in the list.
912 * @see org.xml.sax.Attributes#getLength
915 public int getLength ()
917 return qAtts.getLength();
922 * Return the Namespace URI of the specified attribute.
924 * @param The attribute's index.
925 * @return Always the empty string.
926 * @see org.xml.sax.Attributes#getURI
929 public String getURI (int i)
936 * Return the local name of the specified attribute.
938 * @param The attribute's index.
939 * @return Always the empty string.
940 * @see org.xml.sax.Attributes#getLocalName
943 public String getLocalName (int i)
950 * Return the qualified (prefixed) name of the specified attribute.
952 * @param The attribute's index.
953 * @return The attribute's qualified name, internalized.
956 public String getQName (int i)
958 return qAtts.getName(i).intern();
963 * Return the type of the specified attribute.
965 * @param The attribute's index.
966 * @return The attribute's type as an internalized string.
969 public String getType (int i)
971 return qAtts.getType(i).intern();
976 * Return the value of the specified attribute.
978 * @param The attribute's index.
979 * @return The attribute's value.
982 public String getValue (int i)
984 return qAtts.getValue(i);
989 * Look up an attribute index by Namespace name.
991 * @param uri The Namespace URI or the empty string.
992 * @param localName The local name.
993 * @return The attributes index, or -1 if none was found.
994 * @see org.xml.sax.Attributes#getIndex(java.lang.String,java.lang.String)
997 public int getIndex (String uri, String localName)
1004 * Look up an attribute index by qualified (prefixed) name.
1006 * @param qName The qualified name.
1007 * @return The attributes index, or -1 if none was found.
1008 * @see org.xml.sax.Attributes#getIndex(java.lang.String)
1011 public int getIndex (String qName)
1013 int max = atts.getLength();
1014 for (int i = 0; i < max; i++) {
1015 if (qAtts.getName(i).equals(qName)) {
1024 * Look up the type of an attribute by Namespace name.
1026 * @param uri The Namespace URI
1027 * @param localName The local name.
1028 * @return The attribute's type as an internalized string.
1031 public String getType (String uri, String localName)
1038 * Look up the type of an attribute by qualified (prefixed) name.
1040 * @param qName The qualified name.
1041 * @return The attribute's type as an internalized string.
1044 public String getType (String qName)
1046 return qAtts.getType(qName).intern();
1051 * Look up the value of an attribute by Namespace name.
1053 * @param uri The Namespace URI
1054 * @param localName The local name.
1055 * @return The attribute's value.
1058 public String getValue (String uri, String localName)
1065 * Look up the value of an attribute by qualified (prefixed) name.
1067 * @param qName The qualified name.
1068 * @return The attribute's value.
1071 public String getValue (String qName)
1073 return qAtts.getValue(qName);
1076 private AttributeList qAtts;
1080 // end of ParserAdapter.java