// CatalogEntityResolver.java - SAX entityResolver using OASIS Catalogs // Written by Norman Walsh, nwalsh@arbortext.com // NO WARRANTY! This class is in the public domain. package com.arbortext.catalog; import java.io.IOException; import java.io.InputStream; import java.lang.Integer; import java.net.URL; import java.net.MalformedURLException; import com.arbortext.catalog.Catalog; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; /** *
Implements SAX entityResolver using OASIS Open Catalogs.
* ** This module, both source code and documentation, is in the * Public Domain, and comes with NO WARRANTY. ** *
This class implements the SAX entityResolver interface. It uses * OASIS Open catalog files to provide a facility for mapping public * or system identifiers in source documents to local system identifiers. * *
This code interrogates the following non-standard system properties:
* *Sets the debug level. A value of 0 is assumed if the * property is not set or is not a number.
The debug level
* *In general, higher numbers produce more information:
*Indicates that unusable system identifiers should be ignored.
*/ private boolean retryBadSystemIds = false; /** *The OASIS Open Catalog used for entity resolution.
* *This field is exposed so that the catalog can be updated * after creating the instance of CatalogEntityResolver that will * be used by the parser.
*/ public Catalog catalog = null; /** *Constructs a CatalogEntityResolver with an empty catalog.
*/ public CatalogEntityResolver() { String property = System.getProperty("xml.catalog.debug"); if (property != null) { try { debug = Integer.parseInt(property); } catch (NumberFormatException e) { debug = 0; } } } /** *Set the Catalog that will be used to resolve entities.
* *This is a convenience method for setting the
* catalog
field.
Parse a Catalog file.
* *This is really just a convenience method which calls
* catalog.parseCatalog()
.
Establish whether or not bad system identifiers should be * ignored.
* *The semantics of catalog file lookup are such that if a system * identifier is supplied in the instance document, it is possible * that it will be used in preference to alternative system identifiers * in the catalog.
* *If this variable is true
and the system identifier
* passed to the entity resolver would be returned, the entity resolver
* attempts to open it. If it cannot be opened, the resolver
* does another catalog search,
* ignoring the fact that a system identifier was specified. If this
* second search locates a system identifer, it will be returned.
This setting is initially false
meaning that
* system identifiers
* in the document will be used in preference to some entries in
* the catalog.
Implements the resolveEntity
method
* for the SAX interface.
Presented with an optional public identifier and a system * identifier, this function attempts to locate a mapping in the * catalogs.
* *If such a mapping is found, the resolver attempts to open * the mapped value as an InputSource and return it. Exceptions are * ignored and null is returned if the mapped value cannot be opened * as an input source.
* * If no mapping is found (or an error occurs attempting to open * the mapped value as an input source), null is returned and the system * will use the specified system identifier as if no entityResolver * was specified. * * @param publicId The public identifier for the entity in question. * This may be null. * * @param systemId The system identifier for the entity in question. * XML requires a system identifier on all external entities, so this * value is always specified. * * @return An InputSource for the mapped identifier, or null. */ public InputSource resolveEntity (String publicId, String systemId) { String resolved = null; if (systemId != null) { try { resolved = catalog.resolveSystem(systemId); } catch (MalformedURLException me) { debug(1, "Malformed URL exception trying to resolve", publicId); resolved = null; } catch (IOException ie) { debug(1, "I/O exception trying to resolve", publicId); resolved = null; } } if (resolved == null) { if (publicId != null) { try { resolved = catalog.resolvePublic(publicId, systemId); } catch (MalformedURLException me) { debug(1, "Malformed URL exception trying to resolve", publicId); } catch (IOException ie) { debug(1, "I/O exception trying to resolve", publicId); } } if (resolved != null) { debug(2, "Resolved", publicId, resolved); } } else { debug(2, "Resolved", systemId, resolved); } if (resolved == null && retryBadSystemIds && publicId != null && systemId != null) { URL systemURL = null; try { systemURL = new URL(systemId); } catch (MalformedURLException e) { try { systemURL = new URL("file:///" + systemId); } catch (MalformedURLException e2) { systemURL = null; } } if (systemURL != null) { try { InputStream iStream = systemURL.openStream(); // The systemId can be opened, so that's the one that // we'll use. There's no point making the caller open // it again though... InputSource iSource = new InputSource(systemId); iSource.setPublicId(publicId); iSource.setByteStream(iStream); return iSource; } catch (Exception e) { // nop } } // we got here, so it must be that the systemId cannot be // opened and the caller wants us to retry... debug(2, "Failed to open", systemId); debug(2, "\tAttempting catalog lookup without system identifier."); return resolveEntity(publicId, null); } if (resolved != null) { try { InputSource iSource = new InputSource(resolved); iSource.setPublicId(publicId); // Ideally this method would not attempt to open the // InputStream, but there is a bug (in Xerces, at least) // that causes the parser to mistakenly open the wrong // system identifier if the returned InputSource does // not have a byteStream. // // It could be argued that we still shouldn't do this here, // but since the purpose of calling the entityResolver is // almost certainly to open the input stream, it seems to // do little harm. // URL url = new URL(resolved); InputStream iStream = url.openStream(); iSource.setByteStream(iStream); return iSource; } catch (Exception e) { debug(1, "Failed to create InputSource", resolved); return null; } } return null; } /** *Print debug message (if the debug level is high enough).
* * @param level The debug level of this message. This message * will only be * displayed if the current debug level is at least equal to this * value. * @param message The text of the message. */ private void debug(int level, String message) { if (debug >= level) { System.out.println(message); } } /** *Print debug message (if the debug level is high enough).
* * @param level The debug level of this message. This message * will only be * displayed if the current debug level is at least equal to this * value. * @param message The text of the message. * @param spec An argument to the message. */ private void debug(int level, String message, String spec) { if (debug >= level) { System.out.println(message + ": " + spec); } } /** *Print debug message (if the debug level is high enough).
* * @param level The debug level of this message. This message * will only be * displayed if the current debug level is at least equal to this * value. * @param message The text of the message. * @param spec1 An argument to the message. * @param spec1 Another argument to the message. */ private void debug(int level, String message, String spec1, String spec2) { if (debug >= level) { System.out.println(message + ": " + spec1); System.out.println("\t" + spec2); } } }