| /* 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 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><?oasis-xml-catalog...?></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:")) { |
| StringBuffer buf = new StringBuffer (); |
| 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) { |
| StringBuffer buf = new StringBuffer (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 (); |
| } |
| } |
| } |