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.1 2005/02/02 00:41:54 tromey 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
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
) {
193 } else if (name
.equals(XMLNS_URIs
)) {
194 checkNotParsing("feature", name
);
197 throw new SAXNotRecognizedException("Feature: " + name
);
203 * Check a parser feature flag.
205 * <p>The only features recognized are namespaces and
206 * namespace-prefixes.</p>
208 * @param name The feature name, as a complete URI.
209 * @return The current feature value.
210 * @exception SAXNotRecognizedException If the feature
211 * value can't be assigned or retrieved.
212 * @exception SAXNotSupportedException If the
213 * feature is not currently readable.
214 * @see org.xml.sax.XMLReader#setFeature
216 public boolean getFeature (String name
)
217 throws SAXNotRecognizedException
, SAXNotSupportedException
219 if (name
.equals(NAMESPACES
)) {
221 } else if (name
.equals(NAMESPACE_PREFIXES
)) {
223 } else if (name
.equals(XMLNS_URIs
)) {
226 throw new SAXNotRecognizedException("Feature: " + name
);
232 * Set a parser property.
234 * <p>No properties are currently recognized.</p>
236 * @param name The property name.
237 * @param value The property value.
238 * @exception SAXNotRecognizedException If the property
239 * value can't be assigned or retrieved.
240 * @exception SAXNotSupportedException If the property
241 * can't be assigned that value.
242 * @see org.xml.sax.XMLReader#setProperty
244 public void setProperty (String name
, Object value
)
245 throws SAXNotRecognizedException
, SAXNotSupportedException
247 throw new SAXNotRecognizedException("Property: " + name
);
252 * Get a parser property.
254 * <p>No properties are currently recognized.</p>
256 * @param name The property name.
257 * @return The property value.
258 * @exception SAXNotRecognizedException If the property
259 * value can't be assigned or retrieved.
260 * @exception SAXNotSupportedException If the property
261 * value is not currently readable.
262 * @see org.xml.sax.XMLReader#getProperty
264 public Object
getProperty (String name
)
265 throws SAXNotRecognizedException
, SAXNotSupportedException
267 throw new SAXNotRecognizedException("Property: " + name
);
272 * Set the entity resolver.
274 * @param resolver The new entity resolver.
275 * @see org.xml.sax.XMLReader#setEntityResolver
277 public void setEntityResolver (EntityResolver resolver
)
279 entityResolver
= resolver
;
284 * Return the current entity resolver.
286 * @return The current entity resolver, or null if none was supplied.
287 * @see org.xml.sax.XMLReader#getEntityResolver
289 public EntityResolver
getEntityResolver ()
291 return entityResolver
;
296 * Set the DTD handler.
298 * @param handler the new DTD handler
299 * @see org.xml.sax.XMLReader#setEntityResolver
301 public void setDTDHandler (DTDHandler handler
)
303 dtdHandler
= handler
;
308 * Return the current DTD handler.
310 * @return the current DTD handler, or null if none was supplied
311 * @see org.xml.sax.XMLReader#getEntityResolver
313 public DTDHandler
getDTDHandler ()
320 * Set the content handler.
322 * @param handler the new content handler
323 * @see org.xml.sax.XMLReader#setEntityResolver
325 public void setContentHandler (ContentHandler handler
)
327 contentHandler
= handler
;
332 * Return the current content handler.
334 * @return The current content handler, or null if none was supplied.
335 * @see org.xml.sax.XMLReader#getEntityResolver
337 public ContentHandler
getContentHandler ()
339 return contentHandler
;
344 * Set the error handler.
346 * @param handler The new error handler.
347 * @see org.xml.sax.XMLReader#setEntityResolver
349 public void setErrorHandler (ErrorHandler handler
)
351 errorHandler
= handler
;
356 * Return the current error handler.
358 * @return The current error handler, or null if none was supplied.
359 * @see org.xml.sax.XMLReader#getEntityResolver
361 public ErrorHandler
getErrorHandler ()
368 * Parse an XML document.
370 * @param systemId The absolute URL of the document.
371 * @exception java.io.IOException If there is a problem reading
372 * the raw content of the document.
373 * @exception SAXException If there is a problem
374 * processing the document.
375 * @see #parse(org.xml.sax.InputSource)
376 * @see org.xml.sax.Parser#parse(java.lang.String)
378 public void parse (String systemId
)
379 throws IOException
, SAXException
381 parse(new InputSource(systemId
));
386 * Parse an XML document.
388 * @param input An input source for the document.
389 * @exception java.io.IOException If there is a problem reading
390 * the raw content of the document.
391 * @exception SAXException If there is a problem
392 * processing the document.
393 * @see #parse(java.lang.String)
394 * @see org.xml.sax.Parser#parse(org.xml.sax.InputSource)
396 public void parse (InputSource input
)
397 throws IOException
, SAXException
400 throw new SAXException("Parser is already in use");
414 ////////////////////////////////////////////////////////////////////
415 // Implementation of org.xml.sax.DocumentHandler.
416 ////////////////////////////////////////////////////////////////////
420 * Adapter implementation method; do not call.
421 * Adapt a SAX1 document locator event.
423 * @param locator A document locator.
424 * @see org.xml.sax.ContentHandler#setDocumentLocator
426 public void setDocumentLocator (Locator locator
)
428 this.locator
= locator
;
429 if (contentHandler
!= null) {
430 contentHandler
.setDocumentLocator(locator
);
436 * Adapter implementation method; do not call.
437 * Adapt a SAX1 start document event.
439 * @exception SAXException The client may raise a
440 * processing exception.
441 * @see org.xml.sax.DocumentHandler#startDocument
443 public void startDocument ()
446 if (contentHandler
!= null) {
447 contentHandler
.startDocument();
453 * Adapter implementation method; do not call.
454 * Adapt a SAX1 end document event.
456 * @exception SAXException The client may raise a
457 * processing exception.
458 * @see org.xml.sax.DocumentHandler#endDocument
460 public void endDocument ()
463 if (contentHandler
!= null) {
464 contentHandler
.endDocument();
470 * Adapter implementation method; do not call.
471 * Adapt a SAX1 startElement event.
473 * <p>If necessary, perform Namespace processing.</p>
475 * @param qName The qualified (prefixed) name.
476 * @param qAtts The XML attribute list (with qnames).
477 * @exception SAXException The client may raise a
478 * processing exception.
480 public void startElement (String qName
, AttributeList qAtts
)
483 // These are exceptions from the
484 // first pass; they should be
485 // ignored if there's a second pass,
486 // but reported otherwise.
487 Vector exceptions
= null;
489 // If we're not doing Namespace
490 // processing, dispatch this quickly.
492 if (contentHandler
!= null) {
493 attAdapter
.setAttributeList(qAtts
);
494 contentHandler
.startElement("", "", qName
.intern(),
501 // OK, we're doing Namespace processing.
502 nsSupport
.pushContext();
503 int length
= qAtts
.getLength();
505 // First pass: handle NS decls
506 for (int i
= 0; i
< length
; i
++) {
507 String attQName
= qAtts
.getName(i
);
509 if (!attQName
.startsWith("xmlns"))
511 // Could be a declaration...
513 int n
= attQName
.indexOf(':');
516 if (n
== -1 && attQName
.length () == 5) {
519 // XML namespaces spec doesn't discuss "xmlnsf:oo"
520 // (and similarly named) attributes ... at most, warn
522 } else // xmlns:foo=...
523 prefix
= attQName
.substring(n
+1);
525 String value
= qAtts
.getValue(i
);
526 if (!nsSupport
.declarePrefix(prefix
, value
)) {
527 reportError("Illegal Namespace prefix: " + prefix
);
530 if (contentHandler
!= null)
531 contentHandler
.startPrefixMapping(prefix
, value
);
534 // Second pass: copy all relevant
535 // attributes into the SAX2 AttributeList
536 // using updated prefix bindings
538 for (int i
= 0; i
< length
; i
++) {
539 String attQName
= qAtts
.getName(i
);
540 String type
= qAtts
.getType(i
);
541 String value
= qAtts
.getValue(i
);
544 if (attQName
.startsWith("xmlns")) {
546 int n
= attQName
.indexOf(':');
548 if (n
== -1 && attQName
.length () == 5) {
551 // XML namespaces spec doesn't discuss "xmlnsf:oo"
552 // (and similarly named) attributes ... ignore
555 prefix
= attQName
.substring(6);
557 // Yes, decl: report or prune
558 if (prefix
!= null) {
561 // note funky case: localname can be null
562 // when declaring the default prefix, and
563 // yet the uri isn't null.
564 atts
.addAttribute (nsSupport
.XMLNS
, prefix
,
565 attQName
.intern(), type
, value
);
567 atts
.addAttribute ("", "",
568 attQName
.intern(), type
, value
);
574 // Not a declaration -- report
576 String attName
[] = processName(attQName
, true, true);
577 atts
.addAttribute(attName
[0], attName
[1], attName
[2],
579 } catch (SAXException e
) {
580 if (exceptions
== null)
581 exceptions
= new Vector();
582 exceptions
.addElement(e
);
583 atts
.addAttribute("", attQName
, attQName
, type
, value
);
587 // now handle the deferred exception reports
588 if (exceptions
!= null && errorHandler
!= null) {
589 for (int i
= 0; i
< exceptions
.size(); i
++)
590 errorHandler
.error((SAXParseException
)
591 (exceptions
.elementAt(i
)));
594 // OK, finally report the event.
595 if (contentHandler
!= null) {
596 String name
[] = processName(qName
, false, false);
597 contentHandler
.startElement(name
[0], name
[1], name
[2], atts
);
603 * Adapter implementation method; do not call.
604 * Adapt a SAX1 end element event.
606 * @param qName The qualified (prefixed) name.
607 * @exception SAXException The client may raise a
608 * processing exception.
609 * @see org.xml.sax.DocumentHandler#endElement
611 public void endElement (String qName
)
614 // If we're not doing Namespace
615 // processing, dispatch this quickly.
617 if (contentHandler
!= null) {
618 contentHandler
.endElement("", "", qName
.intern());
624 String names
[] = processName(qName
, false, false);
625 if (contentHandler
!= null) {
626 contentHandler
.endElement(names
[0], names
[1], names
[2]);
627 Enumeration prefixes
= nsSupport
.getDeclaredPrefixes();
628 while (prefixes
.hasMoreElements()) {
629 String prefix
= (String
)prefixes
.nextElement();
630 contentHandler
.endPrefixMapping(prefix
);
633 nsSupport
.popContext();
638 * Adapter implementation method; do not call.
639 * Adapt a SAX1 characters event.
641 * @param ch An array of characters.
642 * @param start The starting position in the array.
643 * @param length The number of characters to use.
644 * @exception SAXException The client may raise a
645 * processing exception.
646 * @see org.xml.sax.DocumentHandler#characters
648 public void characters (char ch
[], int start
, int length
)
651 if (contentHandler
!= null) {
652 contentHandler
.characters(ch
, start
, length
);
658 * Adapter implementation method; do not call.
659 * Adapt a SAX1 ignorable whitespace event.
661 * @param ch An array of characters.
662 * @param start The starting position in the array.
663 * @param length The number of characters to use.
664 * @exception SAXException The client may raise a
665 * processing exception.
666 * @see org.xml.sax.DocumentHandler#ignorableWhitespace
668 public void ignorableWhitespace (char ch
[], int start
, int length
)
671 if (contentHandler
!= null) {
672 contentHandler
.ignorableWhitespace(ch
, start
, length
);
678 * Adapter implementation method; do not call.
679 * Adapt a SAX1 processing instruction event.
681 * @param target The processing instruction target.
682 * @param data The remainder of the processing instruction
683 * @exception SAXException The client may raise a
684 * processing exception.
685 * @see org.xml.sax.DocumentHandler#processingInstruction
687 public void processingInstruction (String target
, String data
)
690 if (contentHandler
!= null) {
691 contentHandler
.processingInstruction(target
, data
);
697 ////////////////////////////////////////////////////////////////////
698 // Internal utility methods.
699 ////////////////////////////////////////////////////////////////////
703 * Initialize the parser before each run.
705 private void setupParser ()
707 // catch an illegal "nonsense" state.
708 if (!prefixes
&& !namespaces
)
709 throw new IllegalStateException ();
713 nsSupport
.setNamespaceDeclUris (true);
715 if (entityResolver
!= null) {
716 parser
.setEntityResolver(entityResolver
);
718 if (dtdHandler
!= null) {
719 parser
.setDTDHandler(dtdHandler
);
721 if (errorHandler
!= null) {
722 parser
.setErrorHandler(errorHandler
);
724 parser
.setDocumentHandler(this);
730 * Process a qualified (prefixed) name.
732 * <p>If the name has an undeclared prefix, use only the qname
733 * and make an ErrorHandler.error callback in case the app is
736 * @param qName The qualified (prefixed) name.
737 * @param isAttribute true if this is an attribute name.
738 * @return The name split into three parts.
739 * @exception SAXException The client may throw
740 * an exception if there is an error callback.
742 private String
[] processName (String qName
, boolean isAttribute
,
743 boolean useException
)
746 String parts
[] = nsSupport
.processName(qName
, nameParts
,
750 throw makeException("Undeclared prefix: " + qName
);
751 reportError("Undeclared prefix: " + qName
);
752 parts
= new String
[3];
753 parts
[0] = parts
[1] = "";
754 parts
[2] = qName
.intern();
761 * Report a non-fatal error.
763 * @param message The error message.
764 * @exception SAXException The client may throw
767 void reportError (String message
)
770 if (errorHandler
!= null)
771 errorHandler
.error(makeException(message
));
776 * Construct an exception for the current context.
778 * @param message The error message.
780 private SAXParseException
makeException (String message
)
782 if (locator
!= null) {
783 return new SAXParseException(message
, locator
);
785 return new SAXParseException(message
, null, null, -1, -1);
791 * Throw an exception if we are parsing.
793 * <p>Use this method to detect illegal feature or
794 * property changes.</p>
796 * @param type The type of thing (feature or property).
797 * @param name The feature or property name.
798 * @exception SAXNotSupportedException If a
799 * document is currently being parsed.
801 private void checkNotParsing (String type
, String name
)
802 throws SAXNotSupportedException
805 throw new SAXNotSupportedException("Cannot change " +
807 name
+ " while parsing");
814 ////////////////////////////////////////////////////////////////////
816 ////////////////////////////////////////////////////////////////////
818 private NamespaceSupport nsSupport
;
819 private AttributeListAdapter attAdapter
;
821 private boolean parsing
= false;
822 private String nameParts
[] = new String
[3];
824 private Parser parser
= null;
826 private AttributesImpl atts
= null;
829 private boolean namespaces
= true;
830 private boolean prefixes
= false;
831 private boolean uris
= false;
838 EntityResolver entityResolver
= null;
839 DTDHandler dtdHandler
= null;
840 ContentHandler contentHandler
= null;
841 ErrorHandler errorHandler
= null;
845 ////////////////////////////////////////////////////////////////////
846 // Inner class to wrap an AttributeList when not doing NS proc.
847 ////////////////////////////////////////////////////////////////////
851 * Adapt a SAX1 AttributeList as a SAX2 Attributes object.
853 * <p>This class is in the Public Domain, and comes with NO
854 * WARRANTY of any kind.</p>
856 * <p>This wrapper class is used only when Namespace support
857 * is disabled -- it provides pretty much a direct mapping
858 * from SAX1 to SAX2, except that names and types are
859 * interned whenever requested.</p>
861 final class AttributeListAdapter
implements Attributes
865 * Construct a new adapter.
867 AttributeListAdapter ()
873 * Set the embedded AttributeList.
875 * <p>This method must be invoked before any of the others
878 * @param The SAX1 attribute list (with qnames).
880 void setAttributeList (AttributeList qAtts
)
887 * Return the length of the attribute list.
889 * @return The number of attributes in the list.
890 * @see org.xml.sax.Attributes#getLength
892 public int getLength ()
894 return qAtts
.getLength();
899 * Return the Namespace URI of the specified attribute.
901 * @param The attribute's index.
902 * @return Always the empty string.
903 * @see org.xml.sax.Attributes#getURI
905 public String
getURI (int i
)
912 * Return the local name of the specified attribute.
914 * @param The attribute's index.
915 * @return Always the empty string.
916 * @see org.xml.sax.Attributes#getLocalName
918 public String
getLocalName (int i
)
925 * Return the qualified (prefixed) name of the specified attribute.
927 * @param The attribute's index.
928 * @return The attribute's qualified name, internalized.
930 public String
getQName (int i
)
932 return qAtts
.getName(i
).intern();
937 * Return the type of the specified attribute.
939 * @param The attribute's index.
940 * @return The attribute's type as an internalized string.
942 public String
getType (int i
)
944 return qAtts
.getType(i
).intern();
949 * Return the value of the specified attribute.
951 * @param The attribute's index.
952 * @return The attribute's value.
954 public String
getValue (int i
)
956 return qAtts
.getValue(i
);
961 * Look up an attribute index by Namespace name.
963 * @param uri The Namespace URI or the empty string.
964 * @param localName The local name.
965 * @return The attributes index, or -1 if none was found.
966 * @see org.xml.sax.Attributes#getIndex(java.lang.String,java.lang.String)
968 public int getIndex (String uri
, String localName
)
975 * Look up an attribute index by qualified (prefixed) name.
977 * @param qName The qualified name.
978 * @return The attributes index, or -1 if none was found.
979 * @see org.xml.sax.Attributes#getIndex(java.lang.String)
981 public int getIndex (String qName
)
983 int max
= atts
.getLength();
984 for (int i
= 0; i
< max
; i
++) {
985 if (qAtts
.getName(i
).equals(qName
)) {
994 * Look up the type of an attribute by Namespace name.
996 * @param uri The Namespace URI
997 * @param localName The local name.
998 * @return The attribute's type as an internalized string.
1000 public String
getType (String uri
, String localName
)
1007 * Look up the type of an attribute by qualified (prefixed) name.
1009 * @param qName The qualified name.
1010 * @return The attribute's type as an internalized string.
1012 public String
getType (String qName
)
1014 return qAtts
.getType(qName
).intern();
1019 * Look up the value of an attribute by Namespace name.
1021 * @param uri The Namespace URI
1022 * @param localName The local name.
1023 * @return The attribute's value.
1025 public String
getValue (String uri
, String localName
)
1032 * Look up the value of an attribute by qualified (prefixed) name.
1034 * @param qName The qualified name.
1035 * @return The attribute's value.
1037 public String
getValue (String qName
)
1039 return qAtts
.getValue(qName
);
1042 private AttributeList qAtts
;
1046 // end of ParserAdapter.java