| /* 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., 51 Franklin Street, Fifth Floor, Boston, MA | |
| 02110-1301 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 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(), | |
| entityResolver != base)); | |
| 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; | |
| } | |
| } | |
| } |