summaryrefslogtreecommitdiff
path: root/libjava/classpath/gnu/xml/util/XCat.java
diff options
context:
space:
mode:
Diffstat (limited to 'libjava/classpath/gnu/xml/util/XCat.java')
-rw-r--r--libjava/classpath/gnu/xml/util/XCat.java1611
1 files changed, 1611 insertions, 0 deletions
diff --git a/libjava/classpath/gnu/xml/util/XCat.java b/libjava/classpath/gnu/xml/util/XCat.java
new file mode 100644
index 000000000..ea23ad682
--- /dev/null
+++ b/libjava/classpath/gnu/xml/util/XCat.java
@@ -0,0 +1,1611 @@
+/* XCat.java --
+ Copyright (C) 2001 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. */
+
+
+package gnu.xml.util;
+
+import gnu.java.lang.CPStringBuilder;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.StringTokenizer;
+import java.util.Stack;
+import java.util.Vector;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.XMLReader;
+
+import org.xml.sax.ext.DefaultHandler2;
+import org.xml.sax.ext.EntityResolver2;
+
+import org.xml.sax.helpers.XMLReaderFactory;
+
+/**
+ * Packages <a href=
+ "http://www.oasis-open.org/committees/entity/spec-2001-08-06.html"
+ >OASIS XML Catalogs</a>,
+ * primarily for entity resolution by parsers.
+ * That specification defines an XML syntax for mappings between
+ * identifiers declared in DTDs (particularly PUBLIC identifiers) and
+ * locations. SAX has always supported such mappings, but conventions for
+ * an XML file syntax to maintain them have previously been lacking.
+ *
+ * <p> This has three main operational modes. The primary intended mode is
+ * to create a resolver, then preloading it with one or more site-standard
+ * catalogs before using it with one or more SAX parsers: <pre>
+ * XCat catalog = new XCat ();
+ * catalog.setErrorHandler (diagnosticErrorHandler);
+ * catalog.loadCatalog ("file:/local/catalogs/catalog.cat");
+ * catalog.loadCatalog ("http://shared/catalog.cat");
+ * ...
+ * catalog.disableLoading ();
+ * parser1.setEntityResolver (catalog);
+ * parser2.setEntityResolver (catalog);
+ * ...</pre>
+ *
+ * <p>A second mode is to arrange that your application uses instances of
+ * this class as its entity resolver, and automatically loads catalogs
+ * referenced by <em>&lt;?oasis-xml-catalog...?&gt;</em> processing
+ * instructions found before the DTD in documents it parses.
+ * It would then discard the resolver after each parse.
+ *
+ * <p> A third mode applies catalogs in contexts other than entity
+ * resolution for parsers.
+ * The {@link #resolveURI resolveURI()} method supports resolving URIs
+ * stored in XML application data, rather than inside DTDs.
+ * Catalogs would be loaded as shown above, and the catalog could
+ * be used concurrently for parser entity resolution and for
+ * application URI resolution.
+ * </p>
+ *
+ * <center><hr width='70%'></center>
+ *
+ * <p>Errors in catalogs implicitly loaded (during resolution) are ignored
+ * beyond being reported through any <em>ErrorHandler</em> assigned using
+ * {@link #setErrorHandler setErrorHandler()}. SAX exceptions
+ * thrown from such a handler won't abort resolution, although throwing a
+ * <em>RuntimeException</em> or <em>Error</em> will normally abort both
+ * resolution and parsing. Useful diagnostic information is available to
+ * any <em>ErrorHandler</em> used to report problems, or from any exception
+ * thrown from an explicit {@link #loadCatalog loadCatalog()} invocation.
+ * Applications can use that information as troubleshooting aids.
+ *
+ * <p>While this class requires <em>SAX2 Extensions 1.1</em> classes in
+ * its class path, basic functionality does not require using a SAX2
+ * parser that supports the extended entity resolution functionality.
+ * See the original SAX1
+ * {@link #resolveEntity(java.lang.String,java.lang.String) resolveEntity()}
+ * method for a list of restrictions which apply when it is used with
+ * older SAX parsers.
+ *
+ * @see EntityResolver2
+ *
+ * @author David Brownell
+ */
+public class XCat implements EntityResolver2
+{
+ private Catalog catalogs [];
+ private boolean usingPublic = true;
+ private boolean loadingPermitted = true;
+ private boolean unified = true;
+ private String parserClass;
+ private ErrorHandler errorHandler;
+
+ // private EntityResolver next; // chain to next if we fail...
+
+ //
+ // NOTE: This is a straightforward implementation, and if
+ // there are lots of "nextCatalog" or "delegate*" entries
+ // in use, two tweaks would be worth considering:
+ //
+ // - Centralize some sort of cache (key by URI) for individual
+ // resolvers. That'd avoid multiple copies of a given catalog.
+ //
+ // - Have resolution track what catalogs (+modes) have been
+ // searched. This would support loop detection.
+ //
+
+
+ /**
+ * Initializes without preloading a catalog.
+ * This API is convenient when you may want to arrange that catalogs
+ * are automatically loaded when explicitly referenced in documents,
+ * using the <em>oasis-xml-catalog</em> processing instruction.
+ * In such cases you won't usually be able to preload catalogs.
+ */
+ public XCat () { }
+
+ /**
+ * Initializes, and preloads a catalog using the default SAX parser.
+ * This API is convenient when you operate with one or more standard
+ * catalogs.
+ *
+ * <p> This just delegates to {@link #loadCatalog loadCatalog()};
+ * see it for exception information.
+ *
+ * @param uri absolute URI for the catalog file.
+ */
+ public XCat (String uri)
+ throws SAXException, IOException
+ { loadCatalog (uri); }
+
+
+ /**
+ * Loads an OASIS XML Catalog.
+ * It is appended to the list of currently active catalogs, or
+ * reloaded if a catalog with the same URI was already loaded.
+ * Callers have control over what parser is used, how catalog parsing
+ * errors are reported, and whether URIs will be resolved consistently.
+ *
+ * <p> The OASIS specification says that errors detected when loading
+ * catalogs "must recover by ignoring the catalog entry file that
+ * failed, and proceeding." In this API, that action can be the
+ * responsibility of applications, when they explicitly load any
+ * catalog using this method.
+ *
+ * <p>Note that catalogs referenced by this one will not be loaded
+ * at this time. Catalogs referenced through <em>nextCatalog</em>
+ * or <em>delegate*</em> elements are normally loaded only if needed.
+ *
+ * @see #setErrorHandler
+ * @see #setParserClass
+ * @see #setUnified
+ *
+ * @param uri absolute URI for the catalog file.
+ *
+ * @exception IOException As thrown by the parser, typically to
+ * indicate problems reading data from that URI.
+ * @exception SAXException As thrown by the parser, typically to
+ * indicate problems parsing data from that URI. It may also
+ * be thrown if the parser doesn't support necessary handlers.
+ * @exception IllegalStateException When attempting to load a
+ * catalog after loading has been {@link #disableLoading disabled},
+ * such as after any entity or URI lookup has been performed.
+ */
+ public synchronized void loadCatalog (String uri)
+ throws SAXException, IOException
+ {
+ Catalog catalog;
+ int index = -1;
+
+ if (!loadingPermitted)
+ throw new IllegalStateException ();
+
+ uri = normalizeURI (uri);
+ if (catalogs != null) {
+ // maybe just reload
+ for (index = 0; index < catalogs.length; index++)
+ if (uri.equals (catalogs [index].catalogURI))
+ break;
+ }
+ catalog = loadCatalog (parserClass, errorHandler, uri, unified);
+
+ // add to list of catalogs
+ if (catalogs == null) {
+ index = 0;
+ catalogs = new Catalog [1];
+ } else if (index == catalogs.length) {
+ Catalog tmp [];
+
+ tmp = new Catalog [index + 1];
+ System.arraycopy (catalogs, 0, tmp, 0, index);
+ catalogs = tmp;
+ }
+ catalogs [index] = catalog;
+ }
+
+
+ /**
+ * "New Style" external entity resolution for parsers.
+ * Calls to this method prevent explicit loading of additional catalogs
+ * using {@link #loadCatalog loadCatalog()}.
+ *
+ * <p>This supports the full core catalog functionality for locating
+ * (and relocating) parsed entities that have been declared in a
+ * document's DTD.
+ *
+ * @param name Entity name, such as "dudley", "%nell", or "[dtd]".
+ * @param publicId Either a normalized public ID, or null.
+ * @param baseURI Absolute base URI associated with systemId.
+ * @param systemId URI found in entity declaration (may be
+ * relative to baseURI).
+ *
+ * @return Input source for accessing the external entity, or null
+ * if no mapping was found. The input source may have opened
+ * the stream, and will have a fully resolved URI.
+ *
+ * @see #getExternalSubset
+ */
+ public InputSource resolveEntity (
+ String name, // UNUSED ... systemId is always non-null
+ String publicId,
+ String baseURI, // UNUSED ... it just lets sysId be relative
+ String systemId
+ ) throws SAXException, IOException
+ {
+ if (loadingPermitted)
+ disableLoading ();
+
+ try {
+ // steps as found in OASIS XML catalog spec 7.1.2
+ // steps 1, 8 involve looping over the list of catalogs
+ for (int i = 0; i < catalogs.length; i++) {
+ InputSource retval;
+ retval = catalogs [i].resolve (usingPublic, publicId, systemId);
+ if (retval != null)
+ return retval;
+ }
+ } catch (DoneDelegation x) {
+ // done!
+ }
+ // step 9 involves returning "no match"
+ return null;
+ }
+
+
+ /**
+ * "New Style" parser callback to add an external subset.
+ * For documents that don't include an external subset, this may
+ * return one according to <em>doctype</em> catalog entries.
+ * (This functionality is not a core part of the OASIS XML Catalog
+ * specification, though it's presented in an appendix.)
+ * If no such entry is defined, this returns null to indicate that
+ * this document will not be modified to include such a subset.
+ * Calls to this method prevent explicit loading of additional catalogs
+ * using {@link #loadCatalog loadCatalog()}.
+ *
+ * <p><em>Warning:</em> That catalog functionality can be dangerous.
+ * It can provide definitions of general entities, and thereby mask
+ * certain well formedess errors.
+ *
+ * @param name Name of the document element, either as declared in
+ * a DOCTYPE declaration or as observed in the text.
+ * @param baseURI Document's base URI (absolute).
+ *
+ * @return Input source for accessing the external subset, or null
+ * if no mapping was found. The input source may have opened
+ * the stream, and will have a fully resolved URI.
+ */
+ public InputSource getExternalSubset (String name, String baseURI)
+ throws SAXException, IOException
+ {
+ if (loadingPermitted)
+ disableLoading ();
+ try {
+ for (int i = 0; i < catalogs.length; i++) {
+ InputSource retval = catalogs [i].getExternalSubset (name);
+ if (retval != null)
+ return retval;
+ }
+ } catch (DoneDelegation x) {
+ // done!
+ }
+ return null;
+ }
+
+
+ /**
+ * "Old Style" external entity resolution for parsers.
+ * This API provides only core functionality.
+ * Calls to this method prevent explicit loading of additional catalogs
+ * using {@link #loadCatalog loadCatalog()}.
+ *
+ * <p>The functional limitations of this interface include:</p><ul>
+ *
+ * <li>Since system IDs will be absolutized before the resolver
+ * sees them, matching against relative URIs won't work.
+ * This may affect <em>system</em>, <em>rewriteSystem</em>,
+ * and <em>delegateSystem</em> catalog entries.
+ *
+ * <li>Because of that absolutization, documents declaring entities
+ * with system IDs using URI schemes that the JVM does not recognize
+ * may be unparsable. URI schemes such as <em>file:/</em>,
+ * <em>http://</em>, <em>https://</em>, and <em>ftp://</em>
+ * will usually work reliably.
+ *
+ * <li>Because missing external subsets can't be provided, the
+ * <em>doctype</em> catalog entries will be ignored.
+ * (The {@link #getExternalSubset getExternalSubset()} method is
+ * a "New Style" resolution option.)
+ *
+ * </ul>
+ *
+ * <p>Applications can tell whether this limited functionality will be
+ * used: if the feature flag associated with the {@link EntityResolver2}
+ * interface is not <em>true</em>, the limitations apply. Applications
+ * can't usually know whether a given document and catalog will trigger
+ * those limitations. The issue can only be bypassed by operational
+ * procedures such as not using catalogs or documents which involve
+ * those features.
+ *
+ * @param publicId Either a normalized public ID, or null
+ * @param systemId Always an absolute URI.
+ *
+ * @return Input source for accessing the external entity, or null
+ * if no mapping was found. The input source may have opened
+ * the stream, and will have a fully resolved URI.
+ */
+ final public InputSource resolveEntity (String publicId, String systemId)
+ throws SAXException, IOException
+ {
+ return resolveEntity (null, publicId, null, systemId);
+ }
+
+
+ /**
+ * Resolves a URI reference that's not defined to the DTD.
+ * This is intended for use with URIs found in document text, such as
+ * <em>xml-stylesheet</em> processing instructions and in attribute
+ * values, where they are not recognized as URIs by XML parsers.
+ * Calls to this method prevent explicit loading of additional catalogs
+ * using {@link #loadCatalog loadCatalog()}.
+ *
+ * <p>This functionality is supported by the OASIS XML Catalog
+ * specification, but will never be invoked by an XML parser.
+ * It corresponds closely to functionality for mapping system
+ * identifiers for entities declared in DTDs; closely enough that
+ * this implementation's default behavior is that they be
+ * identical, to minimize potential confusion.
+ *
+ * <p>This method could be useful when implementing the
+ * {@link javax.xml.transform.URIResolver} interface, wrapping the
+ * input source in a {@link javax.xml.transform.sax.SAXSource}.
+ *
+ * @see #isUnified
+ * @see #setUnified
+ *
+ * @param baseURI The relevant base URI as specified by the XML Base
+ * specification. This recognizes <em>xml:base</em> attributes
+ * as overriding the actual (physical) base URI.
+ * @param uri Either an absolute URI, or one relative to baseURI
+ *
+ * @return Input source for accessing the mapped URI, or null
+ * if no mapping was found. The input source may have opened
+ * the stream, and will have a fully resolved URI.
+ */
+ public InputSource resolveURI (String baseURI, String uri)
+ throws SAXException, IOException
+ {
+ if (loadingPermitted)
+ disableLoading ();
+
+ // NOTE: baseURI isn't used here, but caller MUST have it,
+ // and heuristics _might_ use it in the future ... plus,
+ // it's symmetric with resolveEntity ().
+
+ // steps 1, 6 involve looping
+ try {
+ for (int i = 0; i < catalogs.length; i++) {
+ InputSource tmp = catalogs [i].resolveURI (uri);
+ if (tmp != null)
+ return tmp;
+ }
+ } catch (DoneDelegation x) {
+ // done
+ }
+ // step 7 reports no match
+ return null;
+ }
+
+
+ /**
+ * Records that catalog loading is no longer permitted.
+ * Loading is automatically disabled when lookups are performed,
+ * and should be manually disabled when <em>startDTD()</em> (or
+ * any other DTD declaration callback) is invoked, or at the latest
+ * when the document root element is seen.
+ */
+ public synchronized void disableLoading ()
+ {
+ // NOTE: this method and loadCatalog() are synchronized
+ // so that it's impossible to load (top level) catalogs
+ // after lookups start. Likewise, deferred loading is also
+ // synchronized (for "next" and delegated catalogs) to
+ // ensure that parsers can share resolvers.
+ loadingPermitted = false;
+ }
+
+
+ /**
+ * Returns the error handler used to report catalog errors.
+ * Null is returned if the parser's default error handling
+ * will be used.
+ *
+ * @see #setErrorHandler
+ */
+ public ErrorHandler getErrorHandler ()
+ { return errorHandler; }
+
+ /**
+ * Assigns the error handler used to report catalog errors.
+ * These errors may come either from the SAX2 parser or
+ * from the catalog parsing code driven by the parser.
+ *
+ * <p> If you're sharing the resolver between parsers, don't
+ * change this once lookups have begun.
+ *
+ * @see #getErrorHandler
+ *
+ * @param parser The error handler, or null saying to use the default
+ * (no diagnostics, and only fatal errors terminate loading).
+ */
+ public void setErrorHandler (ErrorHandler handler)
+ { errorHandler = handler; }
+
+
+ /**
+ * Returns the name of the SAX2 parser class used to parse catalogs.
+ * Null is returned if the system default is used.
+ * @see #setParserClass
+ */
+ public String getParserClass ()
+ { return parserClass; }
+
+ /**
+ * Names the SAX2 parser class used to parse catalogs.
+ *
+ * <p> If you're sharing the resolver between parsers, don't change
+ * this once lookups have begun.
+ *
+ * <p> Note that in order to properly support the <em>xml:base</em>
+ * attribute and relative URI resolution, the SAX parser used to parse
+ * the catalog must provide a {@link Locator} and support the optional
+ * declaration and lexical handlers.
+ *
+ * @see #getParserClass
+ *
+ * @param parser The parser class name, or null saying to use the
+ * system default SAX2 parser.
+ */
+ public void setParserClass (String parser)
+ { parserClass = parser; }
+
+
+ /**
+ * Returns true (the default) if all methods resolve
+ * a given URI in the same way.
+ * Returns false if calls resolving URIs as entities (such as
+ * {@link #resolveEntity resolveEntity()}) use different catalog entries
+ * than those resolving them as URIs ({@link #resolveURI resolveURI()}),
+ * which will generally produce different results.
+ *
+ * <p>The OASIS XML Catalog specification defines two related schemes
+ * to map URIs "as URIs" or "as system IDs".
+ * URIs use <em>uri</em>, <em>rewriteURI</em>, and <em>delegateURI</em>
+ * elements. System IDs do the same things with <em>systemId</em>,
+ * <em>rewriteSystemId</em>, and <em>delegateSystemId</em>.
+ * It's confusing and error prone to maintain two parallel copies of
+ * such data. Accordingly, this class makes that behavior optional.
+ * The <em>unified</em> interpretation of URI mappings is preferred,
+ * since it prevents surprises where one URI gets mapped to different
+ * contents depending on whether the reference happens to have come
+ * from a DTD (or not).
+ *
+ * @see #setUnified
+ */
+ public boolean isUnified ()
+ { return unified; }
+
+ /**
+ * Assigns the value of the flag returned by {@link #isUnified}.
+ * Set it to false to be strictly conformant with the OASIS XML Catalog
+ * specification. Set it to true to make all mappings for a given URI
+ * give the same result, regardless of the reason for the mapping.
+ *
+ * <p>Don't change this once you've loaded the first catalog.
+ *
+ * @param value new flag setting
+ */
+ public void setUnified (boolean value)
+ { unified = value; }
+
+
+ /**
+ * Returns true (the default) if a catalog's public identifier
+ * mappings will be used.
+ * When false is returned, such mappings are ignored except when
+ * system IDs are discarded, such as for
+ * entities using the <em>urn:publicid:</em> URI scheme in their
+ * system identifiers. (See RFC 3151 for information about that
+ * URI scheme. Using it in system identifiers may not work well
+ * with many SAX parsers unless the <em>resolve-dtd-uris</em>
+ * feature flag is set to false.)
+ * @see #setUsingPublic
+ */
+ public boolean isUsingPublic ()
+ { return usingPublic; }
+
+ /**
+ * Specifies which catalog search mode is used.
+ * By default, public identifier mappings are able to override system
+ * identifiers when both are available.
+ * Applications may choose to ignore public
+ * identifier mappings in such cases, so that system identifiers
+ * declared in DTDs will only be overridden by an explicit catalog
+ * match for that system ID.
+ *
+ * <p> If you're sharing the resolver between parsers, don't
+ * change this once lookups have begun.
+ * @see #isUsingPublic
+ *
+ * @param value true to always use public identifier mappings,
+ * false to only use them for system ids using the <em>urn:publicid:</em>
+ * URI scheme.
+ */
+ public void setUsingPublic (boolean value)
+ { usingPublic = value; }
+
+
+
+ // hmm, what's this do? :)
+ private static Catalog loadCatalog (
+ String parserClass,
+ ErrorHandler eh,
+ String uri,
+ boolean unified
+ ) throws SAXException, IOException
+ {
+ XMLReader parser;
+ Loader loader;
+ boolean doesIntern = false;
+
+ if (parserClass == null)
+ parser = XMLReaderFactory.createXMLReader ();
+ else
+ parser = XMLReaderFactory.createXMLReader (parserClass);
+ if (eh != null)
+ parser.setErrorHandler (eh);
+ // resolve-dtd-entities is at default value (unrecognized == true)
+
+ try {
+ doesIntern = parser.getFeature (
+ "http://xml.org/sax/features/string-interning");
+ } catch (SAXNotRecognizedException e) { }
+
+ loader = new Loader (doesIntern, eh, unified);
+ loader.cat.parserClass = parserClass;
+ loader.cat.catalogURI = uri;
+
+ parser.setContentHandler (loader);
+ parser.setProperty (
+ "http://xml.org/sax/properties/declaration-handler",
+ loader);
+ parser.setProperty (
+ "http://xml.org/sax/properties/lexical-handler",
+ loader);
+ parser.parse (uri);
+
+ return loader.cat;
+ }
+
+ // perform one or both the normalizations for public ids
+ private static String normalizePublicId (boolean full, String publicId)
+ {
+ if (publicId.startsWith ("urn:publicid:")) {
+ CPStringBuilder buf = new CPStringBuilder ();
+ char chars [] = publicId.toCharArray ();
+boolean hasbug = false;
+
+ for (int i = 13; i < chars.length; i++) {
+ switch (chars [i]) {
+ case '+': buf.append (' '); continue;
+ case ':': buf.append ("//"); continue;
+ case ';': buf.append ("::"); continue;
+ case '%':
+// FIXME unhex that char! meanwhile, warn and fallthrough ...
+ hasbug = true;
+ default: buf.append (chars [i]); continue;
+ }
+ }
+ publicId = buf.toString ();
+if (hasbug)
+System.err.println ("nyet unhexing public id: " + publicId);
+ full = true;
+ }
+
+ // SAX parsers do everything except that URN mapping, but
+ // we can't trust other sources to normalize correctly
+ if (full) {
+ StringTokenizer tokens;
+ String token;
+
+ tokens = new StringTokenizer (publicId, " \r\n");
+ publicId = null;
+ while (tokens.hasMoreTokens ()) {
+ if (publicId == null)
+ publicId = tokens.nextToken ();
+ else
+ publicId += " " + tokens.nextToken ();
+ }
+ }
+ return publicId;
+ }
+
+ private static boolean isUriExcluded (int c)
+ { return c <= 0x20 || c >= 0x7f || "\"<>^`{|}".indexOf (c) != -1; }
+
+ private static int hexNibble (int c)
+ {
+ if (c < 10)
+ return c + '0';
+ return ('a' - 10) + c;
+ }
+
+ // handles URIs with "excluded" characters
+ private static String normalizeURI (String systemId)
+ {
+ int length = systemId.length ();
+
+ for (int i = 0; i < length; i++) {
+ char c = systemId.charAt (i);
+
+ // escape non-ASCII plus "excluded" characters
+ if (isUriExcluded (c)) {
+ byte buf [];
+ ByteArrayOutputStream out;
+ int b;
+
+ // a JVM that doesn't know UTF8 and 8859_1 is unusable!
+ try {
+ buf = systemId.getBytes ("UTF8");
+ out = new ByteArrayOutputStream (buf.length + 10);
+
+ for (i = 0; i < buf.length; i++) {
+ b = buf [i] & 0x0ff;
+ if (isUriExcluded (b)) {
+ out.write ((int) '%');
+ out.write (hexNibble (b >> 4));
+ out.write (hexNibble (b & 0x0f));
+ } else
+ out.write (b);
+ }
+ return out.toString ("8859_1");
+ } catch (IOException e) {
+ throw new RuntimeException (
+ "can't normalize URI: " + e.getMessage ());
+ }
+ }
+ }
+ return systemId;
+ }
+
+ // thrown to mark authoritative end of a search
+ private static class DoneDelegation extends SAXException
+ {
+ DoneDelegation () { }
+ }
+
+
+ /**
+ * Represents a OASIS XML Catalog, and encapsulates much of
+ * the catalog functionality.
+ */
+ private static class Catalog
+ {
+ // loading infrastructure
+ String catalogURI;
+ ErrorHandler eh;
+ boolean unified;
+ String parserClass;
+
+ // catalog data
+ boolean hasPreference;
+ boolean usingPublic;
+
+ Hashtable publicIds;
+ Hashtable publicDelegations;
+
+ Hashtable systemIds;
+ Hashtable systemRewrites;
+ Hashtable systemDelegations;
+
+ Hashtable uris;
+ Hashtable uriRewrites;
+ Hashtable uriDelegations;
+
+ Hashtable doctypes;
+
+ Vector next;
+
+ // nonpublic!
+ Catalog () { }
+
+
+ // steps as found in OASIS XML catalog spec 7.1.2
+ private InputSource locatePublicId (String publicId)
+ throws SAXException, IOException
+ {
+ // 5. return (first) 'public' entry
+ if (publicIds != null) {
+ String retval = (String) publicIds.get (publicId);
+ if (retval != null) {
+ // IF the URI is accessible ...
+ return new InputSource (retval);
+ }
+ }
+
+ // 6. return delegatePublic catalog match [complex]
+ if (publicDelegations != null)
+ return checkDelegations (publicDelegations, publicId,
+ publicId, null);
+
+ return null;
+ }
+
+ // steps as found in OASIS XML catalog spec 7.1.2 or 7.2.2
+ private InputSource mapURI (
+ String uri,
+ Hashtable ids,
+ Hashtable rewrites,
+ Hashtable delegations
+ ) throws SAXException, IOException
+ {
+ // 7.1.2: 2. return (first) 'system' entry
+ // 7.2.2: 2. return (first) 'uri' entry
+ if (ids != null) {
+ String retval = (String) ids.get (uri);
+ if (retval != null) {
+ // IF the URI is accessible ...
+ return new InputSource (retval);
+ }
+ }
+
+ // 7.1.2: 3. return 'rewriteSystem' entries
+ // 7.2.2: 3. return 'rewriteURI' entries
+ if (rewrites != null) {
+ String prefix = null;
+ String replace = null;
+ int prefixLen = -1;
+
+ for (Enumeration e = rewrites.keys ();
+ e.hasMoreElements ();
+ /* NOP */) {
+ String temp = (String) e.nextElement ();
+ int len = -1;
+
+ if (!uri.startsWith (temp))
+ continue;
+ if (prefix != null
+ && (len = temp.length ()) < prefixLen)
+ continue;
+ prefix = temp;
+ prefixLen = len;
+ replace = (String) rewrites.get (temp);
+ }
+ if (prefix != null) {
+ CPStringBuilder buf = new CPStringBuilder (replace);
+ buf.append (uri.substring (prefixLen));
+ // IF the URI is accessible ...
+ return new InputSource (buf.toString ());
+ }
+ }
+
+ // 7.1.2: 4. return 'delegateSystem' catalog match [complex]
+ // 7.2.2: 4. return 'delegateURI' catalog match [complex]
+ if (delegations != null)
+ return checkDelegations (delegations, uri, null, uri);
+
+ return null;
+ }
+
+
+ /**
+ * Returns a URI for an external entity.
+ */
+ public InputSource resolve (
+ boolean usingPublic,
+ String publicId,
+ String systemId
+ ) throws SAXException, IOException
+ {
+ boolean preferSystem;
+ InputSource retval;
+
+ if (hasPreference)
+ preferSystem = !this.usingPublic;
+ else
+ preferSystem = !usingPublic;
+
+ if (publicId != null)
+ publicId = normalizePublicId (false, publicId);
+
+ // behavior here matches section 7.1.1 of the oasis spec
+ if (systemId != null) {
+ if (systemId.startsWith ("urn:publicid:")) {
+ String temp = normalizePublicId (true, systemId);
+ if (publicId == null) {
+ publicId = temp;
+ systemId = null;
+ } else if (!publicId.equals (temp)) {
+ // error; ok to recover by:
+ systemId = null;
+ }
+ } else
+ systemId = normalizeURI (systemId);
+ }
+
+ if (systemId == null && publicId == null)
+ return null;
+
+ if (systemId != null) {
+ retval = mapURI (systemId, systemIds, systemRewrites,
+ systemDelegations);
+ if (retval != null) {
+ retval.setPublicId (publicId);
+ return retval;
+ }
+ }
+
+ if (publicId != null
+ && !(systemId != null && preferSystem)) {
+ retval = locatePublicId (publicId);
+ if (retval != null) {
+ retval.setPublicId (publicId);
+ return retval;
+ }
+ }
+
+ // 7. apply nextCatalog entries
+ if (next != null) {
+ int length = next.size ();
+ for (int i = 0; i < length; i++) {
+ Catalog n = getNext (i);
+ retval = n.resolve (usingPublic, publicId, systemId);
+ if (retval != null)
+ return retval;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Maps one URI into another, for resources that are not defined
+ * using XML external entity or notation syntax.
+ */
+ public InputSource resolveURI (String uri)
+ throws SAXException, IOException
+ {
+ if (uri.startsWith ("urn:publicid:"))
+ return resolve (true, normalizePublicId (true, uri), null);
+
+ InputSource retval;
+
+ uri = normalizeURI (uri);
+
+ // 7.2.2 steps 2-4
+ retval = mapURI (uri, uris, uriRewrites, uriDelegations);
+ if (retval != null)
+ return retval;
+
+ // 7.2.2 step 5. apply nextCatalog entries
+ if (next != null) {
+ int length = next.size ();
+ for (int i = 0; i < length; i++) {
+ Catalog n = getNext (i);
+ retval = n.resolveURI (uri);
+ if (retval != null)
+ return retval;
+ }
+ }
+
+ return null;
+ }
+
+
+ /**
+ * Finds the external subset associated with a given root element.
+ */
+ public InputSource getExternalSubset (String name)
+ throws SAXException, IOException
+ {
+ if (doctypes != null) {
+ String value = (String) doctypes.get (name);
+ if (value != null) {
+ // IF the URI is accessible ...
+ return new InputSource (value);
+ }
+ }
+ if (next != null) {
+ int length = next.size ();
+ for (int i = 0; i < length; i++) {
+ Catalog n = getNext (i);
+ if (n == null)
+ continue;
+ InputSource retval = n.getExternalSubset (name);
+ if (retval != null)
+ return retval;
+ }
+ }
+ return null;
+ }
+
+ private synchronized Catalog getNext (int i)
+ throws SAXException, IOException
+ {
+ Object obj;
+
+ if (next == null || i < 0 || i >= next.size ())
+ return null;
+ obj = next.elementAt (i);
+ if (obj instanceof Catalog)
+ return (Catalog) obj;
+
+ // ok, we deferred reading that catalog till now.
+ // load and cache it.
+ Catalog cat = null;
+
+ try {
+ cat = loadCatalog (parserClass, eh, (String) obj, unified);
+ next.setElementAt (cat, i);
+ } catch (SAXException e) {
+ // must fail quietly, says the OASIS spec
+ } catch (IOException e) {
+ // same applies here
+ }
+ return cat;
+ }
+
+ private InputSource checkDelegations (
+ Hashtable delegations,
+ String id,
+ String publicId, // only one of public/system
+ String systemId // will be non-null...
+ ) throws SAXException, IOException
+ {
+ Vector matches = null;
+ int length = 0;
+
+ // first, see if any prefixes match.
+ for (Enumeration e = delegations.keys ();
+ e.hasMoreElements ();
+ /* NOP */) {
+ String prefix = (String) e.nextElement ();
+
+ if (!id.startsWith (prefix))
+ continue;
+ if (matches == null)
+ matches = new Vector ();
+
+ // maintain in longer->shorter sorted order
+ // NOTE: assumes not many matches will fire!
+ int index;
+
+ for (index = 0; index < length; index++) {
+ String temp = (String) matches.elementAt (index);
+ if (prefix.length () > temp.length ()) {
+ matches.insertElementAt (prefix, index);
+ break;
+ }
+ }
+ if (index == length)
+ matches.addElement (prefix);
+ length++;
+ }
+ if (matches == null)
+ return null;
+
+ // now we know the list of catalogs to replace our "top level"
+ // list ... we use it here, rather than somehow going back and
+ // restarting, since this helps avoid reading most catalogs.
+ // this assumes stackspace won't be a problem.
+ for (int i = 0; i < length; i++) {
+ Catalog catalog = null;
+ InputSource result;
+
+ // get this catalog. we may not have read it yet.
+ synchronized (delegations) {
+ Object prefix = matches.elementAt (i);
+ Object cat = delegations.get (prefix);
+
+ if (cat instanceof Catalog)
+ catalog = (Catalog) cat;
+ else {
+ try {
+ // load and cache that catalog
+ catalog = loadCatalog (parserClass, eh,
+ (String) cat, unified);
+ delegations.put (prefix, catalog);
+ } catch (SAXException e) {
+ // must ignore, says the OASIS spec
+ } catch (IOException e) {
+ // same applies here
+ }
+ }
+ }
+
+ // ignore failed loads, and proceed
+ if (catalog == null)
+ continue;
+
+ // we have a catalog ... resolve!
+ // usingPublic value can't matter, there's no choice
+ result = catalog.resolve (true, publicId, systemId);
+ if (result != null)
+ return result;
+ }
+
+ // if there were no successes, the entire
+ // lookup failed (all the way to top level)
+ throw new DoneDelegation ();
+ }
+ }
+
+
+ /** This is the namespace URI used for OASIS XML Catalogs. */
+ private static final String catalogNamespace =
+ "urn:oasis:names:tc:entity:xmlns:xml:catalog";
+
+
+ /**
+ * Loads/unmarshals one catalog.
+ */
+ private static class Loader extends DefaultHandler2
+ {
+ private boolean preInterned;
+ private ErrorHandler handler;
+ private boolean unified;
+ private int ignoreDepth;
+ private Locator locator;
+ private boolean started;
+ private Hashtable externals;
+ private Stack bases;
+
+ Catalog cat = new Catalog ();
+
+
+ /**
+ * Constructor.
+ * @param flag true iff the parser already interns strings.
+ * @param eh Errors and warnings are delegated to this.
+ * @param unified true keeps one table for URI mappings;
+ * false matches OASIS spec, storing mappings
+ * for URIs and SYSTEM ids in parallel tables.
+ */
+ Loader (boolean flag, ErrorHandler eh, boolean unified)
+ {
+ preInterned = flag;
+ handler = eh;
+ this.unified = unified;
+ cat.unified = unified;
+ cat.eh = eh;
+ }
+
+
+ // strips out fragments
+ private String nofrag (String uri)
+ throws SAXException
+ {
+ if (uri.indexOf ('#') != -1) {
+ warn ("URI with fragment: " + uri);
+ uri = uri.substring (0, uri.indexOf ('#'));
+ }
+ return uri;
+ }
+
+ // absolutizes relative URIs
+ private String absolutize (String uri)
+ throws SAXException
+ {
+ // avoid creating URLs if they're already absolutized,
+ // or if the URI is already using a known scheme
+ if (uri.startsWith ("file:/")
+ || uri.startsWith ("http:/")
+ || uri.startsWith ("https:/")
+ || uri.startsWith ("ftp:/")
+ || uri.startsWith ("urn:")
+ )
+ return uri;
+
+ // otherwise, let's hope the JDK handles this URI scheme.
+ try {
+ URL base = (URL) bases.peek ();
+ return new URL (base, uri).toString ();
+ } catch (Exception e) {
+ fatal ("can't absolutize URI: " + uri);
+ return null;
+ }
+ }
+
+ // recoverable error
+ private void error (String message)
+ throws SAXException
+ {
+ if (handler == null)
+ return;
+ handler.error (new SAXParseException (message, locator));
+ }
+
+ // nonrecoverable error
+ private void fatal (String message)
+ throws SAXException
+ {
+ SAXParseException spe;
+
+ spe = new SAXParseException (message, locator);
+ if (handler != null)
+ handler.fatalError (spe);
+ throw spe;
+ }
+
+ // low severity problem
+ private void warn (String message)
+ throws SAXException
+ {
+ if (handler == null)
+ return;
+ handler.warning (new SAXParseException (message, locator));
+ }
+
+ // callbacks:
+
+ public void setDocumentLocator (Locator l)
+ { locator = l; }
+
+ public void startDocument ()
+ throws SAXException
+ {
+ if (locator == null)
+ error ("no locator!");
+ bases = new Stack ();
+ String uri = locator.getSystemId ();
+ try {
+ bases.push (new URL (uri));
+ } catch (IOException e) {
+ fatal ("bad document base URI: " + uri);
+ }
+ }
+
+ public void endDocument ()
+ throws SAXException
+ {
+ try {
+ if (!started)
+ error ("not a catalog!");
+ } finally {
+ locator = null;
+ handler = null;
+ externals = null;
+ bases = null;
+ }
+ }
+
+ // XML Base support for external entities.
+
+ // NOTE: expects parser is in default "resolve-dtd-uris" mode.
+ public void externalEntityDecl (String name, String pub, String sys)
+ throws SAXException
+ {
+ if (externals == null)
+ externals = new Hashtable ();
+ if (externals.get (name) == null)
+ externals.put (name, pub);
+ }
+
+ public void startEntity (String name)
+ throws SAXException
+ {
+ if (externals == null)
+ return;
+ String uri = (String) externals.get (name);
+
+ // NOTE: breaks if an EntityResolver substitutes these URIs.
+ // If toplevel loader supports one, must intercept calls...
+ if (uri != null) {
+ try {
+ bases.push (new URL (uri));
+ } catch (IOException e) {
+ fatal ("entity '" + name + "', bad URI: " + uri);
+ }
+ }
+ }
+
+ public void endEntity (String name)
+ {
+ if (externals == null)
+ return;
+ String value = (String) externals.get (name);
+
+ if (value != null)
+ bases.pop ();
+ }
+
+ /**
+ * Processes catalog elements, saving their data.
+ */
+ public void startElement (String namespace, String local,
+ String qName, Attributes atts)
+ throws SAXException
+ {
+ // must ignore non-catalog elements, and their contents
+ if (ignoreDepth != 0 || !catalogNamespace.equals (namespace)) {
+ ignoreDepth++;
+ return;
+ }
+
+ // basic sanity checks
+ if (!preInterned)
+ local = local.intern ();
+ if (!started) {
+ started = true;
+ if ("catalog" != local)
+ fatal ("root element not 'catalog': " + local);
+ }
+
+ // Handle any xml:base attribute
+ String xmlbase = atts.getValue ("xml:base");
+
+ if (xmlbase != null) {
+ URL base = (URL) bases.peek ();
+ try {
+ base = new URL (base, xmlbase);
+ } catch (IOException e) {
+ fatal ("can't resolve xml:base attribute: " + xmlbase);
+ }
+ bases.push (base);
+ } else
+ bases.push (bases.peek ());
+
+ // fetch multi-element attributes, apply standard tweaks
+ // values (uri, catalog, rewritePrefix) get normalized too,
+ // as a precaution and since we may compare the values
+ String catalog = atts.getValue ("catalog");
+ if (catalog != null)
+ catalog = normalizeURI (absolutize (catalog));
+
+ String rewritePrefix = atts.getValue ("rewritePrefix");
+ if (rewritePrefix != null)
+ rewritePrefix = normalizeURI (absolutize (rewritePrefix));
+
+ String systemIdStartString;
+ systemIdStartString = atts.getValue ("systemIdStartString");
+ if (systemIdStartString != null) {
+ systemIdStartString = normalizeURI (systemIdStartString);
+ // unmatchable <rewriteSystemId>, <delegateSystemId> elements
+ if (systemIdStartString.startsWith ("urn:publicid:")) {
+ error ("systemIdStartString is really a publicId!!");
+ return;
+ }
+ }
+
+ String uri = atts.getValue ("uri");
+ if (uri != null)
+ uri = normalizeURI (absolutize (uri));
+
+ String uriStartString;
+ uriStartString = atts.getValue ("uriStartString");
+ if (uriStartString != null) {
+ uriStartString = normalizeURI (uriStartString);
+ // unmatchable <rewriteURI>, <delegateURI> elements
+ if (uriStartString.startsWith ("urn:publicid:")) {
+ error ("uriStartString is really a publicId!!");
+ return;
+ }
+ }
+
+ // strictly speaking "group" and "catalog" shouldn't nest
+ // ... arbitrary restriction, no evident motivation
+
+// FIXME stack "prefer" settings (two elements only!) and use
+// them to populate different public mapping/delegation tables
+
+ if ("catalog" == local || "group" == local) {
+ String prefer = atts.getValue ("prefer");
+
+ if (prefer != null && !"public".equals (prefer)) {
+ if (!"system".equals (prefer)) {
+ error ("in <" + local + " ... prefer='...'>, "
+ + "assuming 'public'");
+ prefer = "public";
+ }
+ }
+ if (prefer != null) {
+ if ("catalog" == local) {
+ cat.hasPreference = true;
+ cat.usingPublic = "public".equals (prefer);
+ } else {
+ if (!cat.hasPreference || cat.usingPublic
+ != "public".equals (prefer)) {
+fatal ("<group prefer=...> case not handled");
+ }
+ }
+ } else if ("group" == local && cat.hasPreference) {
+fatal ("<group prefer=...> case not handled");
+ }
+
+ //
+ // PUBLIC ids: cleanly set up for id substitution
+ //
+ } else if ("public" == local) {
+ String publicId = atts.getValue ("publicId");
+ String value = null;
+
+ if (publicId == null || uri == null) {
+ error ("expecting <public publicId=... uri=.../>");
+ return;
+ }
+ publicId = normalizePublicId (true, publicId);
+ uri = nofrag (uri);
+ if (cat.publicIds == null)
+ cat.publicIds = new Hashtable ();
+ else
+ value = (String) cat.publicIds.get (publicId);
+ if (value != null) {
+ if (!value.equals (uri))
+ warn ("ignoring <public...> entry for " + publicId);
+ } else
+ cat.publicIds.put (publicId, uri);
+
+ } else if ("delegatePublic" == local) {
+ String publicIdStartString;
+ Object value = null;
+
+ publicIdStartString = atts.getValue ("publicIdStartString");
+ if (publicIdStartString == null || catalog == null) {
+ error ("expecting <delegatePublic "
+ + "publicIdStartString=... catalog=.../>");
+ return;
+ }
+ publicIdStartString = normalizePublicId (true,
+ publicIdStartString);
+ if (cat.publicDelegations == null)
+ cat.publicDelegations = new Hashtable ();
+ else
+ value = cat.publicDelegations.get (publicIdStartString);
+ if (value != null) {
+ if (!value.equals (catalog))
+ warn ("ignoring <delegatePublic...> entry for "
+ + uriStartString);
+ } else
+ cat.publicDelegations.put (publicIdStartString, catalog);
+
+
+ //
+ // SYSTEM ids: need substitution due to operational issues
+ //
+ } else if ("system" == local) {
+ String systemId = atts.getValue ("systemId");
+ String value = null;
+
+ if (systemId == null || uri == null) {
+ error ("expecting <system systemId=... uri=.../>");
+ return;
+ }
+ systemId = normalizeURI (systemId);
+ uri = nofrag (uri);
+ if (systemId.startsWith ("urn:publicid:")) {
+ error ("systemId is really a publicId!!");
+ return;
+ }
+ if (cat.systemIds == null) {
+ cat.systemIds = new Hashtable ();
+ if (unified)
+ cat.uris = cat.systemIds;
+ } else
+ value = (String) cat.systemIds.get (systemId);
+ if (value != null) {
+ if (!value.equals (uri))
+ warn ("ignoring <system...> entry for " + systemId);
+ } else
+ cat.systemIds.put (systemId, uri);
+
+ } else if ("rewriteSystem" == local) {
+ String value = null;
+
+ if (systemIdStartString == null || rewritePrefix == null
+ || systemIdStartString.length () == 0
+ || rewritePrefix.length () == 0
+ ) {
+ error ("expecting <rewriteSystem "
+ + "systemIdStartString=... rewritePrefix=.../>");
+ return;
+ }
+ if (cat.systemRewrites == null) {
+ cat.systemRewrites = new Hashtable ();
+ if (unified)
+ cat.uriRewrites = cat.systemRewrites;
+ } else
+ value = (String) cat.systemRewrites.get (
+ systemIdStartString);
+ if (value != null) {
+ if (!value.equals (rewritePrefix))
+ warn ("ignoring <rewriteSystem...> entry for "
+ + systemIdStartString);
+ } else
+ cat.systemRewrites.put (systemIdStartString,
+ rewritePrefix);
+
+ } else if ("delegateSystem" == local) {
+ Object value = null;
+
+ if (systemIdStartString == null || catalog == null) {
+ error ("expecting <delegateSystem "
+ + "systemIdStartString=... catalog=.../>");
+ return;
+ }
+ if (cat.systemDelegations == null) {
+ cat.systemDelegations = new Hashtable ();
+ if (unified)
+ cat.uriDelegations = cat.systemDelegations;
+ } else
+ value = cat.systemDelegations.get (systemIdStartString);
+ if (value != null) {
+ if (!value.equals (catalog))
+ warn ("ignoring <delegateSystem...> entry for "
+ + uriStartString);
+ } else
+ cat.systemDelegations.put (systemIdStartString, catalog);
+
+
+ //
+ // URI: just like "system" ID support, except that
+ // fragment IDs are disallowed in "system" elements.
+ //
+ } else if ("uri" == local) {
+ String name = atts.getValue ("name");
+ String value = null;
+
+ if (name == null || uri == null) {
+ error ("expecting <uri name=... uri=.../>");
+ return;
+ }
+ if (name.startsWith ("urn:publicid:")) {
+ error ("name is really a publicId!!");
+ return;
+ }
+ name = normalizeURI (name);
+ if (cat.uris == null) {
+ cat.uris = new Hashtable ();
+ if (unified)
+ cat.systemIds = cat.uris;
+ } else
+ value = (String) cat.uris.get (name);
+ if (value != null) {
+ if (!value.equals (uri))
+ warn ("ignoring <uri...> entry for " + name);
+ } else
+ cat.uris.put (name, uri);
+
+ } else if ("rewriteURI" == local) {
+ String value = null;
+
+ if (uriStartString == null || rewritePrefix == null
+ || uriStartString.length () == 0
+ || rewritePrefix.length () == 0
+ ) {
+ error ("expecting <rewriteURI "
+ + "uriStartString=... rewritePrefix=.../>");
+ return;
+ }
+ if (cat.uriRewrites == null) {
+ cat.uriRewrites = new Hashtable ();
+ if (unified)
+ cat.systemRewrites = cat.uriRewrites;
+ } else
+ value = (String) cat.uriRewrites.get (uriStartString);
+ if (value != null) {
+ if (!value.equals (rewritePrefix))
+ warn ("ignoring <rewriteURI...> entry for "
+ + uriStartString);
+ } else
+ cat.uriRewrites.put (uriStartString, rewritePrefix);
+
+ } else if ("delegateURI" == local) {
+ Object value = null;
+
+ if (uriStartString == null || catalog == null) {
+ error ("expecting <delegateURI "
+ + "uriStartString=... catalog=.../>");
+ return;
+ }
+ if (cat.uriDelegations == null) {
+ cat.uriDelegations = new Hashtable ();
+ if (unified)
+ cat.systemDelegations = cat.uriDelegations;
+ } else
+ value = cat.uriDelegations.get (uriStartString);
+ if (value != null) {
+ if (!value.equals (catalog))
+ warn ("ignoring <delegateURI...> entry for "
+ + uriStartString);
+ } else
+ cat.uriDelegations.put (uriStartString, catalog);
+
+ //
+ // NON-DELEGATING approach to modularity
+ //
+ } else if ("nextCatalog" == local) {
+ if (catalog == null) {
+ error ("expecting <nextCatalog catalog=.../>");
+ return;
+ }
+ if (cat.next == null)
+ cat.next = new Vector ();
+ cat.next.addElement (catalog);
+
+ //
+ // EXTENSIONS from appendix E
+ //
+ } else if ("doctype" == local) {
+ String name = atts.getValue ("name");
+ String value = null;
+
+ if (name == null || uri == null) {
+ error ("expecting <doctype name=... uri=.../>");
+ return;
+ }
+ name = normalizeURI (name);
+ if (cat.doctypes == null)
+ cat.doctypes = new Hashtable ();
+ else
+ value = (String) cat.doctypes.get (name);
+ if (value != null) {
+ if (!value.equals (uri))
+ warn ("ignoring <doctype...> entry for "
+ + uriStartString);
+ } else
+ cat.doctypes.put (name, uri);
+
+
+ //
+ // RESERVED ... ignore (like reserved attributes) but warn
+ //
+ } else {
+ warn ("ignoring unknown catalog element: " + local);
+ ignoreDepth++;
+ }
+ }
+
+ public void endElement (String uri, String local, String qName)
+ throws SAXException
+ {
+ if (ignoreDepth != 0)
+ ignoreDepth--;
+ else
+ bases.pop ();
+ }
+ }
+}