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.
6 // $Id: ParserAdapter.java,v 1.8.2.4 2002/01/29 21:34:14 dbrownell Exp $
8 package org
.xml
.sax
.helpers
;
10 import java
.io
.IOException
;
11 import java
.util
.Enumeration
;
12 import java
.util
.Vector
;
14 import org
.xml
.sax
.Parser
; // deprecated
15 import org
.xml
.sax
.InputSource
;
16 import org
.xml
.sax
.Locator
;
17 import org
.xml
.sax
.AttributeList
; // deprecated
18 import org
.xml
.sax
.EntityResolver
;
19 import org
.xml
.sax
.DTDHandler
;
20 import org
.xml
.sax
.DocumentHandler
; // deprecated
21 import org
.xml
.sax
.ErrorHandler
;
22 import org
.xml
.sax
.SAXException
;
23 import org
.xml
.sax
.SAXParseException
;
25 import org
.xml
.sax
.XMLReader
;
26 import org
.xml
.sax
.Attributes
;
27 import org
.xml
.sax
.ContentHandler
;
28 import org
.xml
.sax
.SAXNotRecognizedException
;
29 import org
.xml
.sax
.SAXNotSupportedException
;
33 * Adapt a SAX1 Parser as a SAX2 XMLReader.
36 * <em>This module, both source code and documentation, is in the
37 * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
38 * See <a href='http://www.saxproject.org'>http://www.saxproject.org</a>
39 * for further information.
42 * <p>This class wraps a SAX1 {@link org.xml.sax.Parser Parser}
43 * and makes it act as a SAX2 {@link org.xml.sax.XMLReader XMLReader},
44 * with feature, property, and Namespace support. Note
45 * that it is not possible to report {@link org.xml.sax.ContentHandler#skippedEntity
46 * skippedEntity} events, since SAX1 does not make that information available.</p>
48 * <p>This adapter does not test for duplicate Namespace-qualified
49 * attribute names.</p>
52 * @author David Megginson
53 * @version 2.0.1 (sax2r2)
54 * @see org.xml.sax.helpers.XMLReaderAdapter
55 * @see org.xml.sax.XMLReader
56 * @see org.xml.sax.Parser
58 public class ParserAdapter
implements XMLReader
, DocumentHandler
62 ////////////////////////////////////////////////////////////////////
64 ////////////////////////////////////////////////////////////////////
68 * Construct a new parser adapter.
70 * <p>Use the "org.xml.sax.parser" property to locate the
71 * embedded SAX1 driver.</p>
73 * @exception SAXException If the embedded driver
74 * cannot be instantiated or if the
75 * org.xml.sax.parser property is not specified.
77 public ParserAdapter ()
82 String driver
= System
.getProperty("org.xml.sax.parser");
85 setup(ParserFactory
.makeParser());
86 } catch (ClassNotFoundException e1
) {
88 SAXException("Cannot find SAX1 driver class " +
90 } catch (IllegalAccessException e2
) {
92 SAXException("SAX1 driver class " +
94 " found but cannot be loaded", e2
);
95 } catch (InstantiationException e3
) {
97 SAXException("SAX1 driver class " +
99 " loaded but cannot be instantiated", e3
);
100 } catch (ClassCastException e4
) {
102 SAXException("SAX1 driver class " +
104 " does not implement org.xml.sax.Parser");
105 } catch (NullPointerException e5
) {
107 SAXException("System property org.xml.sax.parser not specified");
113 * Construct a new parser adapter.
115 * <p>Note that the embedded parser cannot be changed once the
116 * adapter is created; to embed a different parser, allocate
117 * a new ParserAdapter.</p>
119 * @param parser The SAX1 parser to embed.
120 * @exception java.lang.NullPointerException If the parser parameter
123 public ParserAdapter (Parser parser
)
131 * Internal setup method.
133 * @param parser The embedded parser.
134 * @exception java.lang.NullPointerException If the parser parameter
137 private void setup (Parser parser
)
139 if (parser
== null) {
141 NullPointerException("Parser argument must not be null");
143 this.parser
= parser
;
144 atts
= new AttributesImpl();
145 nsSupport
= new NamespaceSupport();
146 attAdapter
= new AttributeListAdapter();
151 ////////////////////////////////////////////////////////////////////
152 // Implementation of org.xml.sax.XMLReader.
153 ////////////////////////////////////////////////////////////////////
157 // Internal constants for the sake of convenience.
159 private final static String FEATURES
= "http://xml.org/sax/features/";
160 private final static String NAMESPACES
= FEATURES
+ "namespaces";
161 private final static String NAMESPACE_PREFIXES
= FEATURES
+ "namespace-prefixes";
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
178 public void setFeature (String name
, boolean value
)
179 throws SAXNotRecognizedException
, SAXNotSupportedException
181 if (name
.equals(NAMESPACES
)) {
182 checkNotParsing("feature", name
);
184 if (!namespaces
&& !prefixes
) {
187 } else if (name
.equals(NAMESPACE_PREFIXES
)) {
188 checkNotParsing("feature", name
);
190 if (!prefixes
&& !namespaces
) {
194 throw new SAXNotRecognizedException("Feature: " + name
);
200 * Check a parser feature flag.
202 * <p>The only features recognized are namespaces and
203 * namespace-prefixes.</p>
205 * @param name The feature name, as a complete URI.
206 * @return The current feature value.
207 * @exception SAXNotRecognizedException If the feature
208 * value can't be assigned or retrieved.
209 * @exception SAXNotSupportedException If the
210 * feature is not currently readable.
211 * @see org.xml.sax.XMLReader#setFeature
213 public boolean getFeature (String name
)
214 throws SAXNotRecognizedException
, SAXNotSupportedException
216 if (name
.equals(NAMESPACES
)) {
218 } else if (name
.equals(NAMESPACE_PREFIXES
)) {
221 throw new SAXNotRecognizedException("Feature: " + name
);
227 * Set a parser property.
229 * <p>No properties are currently recognized.</p>
231 * @param name The property name.
232 * @param value The property value.
233 * @exception SAXNotRecognizedException If the property
234 * value can't be assigned or retrieved.
235 * @exception SAXNotSupportedException If the property
236 * can't be assigned that value.
237 * @see org.xml.sax.XMLReader#setProperty
239 public void setProperty (String name
, Object value
)
240 throws SAXNotRecognizedException
, SAXNotSupportedException
242 throw new SAXNotRecognizedException("Property: " + name
);
247 * Get a parser property.
249 * <p>No properties are currently recognized.</p>
251 * @param name The property name.
252 * @return The property value.
253 * @exception SAXNotRecognizedException If the property
254 * value can't be assigned or retrieved.
255 * @exception SAXNotSupportedException If the property
256 * value is not currently readable.
257 * @see org.xml.sax.XMLReader#getProperty
259 public Object
getProperty (String name
)
260 throws SAXNotRecognizedException
, SAXNotSupportedException
262 throw new SAXNotRecognizedException("Property: " + name
);
267 * Set the entity resolver.
269 * @param resolver The new entity resolver.
270 * @see org.xml.sax.XMLReader#setEntityResolver
272 public void setEntityResolver (EntityResolver resolver
)
274 entityResolver
= resolver
;
279 * Return the current entity resolver.
281 * @return The current entity resolver, or null if none was supplied.
282 * @see org.xml.sax.XMLReader#getEntityResolver
284 public EntityResolver
getEntityResolver ()
286 return entityResolver
;
291 * Set the DTD handler.
293 * @param resolver The new DTD handler.
294 * @see org.xml.sax.XMLReader#setEntityResolver
296 public void setDTDHandler (DTDHandler handler
)
298 dtdHandler
= handler
;
303 * Return the current DTD handler.
305 * @return The current DTD handler, or null if none was supplied.
306 * @see org.xml.sax.XMLReader#getEntityResolver
308 public DTDHandler
getDTDHandler ()
315 * Set the content handler.
317 * @param resolver The new content handler.
318 * @see org.xml.sax.XMLReader#setEntityResolver
320 public void setContentHandler (ContentHandler handler
)
322 contentHandler
= handler
;
327 * Return the current content handler.
329 * @return The current content handler, or null if none was supplied.
330 * @see org.xml.sax.XMLReader#getEntityResolver
332 public ContentHandler
getContentHandler ()
334 return contentHandler
;
339 * Set the error handler.
341 * @param resolver The new error handler.
342 * @see org.xml.sax.XMLReader#setEntityResolver
344 public void setErrorHandler (ErrorHandler handler
)
346 errorHandler
= handler
;
351 * Return the current error handler.
353 * @return The current error handler, or null if none was supplied.
354 * @see org.xml.sax.XMLReader#getEntityResolver
356 public ErrorHandler
getErrorHandler ()
363 * Parse an XML document.
365 * @param systemId The absolute URL of the document.
366 * @exception java.io.IOException If there is a problem reading
367 * the raw content of the document.
368 * @exception SAXException If there is a problem
369 * processing the document.
370 * @see #parse(org.xml.sax.InputSource)
371 * @see org.xml.sax.Parser#parse(java.lang.String)
373 public void parse (String systemId
)
374 throws IOException
, SAXException
376 parse(new InputSource(systemId
));
381 * Parse an XML document.
383 * @param input An input source for the document.
384 * @exception java.io.IOException If there is a problem reading
385 * the raw content of the document.
386 * @exception SAXException If there is a problem
387 * processing the document.
388 * @see #parse(java.lang.String)
389 * @see org.xml.sax.Parser#parse(org.xml.sax.InputSource)
391 public void parse (InputSource input
)
392 throws IOException
, SAXException
395 throw new SAXException("Parser is already in use");
409 ////////////////////////////////////////////////////////////////////
410 // Implementation of org.xml.sax.DocumentHandler.
411 ////////////////////////////////////////////////////////////////////
415 * Adapter implementation method; do not call.
416 * Adapt a SAX1 document locator event.
418 * @param locator A document locator.
419 * @see org.xml.sax.ContentHandler#setDocumentLocator
421 public void setDocumentLocator (Locator locator
)
423 this.locator
= locator
;
424 if (contentHandler
!= null) {
425 contentHandler
.setDocumentLocator(locator
);
431 * Adapter implementation method; do not call.
432 * Adapt a SAX1 start document event.
434 * @exception SAXException The client may raise a
435 * processing exception.
436 * @see org.xml.sax.DocumentHandler#startDocument
438 public void startDocument ()
441 if (contentHandler
!= null) {
442 contentHandler
.startDocument();
448 * Adapter implementation method; do not call.
449 * Adapt a SAX1 end document event.
451 * @exception SAXException The client may raise a
452 * processing exception.
453 * @see org.xml.sax.DocumentHandler#endDocument
455 public void endDocument ()
458 if (contentHandler
!= null) {
459 contentHandler
.endDocument();
465 * Adapter implementation method; do not call.
466 * Adapt a SAX1 startElement event.
468 * <p>If necessary, perform Namespace processing.</p>
470 * @param qName The qualified (prefixed) name.
471 * @param qAtts The XML 1.0 attribute list (with qnames).
472 * @exception SAXException The client may raise a
473 * processing exception.
475 public void startElement (String qName
, AttributeList qAtts
)
478 // These are exceptions from the
479 // first pass; they should be
480 // ignored if there's a second pass,
481 // but reported otherwise.
482 Vector exceptions
= null;
484 // If we're not doing Namespace
485 // processing, dispatch this quickly.
487 if (contentHandler
!= null) {
488 attAdapter
.setAttributeList(qAtts
);
489 contentHandler
.startElement("", "", qName
.intern(),
496 // OK, we're doing Namespace processing.
497 nsSupport
.pushContext();
498 int length
= qAtts
.getLength();
500 // First pass: handle NS decls
501 for (int i
= 0; i
< length
; i
++) {
502 String attQName
= qAtts
.getName(i
);
504 if (!attQName
.startsWith("xmlns"))
506 // Could be a declaration...
508 int n
= attQName
.indexOf(':');
511 if (n
== -1 && attQName
.length () == 5) {
514 // XML namespaces spec doesn't discuss "xmlnsf:oo"
515 // (and similarly named) attributes ... at most, warn
517 } else // xmlns:foo=...
518 prefix
= attQName
.substring(n
+1);
520 String value
= qAtts
.getValue(i
);
521 if (!nsSupport
.declarePrefix(prefix
, value
)) {
522 reportError("Illegal Namespace prefix: " + prefix
);
525 if (contentHandler
!= null)
526 contentHandler
.startPrefixMapping(prefix
, value
);
529 // Second pass: copy all relevant
530 // attributes into the SAX2 AttributeList
531 // using updated prefix bindings
533 for (int i
= 0; i
< length
; i
++) {
534 String attQName
= qAtts
.getName(i
);
535 String type
= qAtts
.getType(i
);
536 String value
= qAtts
.getValue(i
);
539 if (attQName
.startsWith("xmlns")) {
541 int n
= attQName
.indexOf(':');
543 if (n
== -1 && attQName
.length () == 5) {
546 // XML namespaces spec doesn't discuss "xmlnsf:oo"
547 // (and similarly named) attributes ... ignore
550 prefix
= attQName
.substring(n
+1);
552 // Yes, decl: report or prune
553 if (prefix
!= null) {
555 atts
.addAttribute("", "", attQName
.intern(),
561 // Not a declaration -- report
563 String attName
[] = processName(attQName
, true, true);
564 atts
.addAttribute(attName
[0], attName
[1], attName
[2],
566 } catch (SAXException e
) {
567 if (exceptions
== null)
568 exceptions
= new Vector();
569 exceptions
.addElement(e
);
570 atts
.addAttribute("", attQName
, attQName
, type
, value
);
574 // now handle the deferred exception reports
575 if (exceptions
!= null && errorHandler
!= null) {
576 for (int i
= 0; i
< exceptions
.size(); i
++)
577 errorHandler
.error((SAXParseException
)
578 (exceptions
.elementAt(i
)));
581 // OK, finally report the event.
582 if (contentHandler
!= null) {
583 String name
[] = processName(qName
, false, false);
584 contentHandler
.startElement(name
[0], name
[1], name
[2], atts
);
590 * Adapter implementation method; do not call.
591 * Adapt a SAX1 end element event.
593 * @param qName The qualified (prefixed) name.
594 * @exception SAXException The client may raise a
595 * processing exception.
596 * @see org.xml.sax.DocumentHandler#endElement
598 public void endElement (String qName
)
601 // If we're not doing Namespace
602 // processing, dispatch this quickly.
604 if (contentHandler
!= null) {
605 contentHandler
.endElement("", "", qName
.intern());
611 String names
[] = processName(qName
, false, false);
612 if (contentHandler
!= null) {
613 contentHandler
.endElement(names
[0], names
[1], names
[2]);
614 Enumeration prefixes
= nsSupport
.getDeclaredPrefixes();
615 while (prefixes
.hasMoreElements()) {
616 String prefix
= (String
)prefixes
.nextElement();
617 contentHandler
.endPrefixMapping(prefix
);
620 nsSupport
.popContext();
625 * Adapter implementation method; do not call.
626 * Adapt a SAX1 characters event.
628 * @param ch An array of characters.
629 * @param start The starting position in the array.
630 * @param length The number of characters to use.
631 * @exception SAXException The client may raise a
632 * processing exception.
633 * @see org.xml.sax.DocumentHandler#characters
635 public void characters (char ch
[], int start
, int length
)
638 if (contentHandler
!= null) {
639 contentHandler
.characters(ch
, start
, length
);
645 * Adapter implementation method; do not call.
646 * Adapt a SAX1 ignorable whitespace event.
648 * @param ch An array of characters.
649 * @param start The starting position in the array.
650 * @param length The number of characters to use.
651 * @exception SAXException The client may raise a
652 * processing exception.
653 * @see org.xml.sax.DocumentHandler#ignorableWhitespace
655 public void ignorableWhitespace (char ch
[], int start
, int length
)
658 if (contentHandler
!= null) {
659 contentHandler
.ignorableWhitespace(ch
, start
, length
);
665 * Adapter implementation method; do not call.
666 * Adapt a SAX1 processing instruction event.
668 * @param target The processing instruction target.
669 * @param data The remainder of the processing instruction
670 * @exception SAXException The client may raise a
671 * processing exception.
672 * @see org.xml.sax.DocumentHandler#processingInstruction
674 public void processingInstruction (String target
, String data
)
677 if (contentHandler
!= null) {
678 contentHandler
.processingInstruction(target
, data
);
684 ////////////////////////////////////////////////////////////////////
685 // Internal utility methods.
686 ////////////////////////////////////////////////////////////////////
690 * Initialize the parser before each run.
692 private void setupParser ()
696 if (entityResolver
!= null) {
697 parser
.setEntityResolver(entityResolver
);
699 if (dtdHandler
!= null) {
700 parser
.setDTDHandler(dtdHandler
);
702 if (errorHandler
!= null) {
703 parser
.setErrorHandler(errorHandler
);
705 parser
.setDocumentHandler(this);
711 * Process a qualified (prefixed) name.
713 * <p>If the name has an undeclared prefix, use only the qname
714 * and make an ErrorHandler.error callback in case the app is
717 * @param qName The qualified (prefixed) name.
718 * @param isAttribute true if this is an attribute name.
719 * @return The name split into three parts.
720 * @exception SAXException The client may throw
721 * an exception if there is an error callback.
723 private String
[] processName (String qName
, boolean isAttribute
,
724 boolean useException
)
727 String parts
[] = nsSupport
.processName(qName
, nameParts
,
731 throw makeException("Undeclared prefix: " + qName
);
732 reportError("Undeclared prefix: " + qName
);
733 parts
= new String
[3];
734 parts
[0] = parts
[1] = "";
735 parts
[2] = qName
.intern();
742 * Report a non-fatal error.
744 * @param message The error message.
745 * @exception SAXException The client may throw
748 void reportError (String message
)
751 if (errorHandler
!= null)
752 errorHandler
.error(makeException(message
));
757 * Construct an exception for the current context.
759 * @param message The error message.
761 private SAXParseException
makeException (String message
)
763 if (locator
!= null) {
764 return new SAXParseException(message
, locator
);
766 return new SAXParseException(message
, null, null, -1, -1);
772 * Throw an exception if we are parsing.
774 * <p>Use this method to detect illegal feature or
775 * property changes.</p>
777 * @param type The type of thing (feature or property).
778 * @param name The feature or property name.
779 * @exception SAXNotSupportedException If a
780 * document is currently being parsed.
782 private void checkNotParsing (String type
, String name
)
783 throws SAXNotSupportedException
786 throw new SAXNotSupportedException("Cannot change " +
788 name
+ " while parsing");
795 ////////////////////////////////////////////////////////////////////
797 ////////////////////////////////////////////////////////////////////
799 private NamespaceSupport nsSupport
;
800 private AttributeListAdapter attAdapter
;
802 private boolean parsing
= false;
803 private String nameParts
[] = new String
[3];
805 private Parser parser
= null;
807 private AttributesImpl atts
= null;
810 private boolean namespaces
= true;
811 private boolean prefixes
= false;
818 EntityResolver entityResolver
= null;
819 DTDHandler dtdHandler
= null;
820 ContentHandler contentHandler
= null;
821 ErrorHandler errorHandler
= null;
825 ////////////////////////////////////////////////////////////////////
826 // Inner class to wrap an AttributeList when not doing NS proc.
827 ////////////////////////////////////////////////////////////////////
831 * Adapt a SAX1 AttributeList as a SAX2 Attributes object.
833 * <p>This class is in the Public Domain, and comes with NO
834 * WARRANTY of any kind.</p>
836 * <p>This wrapper class is used only when Namespace support
837 * is disabled -- it provides pretty much a direct mapping
838 * from SAX1 to SAX2, except that names and types are
839 * interned whenever requested.</p>
841 final class AttributeListAdapter
implements Attributes
845 * Construct a new adapter.
847 AttributeListAdapter ()
853 * Set the embedded AttributeList.
855 * <p>This method must be invoked before any of the others
858 * @param The SAX1 attribute list (with qnames).
860 void setAttributeList (AttributeList qAtts
)
867 * Return the length of the attribute list.
869 * @return The number of attributes in the list.
870 * @see org.xml.sax.Attributes#getLength
872 public int getLength ()
874 return qAtts
.getLength();
879 * Return the Namespace URI of the specified attribute.
881 * @param The attribute's index.
882 * @return Always the empty string.
883 * @see org.xml.sax.Attributes#getURI
885 public String
getURI (int i
)
892 * Return the local name of the specified attribute.
894 * @param The attribute's index.
895 * @return Always the empty string.
896 * @see org.xml.sax.Attributes#getLocalName
898 public String
getLocalName (int i
)
905 * Return the qualified (prefixed) name of the specified attribute.
907 * @param The attribute's index.
908 * @return The attribute's qualified name, internalized.
910 public String
getQName (int i
)
912 return qAtts
.getName(i
).intern();
917 * Return the type of the specified attribute.
919 * @param The attribute's index.
920 * @return The attribute's type as an internalized string.
922 public String
getType (int i
)
924 return qAtts
.getType(i
).intern();
929 * Return the value of the specified attribute.
931 * @param The attribute's index.
932 * @return The attribute's value.
934 public String
getValue (int i
)
936 return qAtts
.getValue(i
);
941 * Look up an attribute index by Namespace name.
943 * @param uri The Namespace URI or the empty string.
944 * @param localName The local name.
945 * @return The attributes index, or -1 if none was found.
946 * @see org.xml.sax.Attributes#getIndex(java.lang.String,java.lang.String)
948 public int getIndex (String uri
, String localName
)
955 * Look up an attribute index by qualified (prefixed) name.
957 * @param qName The qualified name.
958 * @return The attributes index, or -1 if none was found.
959 * @see org.xml.sax.Attributes#getIndex(java.lang.String)
961 public int getIndex (String qName
)
963 int max
= atts
.getLength();
964 for (int i
= 0; i
< max
; i
++) {
965 if (qAtts
.getName(i
).equals(qName
)) {
974 * Look up the type of an attribute by Namespace name.
976 * @param uri The Namespace URI
977 * @param localName The local name.
978 * @return The attribute's type as an internalized string.
980 public String
getType (String uri
, String localName
)
987 * Look up the type of an attribute by qualified (prefixed) name.
989 * @param qName The qualified name.
990 * @return The attribute's type as an internalized string.
992 public String
getType (String qName
)
994 return qAtts
.getType(qName
).intern();
999 * Look up the value of an attribute by Namespace name.
1001 * @param uri The Namespace URI
1002 * @param localName The local name.
1003 * @return The attribute's value.
1005 public String
getValue (String uri
, String localName
)
1012 * Look up the value of an attribute by qualified (prefixed) name.
1014 * @param qName The qualified name.
1015 * @return The attribute's value.
1017 public String
getValue (String qName
)
1019 return qAtts
.getValue(qName
);
1022 private AttributeList qAtts
;
1026 // end of ParserAdapter.java