| /* SAXDriver.java -- |
| Copyright (C) 1999,2000,2001,2004 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA |
| 02111-1307 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. |
| |
| Portions derived from code which carried the following notice: |
| |
| Copyright (c) 1997, 1998 by Microstar Software Ltd. |
| |
| AElfred is free for both commercial and non-commercial use and |
| redistribution, provided that Microstar's copyright and disclaimer are |
| retained intact. You are free to modify AElfred for your own use and |
| to redistribute AElfred with your modifications, provided that the |
| modifications are clearly documented. |
| |
| This program is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| merchantability or fitness for a particular purpose. Please use it AT |
| YOUR OWN RISK. |
| */ |
| |
| package gnu.xml.aelfred2; |
| |
| import java.io.*; |
| |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.util.Locale; |
| import java.util.Stack; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.xml.sax.*; |
| import org.xml.sax.ext.*; |
| import org.xml.sax.helpers.NamespaceSupport; |
| |
| |
| /** |
| * An enhanced SAX2 version of Microstar's Ælfred XML parser. |
| * The enhancements primarily relate to significant improvements in |
| * conformance to the XML specification, and SAX2 support. Performance |
| * has been improved. See the package level documentation for more |
| * information. |
| * |
| * <table border="1" width='100%' cellpadding='3' cellspacing='0'> |
| * <tr bgcolor='#ccccff'> |
| * <th><font size='+1'>Name</font></th> |
| * <th><font size='+1'>Notes</font></th></tr> |
| * |
| * <tr><td colspan=2><center><em>Features ... URL prefix is |
| * <b>http://xml.org/sax/features/</b></em></center></td></tr> |
| * |
| * <tr><td>(URL)/external-general-entities</td> |
| * <td>Value defaults to <em>true</em></td></tr> |
| * <tr><td>(URL)/external-parameter-entities</td> |
| * <td>Value defaults to <em>true</em></td></tr> |
| * <tr><td>(URL)/is-standalone</td> |
| * <td>(PRELIMINARY) Returns true iff the document's parsing |
| * has started (some non-error event after <em>startDocument()</em> |
| * was reported) and the document's standalone flag is set.</td></tr> |
| * <tr><td>(URL)/namespace-prefixes</td> |
| * <td>Value defaults to <em>false</em> (but XML 1.0 names are |
| * always reported)</td></tr> |
| * <tr><td>(URL)/lexical-handler/parameter-entities</td> |
| * <td>Value is fixed at <em>true</em></td></tr> |
| * <tr><td>(URL)/namespaces</td> |
| * <td>Value defaults to <em>true</em></td></tr> |
| * <tr><td>(URL)/resolve-dtd-uris</td> |
| * <td>(PRELIMINARY) Value defaults to <em>true</em></td></tr> |
| * <tr><td>(URL)/string-interning</td> |
| * <td>Value is fixed at <em>true</em></td></tr> |
| * <tr><td>(URL)/use-attributes2</td> |
| * <td>(PRELIMINARY) Value is fixed at <em>true</em></td></tr> |
| * <tr><td>(URL)/use-entity-resolver2</td> |
| * <td>(PRELIMINARY) Value defaults to <em>true</em></td></tr> |
| * <tr><td>(URL)/validation</td> |
| * <td>Value is fixed at <em>false</em></td></tr> |
| * |
| * <tr><td colspan=2><center><em>Handler Properties ... URL prefix is |
| * <b>http://xml.org/sax/properties/</b></em></center></td></tr> |
| * |
| * <tr><td>(URL)/declaration-handler</td> |
| * <td>A declaration handler may be provided. </td></tr> |
| * <tr><td>(URL)/lexical-handler</td> |
| * <td>A lexical handler may be provided. </td></tr> |
| * </table> |
| * |
| * <p>This parser currently implements the SAX1 Parser API, but |
| * it may not continue to do so in the future. |
| * |
| * @author Written by David Megginson (version 1.2a from Microstar) |
| * @author Updated by David Brownell <dbrownell@users.sourceforge.net> |
| * @see org.xml.sax.Parser |
| */ |
| final public class SAXDriver |
| implements Locator, Attributes2, XMLReader, Parser, AttributeList |
| { |
| |
| private final DefaultHandler2 base = new DefaultHandler2(); |
| private XmlParser parser; |
| |
| private EntityResolver entityResolver = base; |
| private EntityResolver2 resolver2 = null; |
| private ContentHandler contentHandler = base; |
| private DTDHandler dtdHandler = base; |
| private ErrorHandler errorHandler = base; |
| private DeclHandler declHandler = base; |
| private LexicalHandler lexicalHandler = base; |
| |
| private String elementName; |
| private Stack entityStack; |
| |
| // one vector (of object/struct): faster, smaller |
| private List attributesList; |
| |
| private boolean namespaces = true; |
| private boolean xmlNames = false; |
| private boolean extGE = true; |
| private boolean extPE = true; |
| private boolean resolveAll = true; |
| private boolean useResolver2 = true; |
| |
| // package private to allow (read-only) access in XmlParser |
| boolean stringInterning = true; |
| |
| private int attributeCount; |
| private boolean attributes; |
| private String[] nsTemp; |
| private NamespaceSupport prefixStack; |
| |
| // |
| // Constructor. |
| // |
| |
| /** |
| * Constructs a SAX Parser. |
| */ |
| public SAXDriver() |
| { |
| reset(); |
| } |
| |
| private void reset() |
| { |
| elementName = null; |
| entityStack = new Stack(); |
| attributesList = Collections.synchronizedList(new ArrayList()); |
| attributeCount = 0; |
| attributes = false; |
| nsTemp = new String[3]; |
| prefixStack = null; |
| } |
| |
| |
| // |
| // Implementation of org.xml.sax.Parser. |
| // |
| |
| /** |
| * <b>SAX1</b>: Sets the locale used for diagnostics; currently, |
| * only locales using the English language are supported. |
| * @param locale The locale for which diagnostics will be generated |
| */ |
| public void setLocale(Locale locale) |
| throws SAXException |
| { |
| if ("en".equals(locale.getLanguage())) |
| { |
| return; |
| } |
| throw new SAXException ("AElfred2 only supports English locales."); |
| } |
| |
| /** |
| * <b>SAX2</b>: Returns the object used when resolving external |
| * entities during parsing (both general and parameter entities). |
| */ |
| public EntityResolver getEntityResolver() |
| { |
| return (entityResolver == base) ? null : entityResolver; |
| } |
| |
| /** |
| * <b>SAX1, SAX2</b>: Set the entity resolver for this parser. |
| * @param handler The object to receive entity events. |
| */ |
| public void setEntityResolver(EntityResolver resolver) |
| { |
| if (resolver instanceof EntityResolver2) |
| { |
| resolver2 = (EntityResolver2) resolver; |
| } |
| else |
| { |
| resolver2 = null; |
| } |
| if (resolver == null) |
| { |
| resolver = base; |
| } |
| entityResolver = resolver; |
| } |
| |
| /** |
| * <b>SAX2</b>: Returns the object used to process declarations related |
| * to notations and unparsed entities. |
| */ |
| public DTDHandler getDTDHandler() |
| { |
| return (dtdHandler == base) ? null : dtdHandler; |
| } |
| |
| /** |
| * <b>SAX1, SAX2</b>: Set the DTD handler for this parser. |
| * @param handler The object to receive DTD events. |
| */ |
| public void setDTDHandler(DTDHandler handler) |
| { |
| if (handler == null) |
| { |
| handler = base; |
| } |
| this.dtdHandler = handler; |
| } |
| |
| |
| /** |
| * <b>SAX1</b>: Set the document handler for this parser. If a |
| * content handler was set, this document handler will supplant it. |
| * The parser is set to report all XML 1.0 names rather than to |
| * filter out "xmlns" attributes (the "namespace-prefixes" feature |
| * is set to true). |
| * |
| * @deprecated SAX2 programs should use the XMLReader interface |
| * and a ContentHandler. |
| * |
| * @param handler The object to receive document events. |
| */ |
| public void setDocumentHandler(DocumentHandler handler) |
| { |
| contentHandler = new Adapter(handler); |
| xmlNames = true; |
| } |
| |
| /** |
| * <b>SAX2</b>: Returns the object used to report the logical |
| * content of an XML document. |
| */ |
| public ContentHandler getContentHandler() |
| { |
| return (contentHandler == base) ? null : contentHandler; |
| } |
| |
| /** |
| * <b>SAX2</b>: Assigns the object used to report the logical |
| * content of an XML document. If a document handler was set, |
| * this content handler will supplant it (but XML 1.0 style name |
| * reporting may remain enabled). |
| */ |
| public void setContentHandler(ContentHandler handler) |
| { |
| if (handler == null) |
| { |
| handler = base; |
| } |
| contentHandler = handler; |
| } |
| |
| /** |
| * <b>SAX1, SAX2</b>: Set the error handler for this parser. |
| * @param handler The object to receive error events. |
| */ |
| public void setErrorHandler(ErrorHandler handler) |
| { |
| if (handler == null) |
| { |
| handler = base; |
| } |
| this.errorHandler = handler; |
| } |
| |
| /** |
| * <b>SAX2</b>: Returns the object used to receive callbacks for XML |
| * errors of all levels (fatal, nonfatal, warning); this is never null; |
| */ |
| public ErrorHandler getErrorHandler() |
| { |
| return (errorHandler == base) ? null : errorHandler; |
| } |
| |
| /** |
| * <b>SAX1, SAX2</b>: Auxiliary API to parse an XML document, used mostly |
| * when no URI is available. |
| * If you want anything useful to happen, you should set |
| * at least one type of handler. |
| * @param source The XML input source. Don't set 'encoding' unless |
| * you know for a fact that it's correct. |
| * @see #setEntityResolver |
| * @see #setDTDHandler |
| * @see #setContentHandler |
| * @see #setErrorHandler |
| * @exception SAXException The handlers may throw any SAXException, |
| * and the parser normally throws SAXParseException objects. |
| * @exception IOException IOExceptions are normally through through |
| * the parser if there are problems reading the source document. |
| */ |
| public void parse(InputSource source) |
| throws SAXException, IOException |
| { |
| synchronized (base) |
| { |
| parser = new XmlParser(); |
| if (namespaces) |
| { |
| prefixStack = new NamespaceSupport(); |
| } |
| else if (!xmlNames) |
| { |
| throw new IllegalStateException(); |
| } |
| parser.setHandler(this); |
| |
| try |
| { |
| Reader r = source.getCharacterStream(); |
| InputStream in = source.getByteStream(); |
| |
| parser.doParse(source.getSystemId(), |
| source.getPublicId(), |
| r, |
| in, |
| source.getEncoding()); |
| } |
| catch (SAXException e) |
| { |
| throw e; |
| } |
| catch (IOException e) |
| { |
| throw e; |
| } |
| catch (RuntimeException e) |
| { |
| throw e; |
| } |
| catch (Exception e) |
| { |
| throw new SAXParseException(e.getMessage(), this, e); |
| } |
| finally |
| { |
| contentHandler.endDocument(); |
| reset(); |
| } |
| } |
| } |
| |
| /** |
| * <b>SAX1, SAX2</b>: Preferred API to parse an XML document, using a |
| * system identifier (URI). |
| */ |
| public void parse(String systemId) |
| throws SAXException, IOException |
| { |
| parse(new InputSource(systemId)); |
| } |
| |
| // |
| // Implementation of SAX2 "XMLReader" interface |
| // |
| static final String FEATURE = "http://xml.org/sax/features/"; |
| static final String PROPERTY = "http://xml.org/sax/properties/"; |
| |
| /** |
| * <b>SAX2</b>: Tells the value of the specified feature flag. |
| * |
| * @exception SAXNotRecognizedException thrown if the feature flag |
| * is neither built in, nor yet assigned. |
| */ |
| public boolean getFeature(String featureId) |
| throws SAXNotRecognizedException, SAXNotSupportedException |
| { |
| if ((FEATURE + "validation").equals(featureId)) |
| { |
| return false; |
| } |
| |
| // external entities (both types) are optionally included |
| if ((FEATURE + "external-general-entities").equals(featureId)) |
| { |
| return extGE; |
| } |
| if ((FEATURE + "external-parameter-entities").equals(featureId)) |
| { |
| return extPE; |
| } |
| |
| // element/attribute names are as written in document; no mangling |
| if ((FEATURE + "namespace-prefixes").equals(featureId)) |
| { |
| return xmlNames; |
| } |
| |
| // report element/attribute namespaces? |
| if ((FEATURE + "namespaces").equals(featureId)) |
| { |
| return namespaces; |
| } |
| |
| // all PEs and GEs are reported |
| if ((FEATURE + "lexical-handler/parameter-entities").equals(featureId)) |
| { |
| return true; |
| } |
| |
| // default is true |
| if ((FEATURE + "string-interning").equals(featureId)) |
| { |
| return stringInterning; |
| } |
| |
| // EXTENSIONS 1.1 |
| |
| // always returns isSpecified info |
| if ((FEATURE + "use-attributes2").equals(featureId)) |
| { |
| return true; |
| } |
| |
| // meaningful between startDocument/endDocument |
| if ((FEATURE + "is-standalone").equals(featureId)) |
| { |
| if (parser == null) |
| { |
| throw new SAXNotSupportedException(featureId); |
| } |
| return parser.isStandalone(); |
| } |
| |
| // optionally don't absolutize URIs in declarations |
| if ((FEATURE + "resolve-dtd-uris").equals(featureId)) |
| { |
| return resolveAll; |
| } |
| |
| // optionally use resolver2 interface methods, if possible |
| if ((FEATURE + "use-entity-resolver2").equals(featureId)) |
| { |
| return useResolver2; |
| } |
| |
| throw new SAXNotRecognizedException(featureId); |
| } |
| |
| // package private |
| DeclHandler getDeclHandler() |
| { |
| return declHandler; |
| } |
| |
| // package private |
| boolean resolveURIs() |
| { |
| return resolveAll; |
| } |
| |
| /** |
| * <b>SAX2</b>: Returns the specified property. |
| * |
| * @exception SAXNotRecognizedException thrown if the property value |
| * is neither built in, nor yet stored. |
| */ |
| public Object getProperty(String propertyId) |
| throws SAXNotRecognizedException |
| { |
| if ((PROPERTY + "declaration-handler").equals(propertyId)) |
| { |
| return (declHandler == base) ? null : declHandler; |
| } |
| |
| if ((PROPERTY + "lexical-handler").equals(propertyId)) |
| { |
| return (lexicalHandler == base) ? null : lexicalHandler; |
| } |
| |
| // unknown properties |
| throw new SAXNotRecognizedException(propertyId); |
| } |
| |
| /** |
| * <b>SAX2</b>: Sets the state of feature flags in this parser. Some |
| * built-in feature flags are mutable. |
| */ |
| public void setFeature(String featureId, boolean value) |
| throws SAXNotRecognizedException, SAXNotSupportedException |
| { |
| boolean state; |
| |
| // Features with a defined value, we just change it if we can. |
| state = getFeature (featureId); |
| |
| if (state == value) |
| { |
| return; |
| } |
| if (parser != null) |
| { |
| throw new SAXNotSupportedException("not while parsing"); |
| } |
| |
| if ((FEATURE + "namespace-prefixes").equals(featureId)) |
| { |
| // in this implementation, this only affects xmlns reporting |
| xmlNames = value; |
| // forcibly prevent illegal parser state |
| if (!xmlNames) |
| { |
| namespaces = true; |
| } |
| return; |
| } |
| |
| if ((FEATURE + "namespaces").equals(featureId)) |
| { |
| namespaces = value; |
| // forcibly prevent illegal parser state |
| if (!namespaces) |
| { |
| xmlNames = true; |
| } |
| return; |
| } |
| |
| if ((FEATURE + "external-general-entities").equals(featureId)) |
| { |
| extGE = value; |
| return; |
| } |
| if ((FEATURE + "external-parameter-entities").equals(featureId)) |
| { |
| extPE = value; |
| return; |
| } |
| if ((FEATURE + "resolve-dtd-uris").equals(featureId)) |
| { |
| resolveAll = value; |
| return; |
| } |
| |
| if ((FEATURE + "use-entity-resolver2").equals(featureId)) |
| { |
| useResolver2 = value; |
| return; |
| } |
| |
| throw new SAXNotRecognizedException(featureId); |
| } |
| |
| /** |
| * <b>SAX2</b>: Assigns the specified property. Like SAX1 handlers, |
| * these may be changed at any time. |
| */ |
| public void setProperty(String propertyId, Object value) |
| throws SAXNotRecognizedException, SAXNotSupportedException |
| { |
| // see if the property is recognized |
| getProperty(propertyId); |
| |
| // Properties with a defined value, we just change it if we can. |
| |
| if ((PROPERTY + "declaration-handler").equals(propertyId)) |
| { |
| if (value == null) |
| { |
| declHandler = base; |
| } |
| else if (!(value instanceof DeclHandler)) |
| { |
| throw new SAXNotSupportedException(propertyId); |
| } |
| else |
| { |
| declHandler = (DeclHandler) value; |
| } |
| return ; |
| } |
| |
| if ((PROPERTY + "lexical-handler").equals(propertyId)) |
| { |
| if (value == null) |
| { |
| lexicalHandler = base; |
| } |
| else if (!(value instanceof LexicalHandler)) |
| { |
| throw new SAXNotSupportedException(propertyId); |
| } |
| else |
| { |
| lexicalHandler = (LexicalHandler) value; |
| } |
| return; |
| } |
| |
| throw new SAXNotSupportedException(propertyId); |
| } |
| |
| // |
| // This is where the driver receives XmlParser callbacks and translates |
| // them into SAX callbacks. Some more callbacks have been added for |
| // SAX2 support. |
| // |
| |
| void startDocument() |
| throws SAXException |
| { |
| contentHandler.setDocumentLocator(this); |
| contentHandler.startDocument(); |
| attributesList.clear(); |
| } |
| |
| void xmlDecl(String version, |
| String encoding, |
| boolean standalone, |
| String inputEncoding) |
| throws SAXException |
| { |
| if (contentHandler instanceof ContentHandler2) |
| { |
| ((ContentHandler2) contentHandler).xmlDecl(version, |
| encoding, |
| standalone, |
| inputEncoding); |
| } |
| } |
| |
| void skippedEntity(String name) |
| throws SAXException |
| { |
| contentHandler.skippedEntity(name); |
| } |
| |
| InputSource getExternalSubset(String name, String baseURI) |
| throws SAXException, IOException |
| { |
| if (resolver2 == null || !useResolver2 || !extPE) |
| { |
| return null; |
| } |
| return resolver2.getExternalSubset(name, baseURI); |
| } |
| |
| InputSource resolveEntity(boolean isPE, String name, |
| InputSource in, String baseURI) |
| throws SAXException, IOException |
| { |
| InputSource source; |
| |
| // external entities might be skipped |
| if (isPE && !extPE) |
| { |
| return null; |
| } |
| if (!isPE && !extGE) |
| { |
| return null; |
| } |
| |
| // ... or not |
| lexicalHandler.startEntity(name); |
| if (resolver2 != null && useResolver2) |
| { |
| source = resolver2.resolveEntity(name, in.getPublicId(), |
| baseURI, in.getSystemId()); |
| if (source == null) |
| { |
| in.setSystemId(absolutize(baseURI, |
| in.getSystemId(), false)); |
| source = in; |
| } |
| } |
| else |
| { |
| in.setSystemId(absolutize(baseURI, in.getSystemId(), false)); |
| source = entityResolver.resolveEntity(in.getPublicId(), |
| in.getSystemId()); |
| if (source == null) |
| { |
| source = in; |
| } |
| } |
| startExternalEntity(name, source.getSystemId(), true); |
| return source; |
| } |
| |
| // absolutize a system ID relative to the specified base URI |
| // (temporarily) package-visible for external entity decls |
| String absolutize(String baseURI, String systemId, boolean nice) |
| throws MalformedURLException, SAXException |
| { |
| // FIXME normalize system IDs -- when? |
| // - Convert to UTF-8 |
| // - Map reserved and non-ASCII characters to %HH |
| |
| try |
| { |
| if (baseURI == null) |
| { |
| if (XmlParser.uriWarnings) |
| { |
| warn ("No base URI; hope this SYSTEM id is absolute: " |
| + systemId); |
| } |
| return new URL(systemId).toString(); |
| } |
| else |
| { |
| return new URL(new URL(baseURI), systemId).toString(); |
| } |
| } |
| catch (MalformedURLException e) |
| { |
| // Let unknown URI schemes pass through unless we need |
| // the JVM to map them to i/o streams for us... |
| if (!nice) |
| { |
| throw e; |
| } |
| |
| // sometimes sysids for notations or unparsed entities |
| // aren't really URIs... |
| warn("Can't absolutize SYSTEM id: " + e.getMessage()); |
| return systemId; |
| } |
| } |
| |
| void startExternalEntity(String name, String systemId, boolean stackOnly) |
| throws SAXException |
| { |
| // The following warning was deleted because the application has the |
| // option of not setting systemId. Sun's JAXP or Xerces seems to |
| // ignore this case. |
| /* |
| if (systemId == null) |
| warn ("URI was not reported to parser for entity " + name); |
| */ |
| if (!stackOnly) // spliced [dtd] needs startEntity |
| { |
| lexicalHandler.startEntity(name); |
| } |
| entityStack.push(systemId); |
| } |
| |
| void endExternalEntity(String name) |
| throws SAXException |
| { |
| if (!"[document]".equals(name)) |
| { |
| lexicalHandler.endEntity(name); |
| } |
| entityStack.pop(); |
| } |
| |
| void startInternalEntity(String name) |
| throws SAXException |
| { |
| lexicalHandler.startEntity(name); |
| } |
| |
| void endInternalEntity(String name) |
| throws SAXException |
| { |
| lexicalHandler.endEntity(name); |
| } |
| |
| void doctypeDecl(String name, String publicId, String systemId) |
| throws SAXException |
| { |
| lexicalHandler.startDTD(name, publicId, systemId); |
| |
| // ... the "name" is a declaration and should be given |
| // to the DeclHandler (but sax2 doesn't). |
| |
| // the IDs for the external subset are lexical details, |
| // as are the contents of the internal subset; but sax2 |
| // doesn't provide the internal subset "pre-parse" |
| } |
| |
| void notationDecl(String name, String publicId, String systemId, |
| String baseUri) |
| throws SAXException |
| { |
| try |
| { |
| dtdHandler.notationDecl(name, publicId, |
| (resolveAll && systemId != null) |
| ? absolutize(baseUri, systemId, true) |
| : systemId); |
| } |
| catch (IOException e) |
| { |
| // "can't happen" |
| throw new SAXParseException(e.getMessage(), this, e); |
| } |
| } |
| |
| void unparsedEntityDecl(String name, String publicId, String systemId, |
| String baseUri, String notation) |
| throws SAXException |
| { |
| try |
| { |
| dtdHandler.unparsedEntityDecl(name, publicId, |
| resolveAll |
| ? absolutize(baseUri, systemId, true) |
| : systemId, |
| notation); |
| } |
| catch (IOException e) |
| { |
| // "can't happen" |
| throw new SAXParseException(e.getMessage(), this, e); |
| } |
| } |
| |
| void endDoctype() |
| throws SAXException |
| { |
| lexicalHandler.endDTD(); |
| } |
| |
| private void declarePrefix(String prefix, String uri) |
| throws SAXException |
| { |
| int index = uri.indexOf(':'); |
| |
| // many versions of nwalsh docbook stylesheets |
| // have bogus URLs; so this can't be an error... |
| if (index < 1 && uri.length() != 0) |
| { |
| warn("relative URI for namespace: " + uri); |
| } |
| |
| // FIXME: char [0] must be ascii alpha; chars [1..index] |
| // must be ascii alphanumeric or in "+-." [RFC 2396] |
| |
| //Namespace Constraints |
| //name for xml prefix must be http://www.w3.org/XML/1998/namespace |
| boolean prefixEquality = prefix.equals("xml"); |
| boolean uriEquality = uri.equals("http://www.w3.org/XML/1998/namespace"); |
| if ((prefixEquality || uriEquality) && !(prefixEquality && uriEquality)) |
| { |
| fatal("xml is by definition bound to the namespace name " + |
| "http://www.w3.org/XML/1998/namespace"); |
| } |
| |
| //xmlns prefix declaration is illegal but xml prefix declaration is llegal... |
| if (prefixEquality && uriEquality) |
| { |
| return; |
| } |
| |
| //name for xmlns prefix must be http://www.w3.org/2000/xmlns/ |
| prefixEquality = prefix.equals("xmlns"); |
| uriEquality = uri.equals("http://www.w3.org/2000/xmlns/"); |
| if ((prefixEquality || uriEquality) && !(prefixEquality && uriEquality)) |
| { |
| fatal("http://www.w3.org/2000/xmlns/ is by definition bound" + |
| " to prefix xmlns"); |
| } |
| |
| //even if the uri is http://www.w3.org/2000/xmlns/ |
| // it is illegal to declare it |
| if (prefixEquality && uriEquality) |
| { |
| fatal ("declaring the xmlns prefix is illegal"); |
| } |
| |
| uri = uri.intern(); |
| prefixStack.declarePrefix(prefix, uri); |
| contentHandler.startPrefixMapping(prefix, uri); |
| } |
| |
| void attribute(String qname, String value, boolean isSpecified) |
| throws SAXException |
| { |
| if (!attributes) |
| { |
| attributes = true; |
| if (namespaces) |
| { |
| prefixStack.pushContext(); |
| } |
| } |
| |
| // process namespace decls immediately; |
| // then maybe forget this as an attribute |
| if (namespaces) |
| { |
| int index; |
| |
| // default NS declaration? |
| if (stringInterning) |
| { |
| if ("xmlns" == qname) |
| { |
| declarePrefix("", value); |
| if (!xmlNames) |
| { |
| return; |
| } |
| } |
| // NS prefix declaration? |
| else if ((index = qname.indexOf(':')) == 5 |
| && qname.startsWith("xmlns")) |
| { |
| String prefix = qname.substring(6); |
| |
| if (prefix.equals("")) |
| { |
| fatal("missing prefix " + |
| "in namespace declaration attribute"); |
| } |
| if (value.length() == 0) |
| { |
| verror("missing URI in namespace declaration attribute: " |
| + qname); |
| } |
| else |
| { |
| declarePrefix(prefix, value); |
| } |
| if (!xmlNames) |
| { |
| return; |
| } |
| } |
| } |
| else |
| { |
| if ("xmlns".equals(qname)) |
| { |
| declarePrefix("", value); |
| if (!xmlNames) |
| { |
| return; |
| } |
| } |
| // NS prefix declaration? |
| else if ((index = qname.indexOf(':')) == 5 |
| && qname.startsWith("xmlns")) |
| { |
| String prefix = qname.substring(6); |
| |
| if (value.length() == 0) |
| { |
| verror("missing URI in namespace decl attribute: " |
| + qname); |
| } |
| else |
| { |
| declarePrefix(prefix, value); |
| } |
| if (!xmlNames) |
| { |
| return; |
| } |
| } |
| } |
| } |
| // remember this attribute ... |
| attributeCount++; |
| |
| // attribute type comes from querying parser's DTD records |
| attributesList.add(new Attribute(qname, value, isSpecified)); |
| |
| } |
| |
| void startElement(String elname) |
| throws SAXException |
| { |
| ContentHandler handler = contentHandler; |
| |
| // |
| // NOTE: this implementation of namespace support adds something |
| // like six percent to parsing CPU time, in a large (~50 MB) |
| // document that doesn't use namespaces at all. (Measured by PC |
| // sampling, with a bug where endElement processing was omitted.) |
| // [Measurement referred to older implementation, older JVM ...] |
| // |
| // It ought to become notably faster in such cases. Most |
| // costs are the prefix stack calling Hashtable.get() (2%), |
| // String.hashCode() (1.5%) and about 1.3% each for pushing |
| // the context, and two chunks of name processing. |
| // |
| |
| if (!attributes) |
| { |
| if (namespaces) |
| { |
| prefixStack.pushContext(); |
| } |
| } |
| else if (namespaces) |
| { |
| |
| // now we can patch up namespace refs; we saw all the |
| // declarations, so now we'll do the Right Thing |
| Iterator itt = attributesList.iterator(); |
| while (itt.hasNext()) |
| { |
| Attribute attribute = (Attribute) itt.next(); |
| String qname = attribute.name; |
| int index; |
| |
| // default NS declaration? |
| if (stringInterning) |
| { |
| if ("xmlns" == qname) |
| { |
| continue; |
| } |
| } |
| else |
| { |
| if ("xmlns".equals(qname)) |
| { |
| continue; |
| } |
| } |
| //Illegal in the new Namespaces Draft |
| //should it be only in 1.1 docs?? |
| if (qname.equals (":")) |
| { |
| fatal("namespace names consisting of a single colon " + |
| "character are invalid"); |
| } |
| index = qname.indexOf(':'); |
| |
| // NS prefix declaration? |
| if (index == 5 && qname.startsWith("xmlns")) |
| { |
| continue; |
| } |
| |
| // it's not a NS decl; patch namespace info items |
| if (prefixStack.processName(qname, nsTemp, true) == null) |
| { |
| fatal("undeclared attribute prefix in: " + qname); |
| } |
| else |
| { |
| attribute.nameSpace = nsTemp[0]; |
| attribute.localName = nsTemp[1]; |
| } |
| } |
| } |
| |
| // save element name so attribute callbacks work |
| elementName = elname; |
| if (namespaces) |
| { |
| if (prefixStack.processName(elname, nsTemp, false) == null) |
| { |
| fatal("undeclared element prefix in: " + elname); |
| nsTemp[0] = nsTemp[1] = ""; |
| } |
| handler.startElement(nsTemp[0], nsTemp[1], elname, this); |
| } |
| else |
| { |
| handler.startElement("", "", elname, this); |
| } |
| // elementName = null; |
| |
| // elements with no attributes are pretty common! |
| if (attributes) |
| { |
| attributesList.clear(); |
| attributeCount = 0; |
| attributes = false; |
| } |
| } |
| |
| void endElement(String elname) |
| throws SAXException |
| { |
| ContentHandler handler = contentHandler; |
| |
| if (!namespaces) |
| { |
| handler.endElement("", "", elname); |
| return; |
| } |
| prefixStack.processName(elname, nsTemp, false); |
| handler.endElement(nsTemp[0], nsTemp[1], elname); |
| |
| Enumeration prefixes = prefixStack.getDeclaredPrefixes(); |
| |
| while (prefixes.hasMoreElements()) |
| { |
| handler.endPrefixMapping((String) prefixes.nextElement()); |
| } |
| prefixStack.popContext(); |
| } |
| |
| void startCDATA() |
| throws SAXException |
| { |
| lexicalHandler.startCDATA(); |
| } |
| |
| void charData(char[] ch, int start, int length) |
| throws SAXException |
| { |
| contentHandler.characters(ch, start, length); |
| } |
| |
| void endCDATA() |
| throws SAXException |
| { |
| lexicalHandler.endCDATA(); |
| } |
| |
| void ignorableWhitespace(char[] ch, int start, int length) |
| throws SAXException |
| { |
| contentHandler.ignorableWhitespace(ch, start, length); |
| } |
| |
| void processingInstruction(String target, String data) |
| throws SAXException |
| { |
| contentHandler.processingInstruction(target, data); |
| } |
| |
| void comment(char[] ch, int start, int length) |
| throws SAXException |
| { |
| if (lexicalHandler != base) |
| { |
| lexicalHandler.comment(ch, start, length); |
| } |
| } |
| |
| void fatal(String message) |
| throws SAXException |
| { |
| SAXParseException fatal; |
| |
| fatal = new SAXParseException(message, this); |
| errorHandler.fatalError(fatal); |
| |
| // Even if the application can continue ... we can't! |
| throw fatal; |
| } |
| |
| // We can safely report a few validity errors that |
| // make layered SAX2 DTD validation more conformant |
| void verror(String message) |
| throws SAXException |
| { |
| SAXParseException err; |
| |
| err = new SAXParseException(message, this); |
| errorHandler.error(err); |
| } |
| |
| void warn(String message) |
| throws SAXException |
| { |
| SAXParseException err; |
| |
| err = new SAXParseException(message, this); |
| errorHandler.warning(err); |
| } |
| |
| // |
| // Implementation of org.xml.sax.Attributes. |
| // |
| |
| /** |
| * <b>SAX1 AttributeList, SAX2 Attributes</b> method |
| * (don't invoke on parser); |
| */ |
| public int getLength() |
| { |
| return attributesList.size(); |
| } |
| |
| /** |
| * <b>SAX2 Attributes</b> method (don't invoke on parser); |
| */ |
| public String getURI(int index) |
| { |
| if (index < 0 || index >= attributesList.size()) |
| { |
| return null; |
| } |
| return ((Attribute) attributesList.get(index)).nameSpace; |
| } |
| |
| /** |
| * <b>SAX2 Attributes</b> method (don't invoke on parser); |
| */ |
| public String getLocalName(int index) |
| { |
| if (index < 0 || index >= attributesList.size()) |
| { |
| return null; |
| } |
| Attribute attr = (Attribute) attributesList.get(index); |
| // FIXME attr.localName is sometimes null, why? |
| if (namespaces && attr.localName == null) |
| { |
| // XXX fix this here for now |
| int ci = attr.name.indexOf(':'); |
| attr.localName = (ci == -1) ? attr.name : |
| attr.name.substring(ci + 1); |
| } |
| return (attr.localName == null) ? "" : attr.localName; |
| } |
| |
| /** |
| * <b>SAX2 Attributes</b> method (don't invoke on parser); |
| */ |
| public String getQName(int index) |
| { |
| if (index < 0 || index >= attributesList.size()) |
| { |
| return null; |
| } |
| Attribute attr = (Attribute) attributesList.get(index); |
| return (attr.name == null) ? "" : attr.name; |
| } |
| |
| /** |
| * <b>SAX1 AttributeList</b> method (don't invoke on parser); |
| */ |
| public String getName(int index) |
| { |
| return getQName(index); |
| } |
| |
| /** |
| * <b>SAX1 AttributeList, SAX2 Attributes</b> method |
| * (don't invoke on parser); |
| */ |
| public String getType(int index) |
| { |
| if (index < 0 || index >= attributesList.size()) |
| { |
| return null; |
| } |
| String type = parser.getAttributeType(elementName, getQName(index)); |
| if (type == null) |
| { |
| return "CDATA"; |
| } |
| // ... use DeclHandler.attributeDecl to see enumerations |
| if (type == "ENUMERATION") |
| { |
| return "NMTOKEN"; |
| } |
| return type; |
| } |
| |
| /** |
| * <b>SAX1 AttributeList, SAX2 Attributes</b> method |
| * (don't invoke on parser); |
| */ |
| public String getValue(int index) |
| { |
| if (index < 0 || index >= attributesList.size()) |
| { |
| return null; |
| } |
| return ((Attribute) attributesList.get(index)).value; |
| } |
| |
| /** |
| * <b>SAX2 Attributes</b> method (don't invoke on parser); |
| */ |
| public int getIndex(String uri, String local) |
| { |
| int length = getLength(); |
| |
| for (int i = 0; i < length; i++) |
| { |
| if (!getURI(i).equals(uri)) |
| { |
| continue; |
| } |
| if (getLocalName(i).equals(local)) |
| { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * <b>SAX2 Attributes</b> method (don't invoke on parser); |
| */ |
| public int getIndex(String xmlName) |
| { |
| int length = getLength(); |
| |
| for (int i = 0; i < length; i++) |
| { |
| if (getQName(i).equals(xmlName)) |
| { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * <b>SAX2 Attributes</b> method (don't invoke on parser); |
| */ |
| public String getType(String uri, String local) |
| { |
| int index = getIndex(uri, local); |
| |
| if (index < 0) |
| { |
| return null; |
| } |
| return getType(index); |
| } |
| |
| /** |
| * <b>SAX1 AttributeList, SAX2 Attributes</b> method |
| * (don't invoke on parser); |
| */ |
| public String getType(String xmlName) |
| { |
| int index = getIndex(xmlName); |
| |
| if (index < 0) |
| { |
| return null; |
| } |
| return getType(index); |
| } |
| |
| /** |
| * <b>SAX Attributes</b> method (don't invoke on parser); |
| */ |
| public String getValue(String uri, String local) |
| { |
| int index = getIndex(uri, local); |
| |
| if (index < 0) |
| { |
| return null; |
| } |
| return getValue(index); |
| } |
| |
| /** |
| * <b>SAX1 AttributeList, SAX2 Attributes</b> method |
| * (don't invoke on parser); |
| */ |
| public String getValue(String xmlName) |
| { |
| int index = getIndex(xmlName); |
| |
| if (index < 0) |
| { |
| return null; |
| } |
| return getValue(index); |
| } |
| |
| // |
| // Implementation of org.xml.sax.ext.Attributes2 |
| // |
| |
| /** @return false unless the attribute was declared in the DTD. |
| * @throws java.lang.ArrayIndexOutOfBoundsException |
| * When the supplied index does not identify an attribute. |
| */ |
| public boolean isDeclared(int index) |
| { |
| if (index < 0 || index >= attributeCount) |
| { |
| throw new ArrayIndexOutOfBoundsException(); |
| } |
| String type = parser.getAttributeType(elementName, getQName(index)); |
| return (type != null); |
| } |
| |
| /** @return false unless the attribute was declared in the DTD. |
| * @throws java.lang.IllegalArgumentException |
| * When the supplied names do not identify an attribute. |
| */ |
| public boolean isDeclared(String qName) |
| { |
| int index = getIndex(qName); |
| if (index < 0) |
| { |
| throw new IllegalArgumentException(); |
| } |
| String type = parser.getAttributeType(elementName, qName); |
| return (type != null); |
| } |
| |
| /** @return false unless the attribute was declared in the DTD. |
| * @throws java.lang.IllegalArgumentException |
| * When the supplied names do not identify an attribute. |
| */ |
| public boolean isDeclared(String uri, String localName) |
| { |
| int index = getIndex(uri, localName); |
| return isDeclared(index); |
| } |
| |
| /** |
| * <b>SAX-ext Attributes2</b> method (don't invoke on parser); |
| */ |
| public boolean isSpecified(int index) |
| { |
| return ((Attribute) attributesList.get(index)).specified; |
| } |
| |
| /** |
| * <b>SAX-ext Attributes2</b> method (don't invoke on parser); |
| */ |
| public boolean isSpecified(String uri, String local) |
| { |
| int index = getIndex (uri, local); |
| return isSpecified(index); |
| } |
| |
| /** |
| * <b>SAX-ext Attributes2</b> method (don't invoke on parser); |
| */ |
| public boolean isSpecified(String xmlName) |
| { |
| int index = getIndex (xmlName); |
| return isSpecified(index); |
| } |
| |
| // |
| // Implementation of org.xml.sax.Locator. |
| // |
| |
| /** |
| * <b>SAX Locator</b> method (don't invoke on parser); |
| */ |
| public String getPublicId() |
| { |
| return null; // FIXME track public IDs too |
| } |
| |
| /** |
| * <b>SAX Locator</b> method (don't invoke on parser); |
| */ |
| public String getSystemId() |
| { |
| if (entityStack.empty()) |
| { |
| return null; |
| } |
| else |
| { |
| return (String) entityStack.peek(); |
| } |
| } |
| |
| /** |
| * <b>SAX Locator</b> method (don't invoke on parser); |
| */ |
| public int getLineNumber() |
| { |
| return parser.getLineNumber(); |
| } |
| |
| /** |
| * <b>SAX Locator</b> method (don't invoke on parser); |
| */ |
| public int getColumnNumber() |
| { |
| return parser.getColumnNumber(); |
| } |
| |
| // adapter between SAX2 content handler and SAX1 document handler callbacks |
| private static class Adapter |
| implements ContentHandler |
| { |
| |
| private DocumentHandler docHandler; |
| |
| Adapter(DocumentHandler dh) |
| { |
| docHandler = dh; |
| } |
| |
| public void setDocumentLocator(Locator l) |
| { |
| docHandler.setDocumentLocator(l); |
| } |
| |
| public void startDocument() |
| throws SAXException |
| { |
| docHandler.startDocument(); |
| } |
| |
| public void processingInstruction(String target, String data) |
| throws SAXException |
| { |
| docHandler.processingInstruction(target, data); |
| } |
| |
| public void startPrefixMapping(String prefix, String uri) |
| { |
| /* ignored */ |
| } |
| |
| public void startElement(String namespace, |
| String local, |
| String name, |
| Attributes attrs) |
| throws SAXException |
| { |
| docHandler.startElement(name, (AttributeList) attrs); |
| } |
| |
| public void characters(char[] buf, int offset, int len) |
| throws SAXException |
| { |
| docHandler.characters(buf, offset, len); |
| } |
| |
| public void ignorableWhitespace(char[] buf, int offset, int len) |
| throws SAXException |
| { |
| docHandler.ignorableWhitespace(buf, offset, len); |
| } |
| |
| public void skippedEntity(String name) |
| { |
| /* ignored */ |
| } |
| |
| public void endElement(String u, String l, String name) |
| throws SAXException |
| { |
| docHandler.endElement(name); |
| } |
| |
| public void endPrefixMapping(String prefix) |
| { |
| /* ignored */ |
| } |
| |
| public void endDocument() |
| throws SAXException |
| { |
| docHandler.endDocument(); |
| } |
| } |
| |
| private static class Attribute |
| { |
| |
| String name; |
| String value; |
| String nameSpace; |
| String localName; |
| boolean specified; |
| |
| Attribute(String name, String value, boolean specified) |
| { |
| this.name = name; |
| this.value = value; |
| this.nameSpace = ""; |
| this.specified = specified; |
| } |
| |
| } |
| |
| } |