netbeans-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From GitBox <...@apache.org>
Subject [GitHub] matthiasblaesing closed pull request #192: [NETBEANS-54] Module Review o.apache.xml.resolver (Build fails on sys?
Date Thu, 01 Jan 1970 00:00:00 GMT
matthiasblaesing closed pull request #192: [NETBEANS-54] Module Review o.apache.xml.resolver (Build fails on sys?
URL: https://github.com/apache/incubator-netbeans/pull/192
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/o.apache.xml.resolver/build.xml b/o.apache.xml.resolver/build.xml
index 40143e1fb..b19082f49 100644
--- a/o.apache.xml.resolver/build.xml
+++ b/o.apache.xml.resolver/build.xml
@@ -42,9 +42,16 @@
         <!-- ... 2. Unzips maven sources -->
         <unzip src="external/xml-resolver-1.2-sources.jar" dest="build/external-patch/sources" />
         <!-- ... 3. Applies patch 'external/xml-resolver-1.2-netbeans.patch -->
+        <!-- DISABLED: 
+             - ant patch task requires external 'patch' tool, not available on all systems.
+             - We may want to reuse some code from the 'diff' module to implement an patch task later on.
         <patch dir="build/external-patch/sources"
             patchfile="external/xml-resolver-1.2-netbeans.patch"
             strip="0" />
+        -->
+        <copy todir="build/externa-patch/sources">
+            <fileset dir="patch-src" />
+        </copy>
         <!-- ... 4. Compiles sources to build/external-patch/classes -->
         <mkdir dir="build/external-patch/classes" />
         <javac srcdir="build/external-patch/sources"
diff --git a/o.apache.xml.resolver/patch-src/org/apache/xml/resolver/Catalog.java b/o.apache.xml.resolver/patch-src/org/apache/xml/resolver/Catalog.java
new file mode 100644
index 000000000..cc129792d
--- /dev/null
+++ b/o.apache.xml.resolver/patch-src/org/apache/xml/resolver/Catalog.java
@@ -0,0 +1,2231 @@
+// Catalog.java - Represents OASIS Open Catalog files.
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.xml.resolver;
+
+import java.io.IOException;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.io.DataInputStream;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import java.net.URL;
+import java.net.MalformedURLException;
+
+import javax.xml.parsers.SAXParserFactory;
+
+import org.apache.xml.resolver.CatalogManager;
+import org.apache.xml.resolver.helpers.PublicId;
+import org.apache.xml.resolver.readers.CatalogReader;
+import org.apache.xml.resolver.readers.SAXCatalogReader;
+import org.apache.xml.resolver.readers.TR9401CatalogReader;
+import org.apache.xml.resolver.readers.OASISXMLCatalogReader;
+import org.apache.xml.resolver.helpers.FileURL;
+
+/**
+ * Represents OASIS Open Catalog files.
+ *
+ * <p>This class implements the semantics of OASIS Open Catalog files
+ * (defined by
+ * <a href="http://www.oasis-open.org/html/a401.htm">OASIS Technical
+ * Resolution 9401:1997 (Amendment 2 to TR 9401)</a>).</p>
+ *
+ * <p>The primary purpose of the Catalog is to associate resources in the
+ * document with local system identifiers. Some entities
+ * (document types, XML entities, and notations) have names and all of them
+ * can have either public or system identifiers or both. (In XML, only a
+ * notation can have a public identifier without a system identifier, but
+ * the methods implemented in this class obey the Catalog semantics
+ * from the SGML
+ * days when system identifiers were optional.)</p>
+ *
+ * <p>The system identifiers returned by the resolution methods in this
+ * class are valid, i.e. usable by, and in fact constructed by, the
+ * <tt>java.net.URL</tt> class. Unfortunately, this class seems to behave in
+ * somewhat non-standard ways and the system identifiers returned may
+ * not be directly usable in a browser or filesystem context.
+ *
+ * <p>This class recognizes all of the Catalog entries defined in
+ * TR9401:1997:</p>
+ *
+ * <ul>
+ * <li><b>BASE</b>
+ * changes the base URI for resolving relative system identifiers. The
+ * initial base URI is the URI of the location of the catalog (which is,
+ * in turn, relative to the location of the current working directory
+ * at startup, as returned by the <tt>user.dir</tt> system property).</li>
+ * <li><b>CATALOG</b>
+ * processes other catalog files. An included catalog occurs logically
+ * at the end of the including catalog.</li>
+ * <li><b>DELEGATE_PUBLIC</b>
+ * specifies alternate catalogs for some public identifiers. The delegated
+ * catalogs are not loaded until they are needed, but they are cached
+ * once loaded.</li>
+ * <li><b>DELEGATE_SYSTEM</b>
+ * specifies alternate catalogs for some system identifiers. The delegated
+ * catalogs are not loaded until they are needed, but they are cached
+ * once loaded.</li>
+ * <li><b>DELEGATE_URI</b>
+ * specifies alternate catalogs for some URIs. The delegated
+ * catalogs are not loaded until they are needed, but they are cached
+ * once loaded.</li>
+ * <li><b>REWRITE_SYSTEM</b>
+ * specifies alternate prefix for a system identifier.</li>
+ * <li><b>REWRITE_URI</b>
+ * specifies alternate prefix for a URI.</li>
+ * <li><b>SYSTEM_SUFFIX</b>
+ * maps any system identifier that ends with a particular suffix to another
+ * system identifier.</li>
+ * <li><b>URI_SUFFIX</b>
+ * maps any URI that ends with a particular suffix to another URI.</li>
+ * <li><b>DOCTYPE</b>
+ * associates the names of root elements with URIs. (In other words, an XML
+ * processor might infer the doctype of an XML document that does not include
+ * a doctype declaration by looking for the DOCTYPE entry in the
+ * catalog which matches the name of the root element of the document.)</li>
+ * <li><b>DOCUMENT</b>
+ * provides a default document.</li>
+ * <li><b>DTDDECL</b>
+ * recognized and silently ignored. Not relevant for XML.</li>
+ * <li><b>ENTITY</b>
+ * associates entity names with URIs.</li>
+ * <li><b>LINKTYPE</b>
+ * recognized and silently ignored. Not relevant for XML.</li>
+ * <li><b>NOTATION</b>
+ * associates notation names with URIs.</li>
+ * <li><b>OVERRIDE</b>
+ * changes the override behavior. Initial behavior is set by the
+ * system property <tt>xml.catalog.override</tt>. The default initial
+ * behavior is 'YES', that is, entries in the catalog override
+ * system identifiers specified in the document.</li>
+ * <li><b>PUBLIC</b>
+ * maps a public identifier to a system identifier.</li>
+ * <li><b>SGMLDECL</b>
+ * recognized and silently ignored. Not relevant for XML.</li>
+ * <li><b>SYSTEM</b>
+ * maps a system identifier to another system identifier.</li>
+ * <li><b>URI</b>
+ * maps a URI to another URI.</li>
+ * </ul>
+ *
+ * <p>Note that BASE entries are treated as described by RFC2396. In
+ * particular, this has the counter-intuitive property that after a BASE
+ * entry identifing "http://example.com/a/b/c" as the base URI,
+ * the relative URI "foo" is resolved to the absolute URI
+ * "http://example.com/a/b/foo". You must provide the trailing slash if
+ * you do not want the final component of the path to be discarded as a
+ * filename would in a URI for a resource: "http://example.com/a/b/c/".
+ * </p>
+ *
+ * <p>Note that subordinate catalogs (all catalogs except the first,
+ * including CATALOG and DELEGATE* catalogs) are only loaded if and when
+ * they are required.</p>
+ *
+ * <p>This class relies on classes which implement the CatalogReader
+ * interface to actually load catalog files. This allows the catalog
+ * semantics to be implemented for TR9401 text-based catalogs, XML
+ * catalogs, or any number of other storage formats.</p>
+ *
+ * <p>Additional catalogs may also be loaded with the
+ * {@link #parseCatalog} method.</p>
+ * </dd>
+ * </dl>
+ *
+ * <p><b>Change Log:</b></p>
+ * <dl>
+ * <dt>2.0</dt>
+ * <dd><p>Rewrite to use CatalogReaders.</p></dd>
+ * <dt>1.1</dt>
+ * <dd><p>Allow quoted components in <tt>xml.catalog.files</tt>
+ * so that URLs containing colons can be used on Unix.
+ * The string passed to <tt>xml.catalog.files</tt> can now have the form:</p>
+ * <pre>
+ * unquoted-path-with-no-sep-chars:"double-quoted path with or without sep chars":'single-quoted path with or without sep chars'
+ * </pre>
+ * <p>(Where ":" is the separater character in this example.)</p>
+ * <p>If an unquoted path contains an embedded double or single quote
+ * character, no special processig is performed on that character. No
+ * path can contain separater characters, double, and single quotes
+ * simultaneously.</p>
+ * <p>Fix bug in calculation of BASE entries: if
+ * a catalog contains multiple BASE entries, each is relative to the preceding
+ * base, not the default base URI of the catalog.</p>
+ * </dd>
+ * <dt>1.0.1</dt>
+ * <dd><p>Fixed a bug in the calculation of the list of subordinate catalogs.
+ * This bug caused an infinite loop where parsing would alternately process
+ * two catalogs indefinitely.</p>
+ * </dd>
+ * </dl>
+ *
+ * @see CatalogReader
+ * @see CatalogEntry
+ *
+ * @author Norman Walsh
+ * <a href="mailto:Norman.Walsh@Sun.COM">Norman.Walsh@Sun.COM</a>
+ *
+ * @version 1.0
+ *
+ * <p>Derived from public domain code originally published by Arbortext,
+ * Inc.</p>
+ */
+public class Catalog {
+  /** The BASE Catalog Entry type. */
+  public static final int BASE     = CatalogEntry.addEntryType("BASE", 1);
+
+  /** The CATALOG Catalog Entry type. */
+  public static final int CATALOG  = CatalogEntry.addEntryType("CATALOG", 1);
+
+  /** The DOCUMENT Catalog Entry type. */
+  public static final int DOCUMENT = CatalogEntry.addEntryType("DOCUMENT", 1);
+
+  /** The OVERRIDE Catalog Entry type. */
+  public static final int OVERRIDE = CatalogEntry.addEntryType("OVERRIDE", 1);
+
+  /** The SGMLDECL Catalog Entry type. */
+  public static final int SGMLDECL = CatalogEntry.addEntryType("SGMLDECL", 1);
+
+  /** The DELEGATE_PUBLIC Catalog Entry type. */
+  public static final int DELEGATE_PUBLIC = CatalogEntry.addEntryType("DELEGATE_PUBLIC", 2);
+
+  /** The DELEGATE_SYSTEM Catalog Entry type. */
+  public static final int DELEGATE_SYSTEM = CatalogEntry.addEntryType("DELEGATE_SYSTEM", 2);
+
+  /** The DELEGATE_URI Catalog Entry type. */
+  public static final int DELEGATE_URI = CatalogEntry.addEntryType("DELEGATE_URI", 2);
+
+  /** The DOCTYPE Catalog Entry type. */
+  public static final int DOCTYPE  = CatalogEntry.addEntryType("DOCTYPE", 2);
+
+  /** The DTDDECL Catalog Entry type. */
+  public static final int DTDDECL  = CatalogEntry.addEntryType("DTDDECL", 2);
+
+  /** The ENTITY Catalog Entry type. */
+  public static final int ENTITY   = CatalogEntry.addEntryType("ENTITY", 2);
+
+  /** The LINKTYPE Catalog Entry type. */
+  public static final int LINKTYPE = CatalogEntry.addEntryType("LINKTYPE", 2);
+
+  /** The NOTATION Catalog Entry type. */
+  public static final int NOTATION = CatalogEntry.addEntryType("NOTATION", 2);
+
+  /** The PUBLIC Catalog Entry type. */
+  public static final int PUBLIC   = CatalogEntry.addEntryType("PUBLIC", 2);
+
+  /** The SYSTEM Catalog Entry type. */
+  public static final int SYSTEM   = CatalogEntry.addEntryType("SYSTEM", 2);
+
+  /** The URI Catalog Entry type. */
+  public static final int URI      = CatalogEntry.addEntryType("URI", 2);
+
+  /** The REWRITE_SYSTEM Catalog Entry type. */
+  public static final int REWRITE_SYSTEM = CatalogEntry.addEntryType("REWRITE_SYSTEM", 2);
+
+  /** The REWRITE_URI Catalog Entry type. */
+  public static final int REWRITE_URI = CatalogEntry.addEntryType("REWRITE_URI", 2);
+  /** The SYSTEM_SUFFIX Catalog Entry type. */
+  public static final int SYSTEM_SUFFIX = CatalogEntry.addEntryType("SYSTEM_SUFFIX", 2);
+  /** The URI_SUFFIX Catalog Entry type. */
+  public static final int URI_SUFFIX = CatalogEntry.addEntryType("URI_SUFFIX", 2);
+
+  /**
+   * The base URI for relative system identifiers in the catalog.
+   * This may be changed by BASE entries in the catalog.
+   */
+  protected URL base;
+
+  /** The base URI of the Catalog file currently being parsed. */
+  protected URL catalogCwd;
+
+  /** The catalog entries currently known to the system. */
+  protected Vector catalogEntries = new Vector();
+
+  /** The default initial override setting. */
+  protected boolean default_override = true;
+
+  /** The catalog manager in use for this instance. */
+  protected CatalogManager catalogManager = CatalogManager.getStaticManager();
+
+  /**
+   * A vector of catalog files to be loaded.
+   *
+   * <p>This list is initially established by
+   * <code>loadSystemCatalogs</code> when
+   * it parses the system catalog list, but CATALOG entries may
+   * contribute to it during the course of parsing.</p>
+   *
+   * @see #loadSystemCatalogs
+   * @see #localCatalogFiles
+   */
+  protected Vector catalogFiles = new Vector();
+
+  /**
+   * A vector of catalog files constructed during processing of
+   * CATALOG entries in the current catalog.
+   *
+   * <p>This two-level system is actually necessary to correctly implement
+   * the semantics of the CATALOG entry. If one catalog file includes
+   * another with a CATALOG entry, the included catalog logically
+   * occurs <i>at the end</i> of the including catalog, and after any
+   * preceding CATALOG entries. In other words, the CATALOG entry
+   * cannot insert anything into the middle of a catalog file.</p>
+   *
+   * <p>When processing reaches the end of each catalog files, any
+   * elements on this vector are added to the front of the
+   * <code>catalogFiles</code> vector.</p>
+   *
+   * @see #catalogFiles
+   */
+  protected Vector localCatalogFiles = new Vector();
+
+  /**
+   * A vector of Catalogs.
+   *
+   * <p>The semantics of Catalog resolution are such that each
+   * catalog is effectively a list of Catalogs (in other words,
+   * a recursive list of Catalog instances).</p>
+   *
+   * <p>Catalogs that are processed as the result of CATALOG or
+   * DELEGATE* entries are subordinate to the catalog that contained
+   * them, but they may in turn have subordinate catalogs.</p>
+   *
+   * <p>Catalogs are only loaded when they are needed, so this vector
+   * initially contains a list of Catalog filenames (URLs). If, during
+   * processing, one of these catalogs has to be loaded, the resulting
+   * Catalog object is placed in the vector, effectively caching it
+   * for the next query.</p>
+   */
+  protected Vector catalogs = new Vector();
+
+  /**
+   * A vector of DELEGATE* Catalog entries constructed during
+   * processing of the Catalog.
+   *
+   * <p>This two-level system has two purposes; first, it allows
+   * us to sort the DELEGATE* entries by the length of the partial
+   * public identifier so that a linear search encounters them in
+   * the correct order and second, it puts them all at the end of
+   * the Catalog.</p>
+   *
+   * <p>When processing reaches the end of each catalog file, any
+   * elements on this vector are added to the end of the
+   * <code>catalogEntries</code> vector. This assures that matching
+   * PUBLIC keywords are encountered before DELEGATE* entries.</p>
+   */
+  protected Vector localDelegate = new Vector();
+
+  /**
+   * A hash of CatalogReaders.
+   *
+   * <p>This hash maps MIME types to elements in the readerArr
+   * vector. This allows the Catalog to quickly locate the reader
+   * for a particular MIME type.</p>
+   */
+  protected Hashtable readerMap = new Hashtable();
+
+  /**
+   * A vector of CatalogReaders.
+   *
+   * <p>This vector contains all of the readers in the order that they
+   * were added. In the event that a catalog is read from a file, where
+   * the MIME type is unknown, each reader is attempted in turn until
+   * one succeeds.</p>
+   */
+  protected Vector readerArr = new Vector();
+
+  /**
+   * Constructs an empty Catalog.
+   *
+   * <p>The constructor interrogates the relevant system properties
+   * using the default (static) CatalogManager
+   * and initializes the catalog data structures.</p>
+   */
+  public Catalog() {
+    // nop;
+  }
+
+  /**
+   * Constructs an empty Catalog with a specific CatalogManager.
+   *
+   * <p>The constructor interrogates the relevant system properties
+   * using the specified Catalog Manager
+   * and initializes the catalog data structures.</p>
+   */
+  public Catalog(CatalogManager manager) {
+    catalogManager = manager;
+  }
+
+  /**
+   * Return the CatalogManager used by this catalog.
+   *
+   */
+  public CatalogManager getCatalogManager() {
+    return catalogManager;
+  }
+
+  /**
+   * Establish the CatalogManager used by this catalog.
+   *
+   */
+  public void setCatalogManager(CatalogManager manager) {
+    catalogManager = manager;
+  }
+
+  /**
+   * Return all registered public IDs.
+   * This method added for solving the following issues in NetBeans codebase:
+   * http://www.netbeans.org/issues/show_bug.cgi?id=98212
+   * and 
+   * http://www.netbeans.org/issues/show_bug.cgi?id=112679
+   */
+  public java.util.Iterator getPublicIDs() {
+      Vector v = new Vector();
+      Enumeration enumeration = catalogEntries.elements();
+
+      while (enumeration.hasMoreElements()) {
+        CatalogEntry e = (CatalogEntry) enumeration.nextElement();
+        if (e.getEntryType() == PUBLIC) {
+            v.add(e.getEntryArg(0));
+        }
+      }
+      return v.iterator();
+  }
+
+  /**
+   * Setup readers.
+   */
+  public void setupReaders() {
+    SAXParserFactory spf = SAXParserFactory.newInstance();
+    spf.setNamespaceAware(true);
+    spf.setValidating(false);
+
+    SAXCatalogReader saxReader = new SAXCatalogReader(spf);
+
+    saxReader.setCatalogParser(null, "XCatalog",
+			       "org.apache.xml.resolver.readers.XCatalogReader");
+
+    saxReader.setCatalogParser(OASISXMLCatalogReader.namespaceName,
+			       "catalog",
+			       "org.apache.xml.resolver.readers.OASISXMLCatalogReader");
+
+    addReader("application/xml", saxReader);
+
+    TR9401CatalogReader textReader = new TR9401CatalogReader();
+    addReader("text/plain", textReader);
+  }
+
+  /**
+   * Add a new CatalogReader to the Catalog.
+   *
+   * <p>This method allows you to add a new CatalogReader to the
+   * catalog. The reader will be associated with the specified mimeType.
+   * You can only have one reader per mimeType.</p>
+   *
+   * <p>In the absence of a mimeType (e.g., when reading a catalog
+   * directly from a file on the local system), the readers are attempted
+   * in the order that you add them to the Catalog.</p>
+   *
+   * <p>Note that subordinate catalogs (created by CATALOG or
+   * DELEGATE* entries) get a copy of the set of readers present in
+   * the primary catalog when they are created. Readers added subsequently
+   * will not be available. For this reason, it is best to add all
+   * of the readers before the first call to parse a catalog.</p>
+   *
+   * @param mimeType The MIME type associated with this reader.
+   * @param reader The CatalogReader to use.
+   */
+  public void addReader(String mimeType, CatalogReader reader) {
+    if (readerMap.containsKey(mimeType)) {
+      Integer pos = (Integer) readerMap.get(mimeType);
+      readerArr.set(pos.intValue(), reader);
+    } else {
+      readerArr.add(reader);
+      Integer pos = new Integer(readerArr.size()-1);
+      readerMap.put(mimeType, pos);
+    }
+  }
+
+  /**
+   * Copies the reader list from the current Catalog to a new Catalog.
+   *
+   * <p>This method is used internally when constructing a new catalog.
+   * It copies the current reader associations over to the new catalog.
+   * </p>
+   *
+   * @param newCatalog The new Catalog.
+   */
+  protected void copyReaders(Catalog newCatalog) {
+    // Have to copy the readers in the right order...convert hash to arr
+    Vector mapArr = new Vector(readerMap.size());
+
+    // Pad the mapArr out to the right length
+    for (int count = 0; count < readerMap.size(); count++) {
+      mapArr.add(null);
+    }
+
+    Enumeration en = readerMap.keys();
+    while (en.hasMoreElements()) {
+      String mimeType = (String) en.nextElement();
+      Integer pos = (Integer) readerMap.get(mimeType);
+      mapArr.set(pos.intValue(), mimeType);
+    }
+
+    for (int count = 0; count < mapArr.size(); count++) {
+      String mimeType = (String) mapArr.get(count);
+      Integer pos = (Integer) readerMap.get(mimeType);
+      newCatalog.addReader(mimeType,
+			   (CatalogReader)
+			   readerArr.get(pos.intValue()));
+    }
+  }
+
+  /**
+   * Create a new Catalog object.
+   *
+   * <p>This method constructs a new instance of the running Catalog
+   * class (which might be a subtype of org.apache.xml.resolver.Catalog).
+   * All new catalogs are managed by the same CatalogManager.
+   * </p>
+   *
+   * <p>N.B. All Catalog subtypes should call newCatalog() to construct
+   * a new Catalog. Do not simply use "new Subclass()" since that will
+   * confuse future subclasses.</p>
+   */
+  protected Catalog newCatalog() {
+    String catalogClass = this.getClass().getName();
+
+    try {
+      Catalog c = (Catalog) (Class.forName(catalogClass).newInstance());
+      c.setCatalogManager(catalogManager);
+      copyReaders(c);
+      return c;
+    } catch (ClassNotFoundException cnfe) {
+      catalogManager.debug.message(1, "Class Not Found Exception: " + catalogClass);
+    } catch (IllegalAccessException iae) {
+      catalogManager.debug.message(1, "Illegal Access Exception: " + catalogClass);
+    } catch (InstantiationException ie) {
+      catalogManager.debug.message(1, "Instantiation Exception: " + catalogClass);
+    } catch (ClassCastException cce) {
+      catalogManager.debug.message(1, "Class Cast Exception: " + catalogClass);
+    } catch (Exception e) {
+      catalogManager.debug.message(1, "Other Exception: " + catalogClass);
+    }
+
+    Catalog c = new Catalog();
+    c.setCatalogManager(catalogManager);
+    copyReaders(c);
+    return c;
+  }
+
+  /**
+   * Returns the current base URI.
+   */
+  public String getCurrentBase() {
+    return base.toString();
+  }
+
+  /**
+   * Returns the default override setting associated with this
+   * catalog.
+   *
+   * <p>All catalog files loaded by this catalog will have the
+   * initial override setting specified by this default.</p>
+   */
+  public String getDefaultOverride() {
+    if (default_override) {
+      return "yes";
+    } else {
+      return "no";
+    }
+  }
+
+  /**
+   * Load the system catalog files.
+   *
+   * <p>The method adds all of the
+   * catalogs specified in the <tt>xml.catalog.files</tt> property
+   * to the Catalog list.</p>
+   *
+   * @throws MalformedURLException  One of the system catalogs is
+   * identified with a filename that is not a valid URL.
+   * @throws IOException One of the system catalogs cannot be read.
+   */
+  public void loadSystemCatalogs()
+    throws MalformedURLException, IOException {
+
+    Vector catalogs = catalogManager.getCatalogFiles();
+    if (catalogs != null) {
+      for (int count = 0; count < catalogs.size(); count++) {
+	catalogFiles.addElement(catalogs.elementAt(count));
+      }
+    }
+
+    if (catalogFiles.size() > 0) {
+      // This is a little odd. The parseCatalog() method expects
+      // a filename, but it adds that name to the end of the
+      // catalogFiles vector, and then processes that vector.
+      // This allows the system to handle CATALOG entries
+      // correctly.
+      //
+      // In this init case, we take the last element off the
+      // catalogFiles vector and pass it to parseCatalog. This
+      // will "do the right thing" in the init case, and allow
+      // parseCatalog() to do the right thing in the non-init
+      // case. Honest.
+      //
+      String catfile = (String) catalogFiles.lastElement();
+      catalogFiles.removeElement(catfile);
+      parseCatalog(catfile);
+    }
+  }
+
+  /**
+   * Parse a catalog file, augmenting internal data structures.
+   *
+   * @param fileName The filename of the catalog file to process
+   *
+   * @throws MalformedURLException The fileName cannot be turned into
+   * a valid URL.
+   * @throws IOException Error reading catalog file.
+   */
+  public synchronized void parseCatalog(String fileName)
+    throws MalformedURLException, IOException {
+
+    default_override = catalogManager.getPreferPublic();
+    catalogManager.debug.message(4, "Parse catalog: " + fileName);
+
+    // Put the file into the list of catalogs to process...
+    // In all cases except the case when initCatalog() is the
+    // caller, this will be the only catalog initially in the list...
+    catalogFiles.addElement(fileName);
+
+    // Now process all the pending catalogs...
+    parsePendingCatalogs();
+  }
+
+  /**
+   * Parse a catalog file, augmenting internal data structures.
+   *
+   * <p>Catalogs retrieved over the net may have an associated MIME type.
+   * The MIME type can be used to select an appropriate reader.</p>
+   *
+   * @param mimeType The MIME type of the catalog file.
+   * @param is The InputStream from which the catalog should be read
+   *
+   * @throws CatalogException Failed to load catalog
+   * mimeType.
+   * @throws IOException Error reading catalog file.
+   */
+  public synchronized void parseCatalog(String mimeType, InputStream is)
+    throws IOException, CatalogException {
+
+    default_override = catalogManager.getPreferPublic();
+    catalogManager.debug.message(4, "Parse " + mimeType + " catalog on input stream");
+
+    CatalogReader reader = null;
+
+    if (readerMap.containsKey(mimeType)) {
+      int arrayPos = ((Integer) readerMap.get(mimeType)).intValue();
+      reader = (CatalogReader) readerArr.get(arrayPos);
+    }
+
+    if (reader == null) {
+      String msg = "No CatalogReader for MIME type: " + mimeType;
+      catalogManager.debug.message(2, msg);
+      throw new CatalogException(CatalogException.UNPARSEABLE, msg);
+    }
+
+    reader.readCatalog(this, is);
+
+    // Now process all the pending catalogs...
+    parsePendingCatalogs();
+  }
+
+  /**
+   * Parse a catalog document, augmenting internal data structures.
+   *
+   * <p>This method supports catalog files stored in jar files: e.g.,
+   * jar:file:///path/to/filename.jar!/path/to/catalog.xml". That URI
+   * doesn't survive transmogrification through the URI processing that
+   * the parseCatalog(String) performs and passing it as an input stream
+   * doesn't set the base URI appropriately.</p>
+   *
+   * <p>Written by Stefan Wachter (2002-09-26)</p>
+   *
+   * @param aUrl The URL of the catalog document to process
+   *
+   * @throws IOException Error reading catalog file.
+   */
+  public synchronized void parseCatalog(URL aUrl) throws IOException {
+    catalogCwd = aUrl;
+    base = aUrl;
+
+    default_override = catalogManager.getPreferPublic();
+    catalogManager.debug.message(4, "Parse catalog: " + aUrl.toString());
+
+    DataInputStream inStream = null;
+    boolean parsed = false;
+
+    for (int count = 0; !parsed && count < readerArr.size(); count++) {
+      CatalogReader reader = (CatalogReader) readerArr.get(count);
+
+      try {
+      	inStream = new DataInputStream(aUrl.openStream());
+      } catch (FileNotFoundException fnfe) {
+        // No catalog; give up!
+        break;
+      }
+
+      try {
+        reader.readCatalog(this, inStream);
+        parsed=true;
+      } catch (CatalogException ce) {
+        if (ce.getExceptionType() == CatalogException.PARSE_FAILED) {
+          // give up!
+          break;
+        } else {
+          // try again!
+        }
+      }
+
+      try {
+      	inStream.close();
+      } catch (IOException e) {
+      	//nop
+      }
+    }
+
+    if (parsed) parsePendingCatalogs();
+  }
+
+  /**
+   * Parse all of the pending catalogs.
+   *
+   * <p>Catalogs may refer to other catalogs, this method parses
+   * all of the currently pending catalog files.</p>
+   */
+  protected synchronized void parsePendingCatalogs()
+    throws MalformedURLException, IOException {
+
+    if (!localCatalogFiles.isEmpty()) {
+      // Move all the localCatalogFiles into the front of
+      // the catalogFiles queue
+      Vector newQueue = new Vector();
+      Enumeration q = localCatalogFiles.elements();
+      while (q.hasMoreElements()) {
+	newQueue.addElement(q.nextElement());
+      }
+
+      // Put the rest of the catalogs on the end of the new list
+      for (int curCat = 0; curCat < catalogFiles.size(); curCat++) {
+	String catfile = (String) catalogFiles.elementAt(curCat);
+	newQueue.addElement(catfile);
+      }
+
+      catalogFiles = newQueue;
+      localCatalogFiles.clear();
+    }
+
+    // Suppose there are no catalog files to process, but the
+    // single catalog already parsed included some delegate
+    // entries? Make sure they don't get lost.
+    if (catalogFiles.isEmpty() && !localDelegate.isEmpty()) {
+      Enumeration e = localDelegate.elements();
+      while (e.hasMoreElements()) {
+	catalogEntries.addElement(e.nextElement());
+      }
+      localDelegate.clear();
+    }
+
+    // Now process all the files on the catalogFiles vector. This
+    // vector can grow during processing if CATALOG entries are
+    // encountered in the catalog
+    while (!catalogFiles.isEmpty()) {
+      String catfile = (String) catalogFiles.elementAt(0);
+      try {
+	catalogFiles.remove(0);
+      } catch (ArrayIndexOutOfBoundsException e) {
+	// can't happen
+      }
+
+      if (catalogEntries.size() == 0 && catalogs.size() == 0) {
+	// We haven't parsed any catalogs yet, let this
+	// catalog be the first...
+	try {
+	  parseCatalogFile(catfile);
+	} catch (CatalogException ce) {
+	  System.out.println("FIXME: " + ce.toString());
+	}
+      } else {
+	// This is a subordinate catalog. We save its name,
+	// but don't bother to load it unless it's necessary.
+	catalogs.addElement(catfile);
+      }
+
+      if (!localCatalogFiles.isEmpty()) {
+	// Move all the localCatalogFiles into the front of
+	// the catalogFiles queue
+	Vector newQueue = new Vector();
+	Enumeration q = localCatalogFiles.elements();
+	while (q.hasMoreElements()) {
+	  newQueue.addElement(q.nextElement());
+	}
+
+	// Put the rest of the catalogs on the end of the new list
+	for (int curCat = 0; curCat < catalogFiles.size(); curCat++) {
+	  catfile = (String) catalogFiles.elementAt(curCat);
+	  newQueue.addElement(catfile);
+	}
+
+	catalogFiles = newQueue;
+	localCatalogFiles.clear();
+      }
+
+      if (!localDelegate.isEmpty()) {
+	Enumeration e = localDelegate.elements();
+	while (e.hasMoreElements()) {
+	  catalogEntries.addElement(e.nextElement());
+	}
+	localDelegate.clear();
+      }
+    }
+
+    // We've parsed them all, reinit the vector...
+    catalogFiles.clear();
+  }
+
+  /**
+   * Parse a single catalog file, augmenting internal data structures.
+   *
+   * @param fileName The filename of the catalog file to process
+   *
+   * @throws MalformedURLException The fileName cannot be turned into
+   * a valid URL.
+   * @throws IOException Error reading catalog file.
+   */
+  protected synchronized void parseCatalogFile(String fileName)
+    throws MalformedURLException, IOException, CatalogException {
+
+    CatalogEntry entry;
+
+    // The base-base is the cwd. If the catalog file is specified
+    // with a relative path, this assures that it gets resolved
+    // properly...
+    try {
+      // tack on a basename because URLs point to files not dirs
+      catalogCwd = FileURL.makeURL("basename");
+    } catch (MalformedURLException e) {
+      String userdir = System.getProperty("user.dir");
+      userdir = userdir.replace('\\', '/');
+      catalogManager.debug.message(1, "Malformed URL on cwd", userdir);
+      catalogCwd = null;
+    }
+
+    // The initial base URI is the location of the catalog file
+    try {
+      base = new URL(catalogCwd, fixSlashes(fileName));
+    } catch (MalformedURLException e) {
+      try {
+	base = new URL("file:" + fixSlashes(fileName));
+      } catch (MalformedURLException e2) {
+	catalogManager.debug.message(1, "Malformed URL on catalog filename",
+		      fixSlashes(fileName));
+	base = null;
+      }
+    }
+
+    catalogManager.debug.message(2, "Loading catalog", fileName);
+    catalogManager.debug.message(4, "Default BASE", base.toString());
+
+    fileName = base.toString();
+
+    DataInputStream inStream = null;
+    boolean parsed = false;
+    boolean notFound = false;
+
+    for (int count = 0; !parsed && count < readerArr.size(); count++) {
+      CatalogReader reader = (CatalogReader) readerArr.get(count);
+
+      try {
+	notFound = false;
+	inStream = new DataInputStream(base.openStream());
+      } catch (FileNotFoundException fnfe) {
+	// No catalog; give up!
+	notFound = true;
+	break;
+      }
+
+      try {
+	reader.readCatalog(this, inStream);
+	parsed = true;
+      } catch (CatalogException ce) {
+	if (ce.getExceptionType() == CatalogException.PARSE_FAILED) {
+	  // give up!
+	  break;
+	} else {
+	  // try again!
+	}
+      }
+
+      try {
+	inStream.close();
+      } catch (IOException e) {
+	//nop
+      }
+    }
+
+    if (!parsed) {
+      if (notFound) {
+	catalogManager.debug.message(3, "Catalog does not exist", fileName);
+      } else {
+	catalogManager.debug.message(1, "Failed to parse catalog", fileName);
+      }
+    }
+  }
+
+  /**
+   * Cleanup and process a Catalog entry.
+   *
+   * <p>This method processes each Catalog entry, changing mapped
+   * relative system identifiers into absolute ones (based on the current
+   * base URI), and maintaining other information about the current
+   * catalog.</p>
+   *
+   * @param entry The CatalogEntry to process.
+   */
+  public void addEntry(CatalogEntry entry) {
+    int type = entry.getEntryType();
+
+    if (type == BASE) {
+      String value = entry.getEntryArg(0);
+      URL newbase = null;
+
+      if (base == null) {
+	catalogManager.debug.message(5, "BASE CUR", "null");
+      } else {
+	catalogManager.debug.message(5, "BASE CUR", base.toString());
+      }
+      catalogManager.debug.message(4, "BASE STR", value);
+
+      try {
+	value = fixSlashes(value);
+	newbase = new URL(base, value);
+      } catch (MalformedURLException e) {
+	try {
+	  newbase = new URL("file:" + value);
+	} catch (MalformedURLException e2) {
+	  catalogManager.debug.message(1, "Malformed URL on base", value);
+	  newbase = null;
+	}
+      }
+
+      if (newbase != null) {
+	base = newbase;
+      }
+
+      catalogManager.debug.message(5, "BASE NEW", base.toString());
+    } else if (type == CATALOG) {
+      String fsi = makeAbsolute(entry.getEntryArg(0));
+
+      catalogManager.debug.message(4, "CATALOG", fsi);
+
+      localCatalogFiles.addElement(fsi);
+    } else if (type == PUBLIC) {
+      String publicid = PublicId.normalize(entry.getEntryArg(0));
+      String systemid = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
+
+      entry.setEntryArg(0, publicid);
+      entry.setEntryArg(1, systemid);
+
+      catalogManager.debug.message(4, "PUBLIC", publicid, systemid);
+
+      catalogEntries.addElement(entry);
+    } else if (type == SYSTEM) {
+      String systemid = normalizeURI(entry.getEntryArg(0));
+      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
+
+      entry.setEntryArg(1, fsi);
+
+      catalogManager.debug.message(4, "SYSTEM", systemid, fsi);
+
+      catalogEntries.addElement(entry);
+    } else if (type == URI) {
+      String uri = normalizeURI(entry.getEntryArg(0));
+      String altURI = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
+
+      entry.setEntryArg(1, altURI);
+
+      catalogManager.debug.message(4, "URI", uri, altURI);
+
+      catalogEntries.addElement(entry);
+    } else if (type == DOCUMENT) {
+      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(0)));
+      entry.setEntryArg(0, fsi);
+
+      catalogManager.debug.message(4, "DOCUMENT", fsi);
+
+      catalogEntries.addElement(entry);
+    } else if (type == OVERRIDE) {
+      catalogManager.debug.message(4, "OVERRIDE", entry.getEntryArg(0));
+
+      catalogEntries.addElement(entry);
+    } else if (type == SGMLDECL) {
+      // meaningless in XML
+      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(0)));
+      entry.setEntryArg(0, fsi);
+
+      catalogManager.debug.message(4, "SGMLDECL", fsi);
+
+      catalogEntries.addElement(entry);
+    } else if (type == DELEGATE_PUBLIC) {
+      String ppi = PublicId.normalize(entry.getEntryArg(0));
+      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
+
+      entry.setEntryArg(0, ppi);
+      entry.setEntryArg(1, fsi);
+
+      catalogManager.debug.message(4, "DELEGATE_PUBLIC", ppi, fsi);
+
+      addDelegate(entry);
+    } else if (type == DELEGATE_SYSTEM) {
+      String psi = normalizeURI(entry.getEntryArg(0));
+      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
+
+      entry.setEntryArg(0, psi);
+      entry.setEntryArg(1, fsi);
+
+      catalogManager.debug.message(4, "DELEGATE_SYSTEM", psi, fsi);
+
+      addDelegate(entry);
+    } else if (type == DELEGATE_URI) {
+      String pui = normalizeURI(entry.getEntryArg(0));
+      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
+
+      entry.setEntryArg(0, pui);
+      entry.setEntryArg(1, fsi);
+
+      catalogManager.debug.message(4, "DELEGATE_URI", pui, fsi);
+
+      addDelegate(entry);
+    } else if (type == REWRITE_SYSTEM) {
+      String psi = normalizeURI(entry.getEntryArg(0));
+      String rpx = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
+
+      entry.setEntryArg(0, psi);
+      entry.setEntryArg(1, rpx);
+
+      catalogManager.debug.message(4, "REWRITE_SYSTEM", psi, rpx);
+
+      catalogEntries.addElement(entry);
+    } else if (type == REWRITE_URI) {
+      String pui = normalizeURI(entry.getEntryArg(0));
+      String upx = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
+
+      entry.setEntryArg(0, pui);
+      entry.setEntryArg(1, upx);
+
+      catalogManager.debug.message(4, "REWRITE_URI", pui, upx);
+
+      catalogEntries.addElement(entry);
+    } else if (type == SYSTEM_SUFFIX) {
+      String pui = normalizeURI(entry.getEntryArg(0));
+      String upx = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
+
+      entry.setEntryArg(0, pui);
+      entry.setEntryArg(1, upx);
+
+      catalogManager.debug.message(4, "SYSTEM_SUFFIX", pui, upx);
+
+      catalogEntries.addElement(entry);
+    } else if (type == URI_SUFFIX) {
+      String pui = normalizeURI(entry.getEntryArg(0));
+      String upx = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
+
+      entry.setEntryArg(0, pui);
+      entry.setEntryArg(1, upx);
+
+      catalogManager.debug.message(4, "URI_SUFFIX", pui, upx);
+
+      catalogEntries.addElement(entry);
+    } else if (type == DOCTYPE) {
+      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
+      entry.setEntryArg(1, fsi);
+
+      catalogManager.debug.message(4, "DOCTYPE", entry.getEntryArg(0), fsi);
+
+      catalogEntries.addElement(entry);
+    } else if (type == DTDDECL) {
+      // meaningless in XML
+      String fpi = PublicId.normalize(entry.getEntryArg(0));
+      entry.setEntryArg(0, fpi);
+      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
+      entry.setEntryArg(1, fsi);
+
+      catalogManager.debug.message(4, "DTDDECL", fpi, fsi);
+
+      catalogEntries.addElement(entry);
+    } else if (type == ENTITY) {
+      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
+      entry.setEntryArg(1, fsi);
+
+      catalogManager.debug.message(4, "ENTITY", entry.getEntryArg(0), fsi);
+
+      catalogEntries.addElement(entry);
+    } else if (type == LINKTYPE) {
+      // meaningless in XML
+      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
+      entry.setEntryArg(1, fsi);
+
+      catalogManager.debug.message(4, "LINKTYPE", entry.getEntryArg(0), fsi);
+
+      catalogEntries.addElement(entry);
+    } else if (type == NOTATION) {
+      String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1)));
+      entry.setEntryArg(1, fsi);
+
+      catalogManager.debug.message(4, "NOTATION", entry.getEntryArg(0), fsi);
+
+      catalogEntries.addElement(entry);
+    } else {
+      catalogEntries.addElement(entry);
+    }
+  }
+
+  /**
+   * Handle unknown CatalogEntry types.
+   *
+   * <p>This method exists to allow subclasses to deal with unknown
+   * entry types.</p>
+   */
+  public void unknownEntry(Vector strings) {
+    if (strings != null && strings.size() > 0) {
+      String keyword = (String) strings.elementAt(0);
+      catalogManager.debug.message(2, "Unrecognized token parsing catalog", keyword);
+    }
+  }
+
+  /**
+   * Parse all subordinate catalogs.
+   *
+   * <p>This method recursively parses all of the subordinate catalogs.
+   * If this method does not throw an exception, you can be confident that
+   * no subsequent call to any resolve*() method will either, with two
+   * possible exceptions:</p>
+   *
+   * <ol>
+   * <li><p>Delegated catalogs are re-parsed each time they are needed
+   * (because a variable list of them may be needed in each case,
+   * depending on the length of the matching partial public identifier).</p>
+   * <p>But they are parsed by this method, so as long as they don't
+   * change or disappear while the program is running, they shouldn't
+   * generate errors later if they don't generate errors now.</p>
+   * <li><p>If you add new catalogs with <code>parseCatalog</code>, they
+   * won't be loaded until they are needed or until you call
+   * <code>parseAllCatalogs</code> again.</p>
+   * </ol>
+   *
+   * <p>On the other hand, if you don't call this method, you may
+   * successfully parse documents without having to load all possible
+   * catalogs.</p>
+   *
+   * @throws MalformedURLException The filename (URL) for a
+   * subordinate or delegated catalog is not a valid URL.
+   * @throws IOException Error reading some subordinate or delegated
+   * catalog file.
+   */
+  public void parseAllCatalogs()
+    throws MalformedURLException, IOException {
+
+    // Parse all the subordinate catalogs
+    for (int catPos = 0; catPos < catalogs.size(); catPos++) {
+      Catalog c = null;
+
+      try {
+	c = (Catalog) catalogs.elementAt(catPos);
+      } catch (ClassCastException e) {
+	String catfile = (String) catalogs.elementAt(catPos);
+	c = newCatalog();
+
+	c.parseCatalog(catfile);
+	catalogs.setElementAt(c, catPos);
+	c.parseAllCatalogs();
+      }
+    }
+
+    // Parse all the DELEGATE catalogs
+    Enumeration en = catalogEntries.elements();
+    while (en.hasMoreElements()) {
+      CatalogEntry e = (CatalogEntry) en.nextElement();
+      if (e.getEntryType() == DELEGATE_PUBLIC
+	  || e.getEntryType() == DELEGATE_SYSTEM
+	  || e.getEntryType() == DELEGATE_URI) {
+	Catalog dcat = newCatalog();
+	dcat.parseCatalog(e.getEntryArg(1));
+      }
+    }
+  }
+
+
+  /**
+   * Return the applicable DOCTYPE system identifier.
+   *
+   * @param entityName The name of the entity (element) for which
+   * a doctype is required.
+   * @param publicId The nominal public identifier for the doctype
+   * (as provided in the source document).
+   * @param systemId The nominal system identifier for the doctype
+   * (as provided in the source document).
+   *
+   * @return The system identifier to use for the doctype.
+   *
+   * @throws MalformedURLException The formal system identifier of a
+   * subordinate catalog cannot be turned into a valid URL.
+   * @throws IOException Error reading subordinate catalog file.
+   */
+  public String resolveDoctype(String entityName,
+			       String publicId,
+			       String systemId)
+    throws MalformedURLException, IOException {
+    String resolved = null;
+
+    catalogManager.debug.message(3, "resolveDoctype("
+		  +entityName+","+publicId+","+systemId+")");
+
+    systemId = normalizeURI(systemId);
+
+    if (publicId != null && publicId.startsWith("urn:publicid:")) {
+      publicId = PublicId.decodeURN(publicId);
+    }
+
+    if (systemId != null && systemId.startsWith("urn:publicid:")) {
+      systemId = PublicId.decodeURN(systemId);
+      if (publicId != null && !publicId.equals(systemId)) {
+	catalogManager.debug.message(1, "urn:publicid: system identifier differs from public identifier; using public identifier");
+	systemId = null;
+      } else {
+	publicId = systemId;
+	systemId = null;
+      }
+    }
+
+    if (systemId != null) {
+      // If there's a SYSTEM entry in this catalog, use it
+      resolved = resolveLocalSystem(systemId);
+      if (resolved != null) {
+	return resolved;
+      }
+    }
+
+    if (publicId != null) {
+      // If there's a PUBLIC entry in this catalog, use it
+      resolved = resolveLocalPublic(DOCTYPE,
+				    entityName,
+				    publicId,
+				    systemId);
+      if (resolved != null) {
+	return resolved;
+      }
+    }
+
+    // If there's a DOCTYPE entry in this catalog, use it
+    boolean over = default_override;
+    Enumeration en = catalogEntries.elements();
+    while (en.hasMoreElements()) {
+      CatalogEntry e = (CatalogEntry) en.nextElement();
+      if (e.getEntryType() == OVERRIDE) {
+	over = e.getEntryArg(0).equalsIgnoreCase("YES");
+	continue;
+      }
+
+      if (e.getEntryType() == DOCTYPE
+	  && e.getEntryArg(0).equals(entityName)) {
+	if (over || systemId == null) {
+	  return e.getEntryArg(1);
+	}
+      }
+    }
+
+    // Otherwise, look in the subordinate catalogs
+    return resolveSubordinateCatalogs(DOCTYPE,
+				      entityName,
+				      publicId,
+				      systemId);
+  }
+
+  /**
+   * Return the applicable DOCUMENT entry.
+   *
+   * @return The system identifier to use for the doctype.
+   *
+   * @throws MalformedURLException The formal system identifier of a
+   * subordinate catalog cannot be turned into a valid URL.
+   * @throws IOException Error reading subordinate catalog file.
+   */
+  public String resolveDocument()
+    throws MalformedURLException, IOException {
+    // If there's a DOCUMENT entry, return it
+
+    catalogManager.debug.message(3, "resolveDocument");
+
+    Enumeration en = catalogEntries.elements();
+    while (en.hasMoreElements()) {
+      CatalogEntry e = (CatalogEntry) en.nextElement();
+      if (e.getEntryType() == DOCUMENT) {
+	return e.getEntryArg(0);
+      }
+    }
+
+    return resolveSubordinateCatalogs(DOCUMENT,
+				      null, null, null);
+  }
+
+  /**
+   * Return the applicable ENTITY system identifier.
+   *
+   * @param entityName The name of the entity for which
+   * a system identifier is required.
+   * @param publicId The nominal public identifier for the entity
+   * (as provided in the source document).
+   * @param systemId The nominal system identifier for the entity
+   * (as provided in the source document).
+   *
+   * @return The system identifier to use for the entity.
+   *
+   * @throws MalformedURLException The formal system identifier of a
+   * subordinate catalog cannot be turned into a valid URL.
+   * @throws IOException Error reading subordinate catalog file.
+   */
+  public String resolveEntity(String entityName,
+			      String publicId,
+			      String systemId)
+    throws MalformedURLException, IOException {
+    String resolved = null;
+
+    catalogManager.debug.message(3, "resolveEntity("
+		  +entityName+","+publicId+","+systemId+")");
+
+    systemId = normalizeURI(systemId);
+
+    if (publicId != null && publicId.startsWith("urn:publicid:")) {
+      publicId = PublicId.decodeURN(publicId);
+    }
+
+    if (systemId != null && systemId.startsWith("urn:publicid:")) {
+      systemId = PublicId.decodeURN(systemId);
+      if (publicId != null && !publicId.equals(systemId)) {
+	catalogManager.debug.message(1, "urn:publicid: system identifier differs from public identifier; using public identifier");
+	systemId = null;
+      } else {
+	publicId = systemId;
+	systemId = null;
+      }
+    }
+
+    if (systemId != null) {
+      // If there's a SYSTEM entry in this catalog, use it
+      resolved = resolveLocalSystem(systemId);
+      if (resolved != null) {
+	return resolved;
+      }
+    }
+
+    if (publicId != null) {
+      // If there's a PUBLIC entry in this catalog, use it
+      resolved = resolveLocalPublic(ENTITY,
+				    entityName,
+				    publicId,
+				    systemId);
+      if (resolved != null) {
+	return resolved;
+      }
+    }
+
+    // If there's a ENTITY entry in this catalog, use it
+    boolean over = default_override;
+    Enumeration en = catalogEntries.elements();
+    while (en.hasMoreElements()) {
+      CatalogEntry e = (CatalogEntry) en.nextElement();
+      if (e.getEntryType() == OVERRIDE) {
+	over = e.getEntryArg(0).equalsIgnoreCase("YES");
+	continue;
+      }
+
+      if (e.getEntryType() == ENTITY
+	  && e.getEntryArg(0).equals(entityName)) {
+	if (over || systemId == null) {
+	  return e.getEntryArg(1);
+	}
+      }
+    }
+
+    // Otherwise, look in the subordinate catalogs
+    return resolveSubordinateCatalogs(ENTITY,
+				      entityName,
+				      publicId,
+				      systemId);
+  }
+
+  /**
+   * Return the applicable NOTATION system identifier.
+   *
+   * @param notationName The name of the notation for which
+   * a doctype is required.
+   * @param publicId The nominal public identifier for the notation
+   * (as provided in the source document).
+   * @param systemId The nominal system identifier for the notation
+   * (as provided in the source document).
+   *
+   * @return The system identifier to use for the notation.
+   *
+   * @throws MalformedURLException The formal system identifier of a
+   * subordinate catalog cannot be turned into a valid URL.
+   * @throws IOException Error reading subordinate catalog file.
+   */
+  public String resolveNotation(String notationName,
+				String publicId,
+				String systemId)
+    throws MalformedURLException, IOException {
+    String resolved = null;
+
+    catalogManager.debug.message(3, "resolveNotation("
+		  +notationName+","+publicId+","+systemId+")");
+
+    systemId = normalizeURI(systemId);
+
+    if (publicId != null && publicId.startsWith("urn:publicid:")) {
+      publicId = PublicId.decodeURN(publicId);
+    }
+
+    if (systemId != null && systemId.startsWith("urn:publicid:")) {
+      systemId = PublicId.decodeURN(systemId);
+      if (publicId != null && !publicId.equals(systemId)) {
+	catalogManager.debug.message(1, "urn:publicid: system identifier differs from public identifier; using public identifier");
+	systemId = null;
+      } else {
+	publicId = systemId;
+	systemId = null;
+      }
+    }
+
+    if (systemId != null) {
+      // If there's a SYSTEM entry in this catalog, use it
+      resolved = resolveLocalSystem(systemId);
+      if (resolved != null) {
+	return resolved;
+      }
+    }
+
+    if (publicId != null) {
+      // If there's a PUBLIC entry in this catalog, use it
+      resolved = resolveLocalPublic(NOTATION,
+				    notationName,
+				    publicId,
+				    systemId);
+      if (resolved != null) {
+	return resolved;
+      }
+    }
+
+    // If there's a NOTATION entry in this catalog, use it
+    boolean over = default_override;
+    Enumeration en = catalogEntries.elements();
+    while (en.hasMoreElements()) {
+      CatalogEntry e = (CatalogEntry) en.nextElement();
+      if (e.getEntryType() == OVERRIDE) {
+	over = e.getEntryArg(0).equalsIgnoreCase("YES");
+	continue;
+      }
+
+      if (e.getEntryType() == NOTATION
+	  && e.getEntryArg(0).equals(notationName)) {
+	if (over || systemId == null) {
+	  return e.getEntryArg(1);
+	}
+      }
+    }
+
+    // Otherwise, look in the subordinate catalogs
+    return resolveSubordinateCatalogs(NOTATION,
+				      notationName,
+				      publicId,
+				      systemId);
+  }
+
+  /**
+   * Return the applicable PUBLIC or SYSTEM identifier.
+   *
+   * <p>This method searches the Catalog and returns the system
+   * identifier specified for the given system or
+   * public identifiers. If
+   * no appropriate PUBLIC or SYSTEM entry is found in the Catalog,
+   * null is returned.</p>
+   *
+   * @param publicId The public identifier to locate in the catalog.
+   * Public identifiers are normalized before comparison.
+   * @param systemId The nominal system identifier for the entity
+   * in question (as provided in the source document).
+   *
+   * @throws MalformedURLException The formal system identifier of a
+   * subordinate catalog cannot be turned into a valid URL.
+   * @throws IOException Error reading subordinate catalog file.
+   *
+   * @return The system identifier to use.
+   * Note that the nominal system identifier is not returned if a
+   * match is not found in the catalog, instead null is returned
+   * to indicate that no match was found.
+   */
+  public String resolvePublic(String publicId, String systemId) 
+    throws MalformedURLException, IOException {
+
+    catalogManager.debug.message(3, "resolvePublic("+publicId+","+systemId+")");
+
+    systemId = normalizeURI(systemId);
+
+    if (publicId != null && publicId.startsWith("urn:publicid:")) {
+      publicId = PublicId.decodeURN(publicId);
+    }
+
+    if (systemId != null && systemId.startsWith("urn:publicid:")) {
+      systemId = PublicId.decodeURN(systemId);
+      if (publicId != null && !publicId.equals(systemId)) {
+	catalogManager.debug.message(1, "urn:publicid: system identifier differs from public identifier; using public identifier");
+	systemId = null;
+      } else {
+	publicId = systemId;
+	systemId = null;
+      }
+    }
+
+    // If there's a SYSTEM entry in this catalog, use it
+    if (systemId != null) {
+      String resolved = resolveLocalSystem(systemId);
+      if (resolved != null) {
+	return resolved;
+      }
+    }
+
+    // If there's a PUBLIC entry in this catalog, use it
+    String resolved = resolveLocalPublic(PUBLIC,
+					 null,
+					 publicId,
+					 systemId);
+    if (resolved != null) {
+      return resolved;
+    }
+
+    // Otherwise, look in the subordinate catalogs
+    return resolveSubordinateCatalogs(PUBLIC,
+				      null,
+				      publicId,
+				      systemId);
+  }
+
+  /**
+   * Return the applicable PUBLIC or SYSTEM identifier.
+   *
+   * <p>This method searches the Catalog and returns the system
+   * identifier specified for the given system or public identifiers.
+   * If no appropriate PUBLIC or SYSTEM entry is found in the Catalog,
+   * delegated Catalogs are interrogated.</p>
+   *
+   * <p>There are four possible cases:</p>
+   *
+   * <ul>
+   * <li>If the system identifier provided matches a SYSTEM entry
+   * in the current catalog, the SYSTEM entry is returned.
+   * <li>If the system identifier is not null, the PUBLIC entries
+   * that were encountered when OVERRIDE YES was in effect are
+   * interrogated and the first matching entry is returned.</li>
+   * <li>If the system identifier is null, then all of the PUBLIC
+   * entries are interrogated and the first matching entry
+   * is returned. This may not be the same as the preceding case, if
+   * some PUBLIC entries are encountered when OVERRIDE NO is in effect. In
+   * XML, the only place where a public identifier may occur without
+   * a system identifier is in a notation declaration.</li>
+   * <li>Finally, if the public identifier matches one of the partial
+   * public identifiers specified in a DELEGATE* entry in
+   * the Catalog, the delegated catalog is interrogated. The first
+   * time that the delegated catalog is required, it will be
+   * retrieved and parsed. It is subsequently cached.
+   * </li>
+   * </ul>
+   *
+   * @param entityType The CatalogEntry type for which this query is
+   * being conducted. This is necessary in order to do the approprate
+   * query on a delegated catalog.
+   * @param entityName The name of the entity being searched for, if
+   * appropriate.
+   * @param publicId The public identifier of the entity in question.
+   * @param systemId The nominal system identifier for the entity
+   * in question (as provided in the source document).
+   *
+   * @throws MalformedURLException The formal system identifier of a
+   * delegated catalog cannot be turned into a valid URL.
+   * @throws IOException Error reading delegated catalog file.
+   *
+   * @return The system identifier to use.
+   * Note that the nominal system identifier is not returned if a
+   * match is not found in the catalog, instead null is returned
+   * to indicate that no match was found.
+   */
+  protected synchronized String resolveLocalPublic(int entityType,
+						   String entityName,
+						   String publicId,
+						   String systemId)
+    throws MalformedURLException, IOException {
+
+    // Always normalize the public identifier before attempting a match
+    publicId = PublicId.normalize(publicId);
+
+    // If there's a SYSTEM entry in this catalog, use it
+    if (systemId != null) {
+      String resolved = resolveLocalSystem(systemId);
+      if (resolved != null) {
+	return resolved;
+      }
+    }
+
+    // If there's a PUBLIC entry in this catalog, use it
+    boolean over = default_override;
+    Enumeration en = catalogEntries.elements();
+    while (en.hasMoreElements()) {
+      CatalogEntry e = (CatalogEntry) en.nextElement();
+      if (e.getEntryType() == OVERRIDE) {
+	over = e.getEntryArg(0).equalsIgnoreCase("YES");
+	continue;
+      }
+
+      if (e.getEntryType() == PUBLIC
+	  && e.getEntryArg(0).equals(publicId)) {
+	if (over || systemId == null) {
+	  return e.getEntryArg(1);
+	}
+      }
+    }
+
+    // If there's a DELEGATE_PUBLIC entry in this catalog, use it
+    over = default_override;
+    en = catalogEntries.elements();
+    Vector delCats = new Vector();
+    while (en.hasMoreElements()) {
+      CatalogEntry e = (CatalogEntry) en.nextElement();
+      if (e.getEntryType() == OVERRIDE) {
+	over = e.getEntryArg(0).equalsIgnoreCase("YES");
+	continue;
+      }
+
+      if (e.getEntryType() == DELEGATE_PUBLIC
+	  && (over || systemId == null)) {
+	String p = (String) e.getEntryArg(0);
+	if (p.length() <= publicId.length()
+	    && p.equals(publicId.substring(0, p.length()))) {
+	  // delegate this match to the other catalog
+
+	  delCats.addElement(e.getEntryArg(1));
+	}
+      }
+    }
+
+    if (delCats.size() > 0) {
+      Enumeration enCats = delCats.elements();
+
+      if (catalogManager.debug.getDebug() > 1) {
+	catalogManager.debug.message(2, "Switching to delegated catalog(s):");
+	while (enCats.hasMoreElements()) {
+	  String delegatedCatalog = (String) enCats.nextElement();
+	  catalogManager.debug.message(2, "\t" + delegatedCatalog);
+	}
+      }
+
+      Catalog dcat = newCatalog();
+
+      enCats = delCats.elements();
+      while (enCats.hasMoreElements()) {
+	String delegatedCatalog = (String) enCats.nextElement();
+	dcat.parseCatalog(delegatedCatalog);
+      }
+
+      return dcat.resolvePublic(publicId, null);
+    }
+
+    // Nada!
+    return null;
+  }
+
+  /**
+   * Return the applicable SYSTEM system identifier.
+   *
+   * <p>If a SYSTEM entry exists in the Catalog
+   * for the system ID specified, return the mapped value.</p>
+   *
+   * <p>On Windows-based operating systems, the comparison between
+   * the system identifier provided and the SYSTEM entries in the
+   * Catalog is case-insensitive.</p>
+   *
+   * @param systemId The system ID to locate in the catalog.
+   *
+   * @return The resolved system identifier.
+   *
+   * @throws MalformedURLException The formal system identifier of a
+   * subordinate catalog cannot be turned into a valid URL.
+   * @throws IOException Error reading subordinate catalog file.
+   */
+  public String resolveSystem(String systemId)
+    throws MalformedURLException, IOException {
+
+    catalogManager.debug.message(3, "resolveSystem("+systemId+")");
+
+    systemId = normalizeURI(systemId);
+
+    if (systemId != null && systemId.startsWith("urn:publicid:")) {
+      systemId = PublicId.decodeURN(systemId);
+      return resolvePublic(systemId, null);
+    }
+
+    // If there's a SYSTEM entry in this catalog, use it
+    if (systemId != null) {
+      String resolved = resolveLocalSystem(systemId);
+      if (resolved != null) {
+	return resolved;
+      }
+    }
+
+    // Otherwise, look in the subordinate catalogs
+    return resolveSubordinateCatalogs(SYSTEM,
+				      null,
+				      null,
+				      systemId);
+  }
+
+  /**
+   * Return the applicable SYSTEM system identifier in this
+   * catalog.
+   *
+   * <p>If a SYSTEM entry exists in the catalog file
+   * for the system ID specified, return the mapped value.</p>
+   *
+   * @param systemId The system ID to locate in the catalog
+   *
+   * @return The mapped system identifier or null
+   */
+  protected String resolveLocalSystem(String systemId)
+    throws MalformedURLException, IOException {
+
+    String osname = System.getProperty("os.name");
+    boolean windows = (osname.indexOf("Windows") >= 0);
+    Enumeration en = catalogEntries.elements();
+    while (en.hasMoreElements()) {
+      CatalogEntry e = (CatalogEntry) en.nextElement();
+      if (e.getEntryType() == SYSTEM
+	  && (e.getEntryArg(0).equals(systemId)
+	      || (windows
+		  && e.getEntryArg(0).equalsIgnoreCase(systemId)))) {
+	return e.getEntryArg(1);
+      }
+    }
+
+    // If there's a REWRITE_SYSTEM entry in this catalog, use it
+    en = catalogEntries.elements();
+    String startString = null;
+    String prefix = null;
+    while (en.hasMoreElements()) {
+      CatalogEntry e = (CatalogEntry) en.nextElement();
+
+      if (e.getEntryType() == REWRITE_SYSTEM) {
+	String p = (String) e.getEntryArg(0);
+	if (p.length() <= systemId.length()
+	    && p.equals(systemId.substring(0, p.length()))) {
+	  // Is this the longest prefix?
+	  if (startString == null
+	      || p.length() > startString.length()) {
+	    startString = p;
+	    prefix = e.getEntryArg(1);
+	  }
+	}
+      }
+    }
+
+    if (prefix != null) {
+      // return the systemId with the new prefix
+      return prefix + systemId.substring(startString.length());
+    }
+
+    // If there's a SYSTEM_SUFFIX entry in this catalog, use it
+    en = catalogEntries.elements();
+    String suffixString = null;
+    String suffixURI = null;
+    while (en.hasMoreElements()) {
+      CatalogEntry e = (CatalogEntry) en.nextElement();
+
+      if (e.getEntryType() == SYSTEM_SUFFIX) {
+	String p = (String) e.getEntryArg(0);
+	if (p.length() <= systemId.length()
+	    && systemId.endsWith(p)) {
+	  // Is this the longest prefix?
+	  if (suffixString == null
+	      || p.length() > suffixString.length()) {
+	    suffixString = p;
+	    suffixURI = e.getEntryArg(1);
+	  }
+	}
+      }
+    }
+
+    if (suffixURI != null) {
+      // return the systemId for the suffix
+      return suffixURI;
+    }
+
+    // If there's a DELEGATE_SYSTEM entry in this catalog, use it
+    en = catalogEntries.elements();
+    Vector delCats = new Vector();
+    while (en.hasMoreElements()) {
+      CatalogEntry e = (CatalogEntry) en.nextElement();
+
+      if (e.getEntryType() == DELEGATE_SYSTEM) {
+	String p = (String) e.getEntryArg(0);
+	if (p.length() <= systemId.length()
+	    && p.equals(systemId.substring(0, p.length()))) {
+	  // delegate this match to the other catalog
+
+	  delCats.addElement(e.getEntryArg(1));
+	}
+      }
+    }
+
+    if (delCats.size() > 0) {
+      Enumeration enCats = delCats.elements();
+
+      if (catalogManager.debug.getDebug() > 1) {
+	catalogManager.debug.message(2, "Switching to delegated catalog(s):");
+	while (enCats.hasMoreElements()) {
+	  String delegatedCatalog = (String) enCats.nextElement();
+	  catalogManager.debug.message(2, "\t" + delegatedCatalog);
+	}
+      }
+
+      Catalog dcat = newCatalog();
+
+      enCats = delCats.elements();
+      while (enCats.hasMoreElements()) {
+	String delegatedCatalog = (String) enCats.nextElement();
+	dcat.parseCatalog(delegatedCatalog);
+      }
+
+      return dcat.resolveSystem(systemId);
+    }
+
+    return null;
+  }
+
+  /**
+   * Return the applicable URI.
+   *
+   * <p>If a URI entry exists in the Catalog
+   * for the URI specified, return the mapped value.</p>
+   *
+   * <p>URI comparison is case sensitive.</p>
+   *
+   * @param uri The URI to locate in the catalog.
+   *
+   * @return The resolved URI.
+   *
+   * @throws MalformedURLException The system identifier of a
+   * subordinate catalog cannot be turned into a valid URL.
+   * @throws IOException Error reading subordinate catalog file.
+   */
+  public String resolveURI(String uri)
+    throws MalformedURLException, IOException {
+
+    catalogManager.debug.message(3, "resolveURI("+uri+")");
+
+    uri = normalizeURI(uri);
+
+    if (uri != null && uri.startsWith("urn:publicid:")) {
+      uri = PublicId.decodeURN(uri);
+      return resolvePublic(uri, null);
+    }
+
+    // If there's a URI entry in this catalog, use it
+    if (uri != null) {
+      String resolved = resolveLocalURI(uri);
+      if (resolved != null) {
+	return resolved;
+      }
+    }
+
+    // Otherwise, look in the subordinate catalogs
+    return resolveSubordinateCatalogs(URI,
+				      null,
+				      null,
+				      uri);
+  }
+
+  /**
+   * Return the applicable URI in this catalog.
+   *
+   * <p>If a URI entry exists in the catalog file
+   * for the URI specified, return the mapped value.</p>
+   *
+   * @param uri The URI to locate in the catalog
+   *
+   * @return The mapped URI or null
+   */
+  protected String resolveLocalURI(String uri)
+    throws MalformedURLException, IOException {
+    Enumeration en = catalogEntries.elements();
+    while (en.hasMoreElements()) {
+      CatalogEntry e = (CatalogEntry) en.nextElement();
+      if (e.getEntryType() == URI
+	  && (e.getEntryArg(0).equals(uri))) {
+	return e.getEntryArg(1);
+      }
+    }
+
+    // If there's a REWRITE_URI entry in this catalog, use it
+    en = catalogEntries.elements();
+    String startString = null;
+    String prefix = null;
+    while (en.hasMoreElements()) {
+      CatalogEntry e = (CatalogEntry) en.nextElement();
+
+      if (e.getEntryType() == REWRITE_URI) {
+	String p = (String) e.getEntryArg(0);
+	if (p.length() <= uri.length()
+	    && p.equals(uri.substring(0, p.length()))) {
+	  // Is this the longest prefix?
+	  if (startString == null
+	      || p.length() > startString.length()) {
+	    startString = p;
+	    prefix = e.getEntryArg(1);
+	  }
+	}
+      }
+    }
+
+    if (prefix != null) {
+      // return the uri with the new prefix
+      return prefix + uri.substring(startString.length());
+    }
+
+    // If there's a URI_SUFFIX entry in this catalog, use it
+    en = catalogEntries.elements();
+    String suffixString = null;
+    String suffixURI = null;
+    while (en.hasMoreElements()) {
+      CatalogEntry e = (CatalogEntry) en.nextElement();
+
+      if (e.getEntryType() == URI_SUFFIX) {
+	String p = (String) e.getEntryArg(0);
+	if (p.length() <= uri.length()
+	    && uri.endsWith(p)) {
+	  // Is this the longest prefix?
+	  if (suffixString == null
+	      || p.length() > suffixString.length()) {
+	    suffixString = p;
+	    suffixURI = e.getEntryArg(1);
+	  }
+	}
+      }
+    }
+
+    if (suffixURI != null) {
+      // return the uri for the suffix
+      return suffixURI;
+    }
+
+    // If there's a DELEGATE_URI entry in this catalog, use it
+    en = catalogEntries.elements();
+    Vector delCats = new Vector();
+    while (en.hasMoreElements()) {
+      CatalogEntry e = (CatalogEntry) en.nextElement();
+
+      if (e.getEntryType() == DELEGATE_URI) {
+	String p = (String) e.getEntryArg(0);
+	if (p.length() <= uri.length()
+	    && p.equals(uri.substring(0, p.length()))) {
+	  // delegate this match to the other catalog
+
+	  delCats.addElement(e.getEntryArg(1));
+	}
+      }
+    }
+
+    if (delCats.size() > 0) {
+      Enumeration enCats = delCats.elements();
+
+      if (catalogManager.debug.getDebug() > 1) {
+	catalogManager.debug.message(2, "Switching to delegated catalog(s):");
+	while (enCats.hasMoreElements()) {
+	  String delegatedCatalog = (String) enCats.nextElement();
+	  catalogManager.debug.message(2, "\t" + delegatedCatalog);
+	}
+      }
+
+      Catalog dcat = newCatalog();
+
+      enCats = delCats.elements();
+      while (enCats.hasMoreElements()) {
+	String delegatedCatalog = (String) enCats.nextElement();
+	dcat.parseCatalog(delegatedCatalog);
+      }
+
+      return dcat.resolveURI(uri);
+    }
+
+    return null;
+  }
+
+  /**
+   * Search the subordinate catalogs, in order, looking for a match.
+   *
+   * <p>This method searches the Catalog and returns the system
+   * identifier specified for the given entity type with the given
+   * name, public, and system identifiers. In some contexts, these
+   * may be null.</p>
+   *
+   * @param entityType The CatalogEntry type for which this query is
+   * being conducted. This is necessary in order to do the approprate
+   * query on a subordinate catalog.
+   * @param entityName The name of the entity being searched for, if
+   * appropriate.
+   * @param publicId The public identifier of the entity in question
+   * (as provided in the source document).
+   * @param systemId The nominal system identifier for the entity
+   * in question (as provided in the source document). This parameter is
+   * overloaded for the URI entry type.
+   *
+   * @throws MalformedURLException The formal system identifier of a
+   * delegated catalog cannot be turned into a valid URL.
+   * @throws IOException Error reading delegated catalog file.
+   *
+   * @return The system identifier to use.
+   * Note that the nominal system identifier is not returned if a
+   * match is not found in the catalog, instead null is returned
+   * to indicate that no match was found.
+   */
+  protected synchronized String resolveSubordinateCatalogs(int entityType,
+							   String entityName,
+							   String publicId,
+							   String systemId)
+    throws MalformedURLException, IOException {
+
+    for (int catPos = 0; catPos < catalogs.size(); catPos++) {
+      Catalog c = null;
+
+      try {
+	c = (Catalog) catalogs.elementAt(catPos);
+      } catch (ClassCastException e) {
+	String catfile = (String) catalogs.elementAt(catPos);
+	c = newCatalog();
+
+	try {
+	  c.parseCatalog(catfile);
+	} catch (MalformedURLException mue) {
+	  catalogManager.debug.message(1, "Malformed Catalog URL", catfile);
+	} catch (FileNotFoundException fnfe) {
+	  catalogManager.debug.message(1, "Failed to load catalog, file not found",
+			catfile);
+	} catch (IOException ioe) {
+	  catalogManager.debug.message(1, "Failed to load catalog, I/O error", catfile);
+	}
+
+	catalogs.setElementAt(c, catPos);
+      }
+
+      String resolved = null;
+
+      // Ok, now what are we supposed to call here?
+      if (entityType == DOCTYPE) {
+	resolved = c.resolveDoctype(entityName,
+				    publicId,
+				    systemId);
+      } else if (entityType == DOCUMENT) {
+	resolved = c.resolveDocument();
+      } else if (entityType == ENTITY) {
+	resolved = c.resolveEntity(entityName,
+				   publicId,
+				   systemId);
+      } else if (entityType == NOTATION) {
+	resolved = c.resolveNotation(entityName,
+				     publicId,
+				     systemId);
+      } else if (entityType == PUBLIC) {
+	resolved = c.resolvePublic(publicId, systemId);
+      } else if (entityType == SYSTEM) {
+	resolved = c.resolveSystem(systemId);
+      } else if (entityType == URI) {
+	resolved = c.resolveURI(systemId);
+      }
+
+      if (resolved != null) {
+	return resolved;
+      }
+    }
+
+    return null;
+  }
+
+  // -----------------------------------------------------------------
+
+  /**
+   * Replace backslashes with forward slashes. (URLs always use
+   * forward slashes.)
+   *
+   * @param sysid The input system identifier.
+   * @return The same system identifier with backslashes turned into
+   * forward slashes.
+   */
+  protected String fixSlashes (String sysid) {
+    return sysid.replace('\\', '/');
+  }
+
+  /**
+   * Construct an absolute URI from a relative one, using the current
+   * base URI.
+   *
+   * @param sysid The (possibly relative) system identifier
+   * @return The system identifier made absolute with respect to the
+   * current {@link #base}.
+   */
+  protected String makeAbsolute(String sysid) {
+    URL local = null;
+
+    sysid = fixSlashes(sysid);
+
+    try {
+      local = new URL(base, sysid);
+    } catch (MalformedURLException e) {
+      catalogManager.debug.message(1, "Malformed URL on system identifier", sysid);
+    }
+
+    if (local != null) {
+      return local.toString();
+    } else {
+      return sysid;
+    }
+  }
+
+  /**
+   * Perform character normalization on a URI reference.
+   *
+   * @param uriref The URI reference
+   * @return The normalized URI reference.
+   */
+  protected String normalizeURI(String uriref) {
+    String newRef = "";
+    byte[] bytes;
+
+    if (uriref == null) {
+      return null;
+    }
+
+    try {
+      bytes = uriref.getBytes("UTF-8");
+    } catch (UnsupportedEncodingException uee) {
+      // this can't happen
+      catalogManager.debug.message(1, "UTF-8 is an unsupported encoding!?");
+      return uriref;
+    }
+
+    for (int count = 0; count < bytes.length; count++) {
+      int ch = bytes[count] & 0xFF;
+
+      if ((ch <= 0x20)    // ctrl
+	  || (ch > 0x7F)  // high ascii
+	  || (ch == 0x22) // "
+	  || (ch == 0x3C) // <
+	  || (ch == 0x3E) // >
+	  || (ch == 0x5C) // \
+	  || (ch == 0x5E) // ^
+	  || (ch == 0x60) // `
+	  || (ch == 0x7B) // {
+	  || (ch == 0x7C) // |
+	  || (ch == 0x7D) // }
+	  || (ch == 0x7F)) {
+	newRef += encodedByte(ch);
+      } else {
+	newRef += (char) bytes[count];
+      }
+    }
+
+    return newRef;
+  }
+
+  /**
+   * Perform %-encoding on a single byte.
+   *
+   * @param b The 8-bit integer that represents th byte. (Bytes are signed
+              but encoding needs to look at the bytes unsigned.)
+   * @return The %-encoded string for the byte in question.
+   */
+  protected String encodedByte (int b) {
+    String hex = Integer.toHexString(b).toUpperCase();
+    if (hex.length() < 2) {
+      return "%0" + hex;
+    } else {
+      return "%" + hex;
+    }
+  }
+
+  // -----------------------------------------------------------------
+
+  /**
+   * Add to the current list of delegated catalogs.
+   *
+   * <p>This method always constructs the {@link #localDelegate}
+   * vector so that it is ordered by length of partial
+   * public identifier.</p>
+   *
+   * @param entry The DELEGATE catalog entry
+   */
+  protected void addDelegate(CatalogEntry entry) {
+    int pos = 0;
+    String partial = entry.getEntryArg(0);
+
+    Enumeration local = localDelegate.elements();
+    while (local.hasMoreElements()) {
+      CatalogEntry dpe = (CatalogEntry) local.nextElement();
+      String dp = dpe.getEntryArg(0);
+      if (dp.equals(partial)) {
+	// we already have this prefix
+	return;
+      }
+      if (dp.length() > partial.length()) {
+	pos++;
+      }
+      if (dp.length() < partial.length()) {
+	break;
+      }
+    }
+
+    // now insert partial into the vector at [pos]
+    if (localDelegate.size() == 0) {
+      localDelegate.addElement(entry);
+    } else {
+      localDelegate.insertElementAt(entry, pos);
+    }
+  }
+}
+
diff --git a/o.apache.xml.resolver/patch-src/org/apache/xml/resolver/CatalogManager.java b/o.apache.xml.resolver/patch-src/org/apache/xml/resolver/CatalogManager.java
new file mode 100644
index 000000000..3ec0fea12
--- /dev/null
+++ b/o.apache.xml.resolver/patch-src/org/apache/xml/resolver/CatalogManager.java
@@ -0,0 +1,838 @@
+// CatalogManager.java - Access CatalogManager.properties
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.xml.resolver;
+
+import java.io.InputStream;
+
+import java.net.URL;
+import java.net.MalformedURLException;
+
+import java.util.MissingResourceException;
+import java.util.PropertyResourceBundle;
+import java.util.ResourceBundle;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import org.apache.xml.resolver.helpers.Debug;
+import org.apache.xml.resolver.helpers.BootstrapResolver;
+import org.apache.xml.resolver.Catalog;
+
+/**
+ * CatalogManager provides an interface to the catalog properties.
+ *
+ * <p>Properties can come from two places: from system properties or
+ * from a <i>CatalogManager.properties</i> file. This class provides a transparent
+ * interface to both, with system properties preferred over property file values.</p>
+ *
+ * <p>The following table summarizes the properties:</p>
+ *
+ * <table border="1">
+ * <thead>
+ * <tr>
+ * <td>System Property</td>
+ * <td>CatalogManager.properties<br/>Property</td>
+ * <td>Description</td>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td>xml.catalog.ignoreMissing</td>
+ * <td>&#160;</td>
+ * <td>If true, a missing <i>CatalogManager.properties</i> file or missing properties
+ * within that file will not generate warning messages. See also the
+ * <i>ignoreMissingProperties</i> method.</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>xml.catalog.files</td>
+ * <td>catalogs</td>
+ * <td>The <emph>semicolon-delimited</emph> list of catalog files.</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>&#160;</td>
+ * <td>relative-catalogs</td>
+ * <td>If false, relative catalog URIs are made absolute with respect to the base URI of
+ * the <i>CatalogManager.properties</i> file. This setting only applies to catalog
+ * URIs obtained from the <i>catalogs</i> property <emph>in the</emph>
+ * <i>CatalogManager.properties</i> file</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>xml.catalog.verbosity</td>
+ * <td>verbosity</td>
+ * <td>If non-zero, the Catalog classes will print informative and debugging messages.
+ * The higher the number, the more messages.</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>xml.catalog.prefer</td>
+ * <td>prefer</td>
+ * <td>Which identifier is preferred, "public" or "system"?</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>xml.catalog.staticCatalog</td>
+ * <td>static-catalog</td>
+ * <td>Should a single catalog be constructed for all parsing, or should a different
+ * catalog be created for each parser?</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>xml.catalog.allowPI</td>
+ * <td>allow-oasis-xml-catalog-pi</td>
+ * <td>If the source document contains "oasis-xml-catalog" processing instructions,
+ * should they be used?</td>
+ * </tr>
+ *
+ * <tr>
+ * <td>xml.catalog.className</td>
+ * <td>catalog-class-name</td>
+ * <td>If you're using the convenience classes
+ * <tt>org.apache.xml.resolver.tools.*</tt>), this setting
+ * allows you to specify an alternate class name to use for the underlying
+ * catalog.</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * @see Catalog
+ *
+ * @author Norman Walsh
+ * <a href="mailto:Norman.Walsh@Sun.COM">Norman.Walsh@Sun.COM</a>
+ *
+ * @version 1.0
+ */
+
+public class CatalogManager {
+  private static String pFiles         = "xml.catalog.files";
+  private static String pVerbosity     = "xml.catalog.verbosity";
+  private static String pPrefer        = "xml.catalog.prefer";
+  private static String pStatic        = "xml.catalog.staticCatalog";
+  private static String pAllowPI       = "xml.catalog.allowPI";
+  private static String pClassname     = "xml.catalog.className";
+  private static String pIgnoreMissing = "xml.catalog.ignoreMissing";
+
+  /** A static CatalogManager instance for sharing */
+  private static CatalogManager staticManager = new CatalogManager();
+
+  /** The bootstrap resolver to use when loading XML Catalogs. */
+  private BootstrapResolver bResolver = new BootstrapResolver();
+
+  /** Flag to ignore missing property files and/or properties */
+  private boolean ignoreMissingProperties
+    = (System.getProperty(pIgnoreMissing) != null
+       || System.getProperty(pFiles) != null);
+
+  /** Holds the resources after they are loaded from the file. */
+  private ResourceBundle resources;
+
+  /** The name of the CatalogManager properties file. */
+  private String propertyFile = "CatalogManager.properties";
+
+  /** The location of the propertyFile */
+  private URL propertyFileURI = null;
+
+  /** Default catalog files list. */
+  private String defaultCatalogFiles = "./xcatalog";
+
+  /** Current catalog files list. */
+  private String catalogFiles = null;
+
+  /** Did the catalogFiles come from the properties file? */
+  private boolean fromPropertiesFile = false;
+
+  /** Default verbosity level if there is no property setting for it. */
+  private int defaultVerbosity = 1;
+
+  /** Current verbosity level. */
+  private Integer verbosity = null;
+
+  /** Default preference setting. */
+  private boolean defaultPreferPublic = true;
+
+  /** Current preference setting. */
+  private Boolean preferPublic = null;
+
+  /** Default setting of the static catalog flag. */
+  private boolean defaultUseStaticCatalog = true;
+
+  /** Current setting of the static catalog flag. */
+  private Boolean useStaticCatalog = null;
+
+  /** The static catalog used by this manager. */
+  private static Catalog staticCatalog = null;
+
+  /** Default setting of the oasisXMLCatalogPI flag. */
+  private boolean defaultOasisXMLCatalogPI = true;
+
+  /** Current setting of the oasisXMLCatalogPI flag. */
+  private Boolean oasisXMLCatalogPI = null;
+
+  /** Default setting of the relativeCatalogs flag. */
+  private boolean defaultRelativeCatalogs = true;
+
+  /** Current setting of the relativeCatalogs flag. */
+  private Boolean relativeCatalogs = null;
+
+  /** Current catalog class name. */
+  private String catalogClassName = null;
+
+  /** The manager's debug object. Used for printing debugging messages.
+   *
+   * <p>This field is public so that objects that have access to this
+   * CatalogManager can use this debug object.</p>
+   */
+  public Debug debug = null;
+
+  /** Constructor. */
+  public CatalogManager() {
+    debug = new Debug();
+    // Note that we don't setDebug() here; we do that lazily. Either the
+    // user will set it explicitly, or we'll do it automagically if they
+    // read from the propertyFile for some other reason. That way, there's
+    // no attempt to read from the file before the caller has had a chance
+    // to avoid it.
+  }
+
+  /** Constructor that specifies an explicit property file. */
+  public CatalogManager(String propertyFile) {
+    this.propertyFile = propertyFile;
+
+    debug = new Debug();
+    // Note that we don't setDebug() here; we do that lazily. Either the
+    // user will set it explicitly, or we'll do it automagically if they
+    // read from the propertyFile for some other reason. That way, there's
+    // no attempt to read from the file before the caller has had a chance
+    // to avoid it.
+  }
+
+  /** Set the bootstrap resolver.*/
+  public void setBootstrapResolver(BootstrapResolver resolver) {
+    bResolver = resolver;
+  }
+
+  /** Get the bootstrap resolver.*/
+  public BootstrapResolver getBootstrapResolver() {
+    return bResolver;
+  }
+
+  /**
+   * Load the properties from the propertyFile and build the
+   * resources from it.
+   */
+  private synchronized void readProperties() {
+      if (propertyFile == null) {
+          return;
+      }
+    try {
+      propertyFileURI = CatalogManager.class.getResource("/"+propertyFile);
+      InputStream in =
+	CatalogManager.class.getResourceAsStream("/"+propertyFile);
+      if (in==null) {
+	if (!ignoreMissingProperties) {
+	  System.err.println("Cannot find "+propertyFile);
+	  // there's no reason to give this warning more than once
+	  ignoreMissingProperties = true;
+	}
+	return;
+      }
+      resources = new PropertyResourceBundle(in);
+    } catch (MissingResourceException mre) {
+      if (!ignoreMissingProperties) {
+	System.err.println("Cannot read "+propertyFile);
+      }
+    } catch (java.io.IOException e) {
+      if (!ignoreMissingProperties) {
+	System.err.println("Failure trying to read "+propertyFile);
+      }
+    }
+
+    // This is a bit of a hack. After we've successfully read the properties,
+    // use them to set the default debug level, if the user hasn't already set
+    // the default debug level.
+    if (verbosity == null) {
+      try {
+	String verbStr = resources.getString("verbosity");
+	int verb = Integer.parseInt(verbStr.trim());
+	debug.setDebug(verb);
+	verbosity = new Integer(verb);
+      } catch (Exception e) {
+	// nop
+      }
+    }
+  }
+
+  /**
+   * Allow access to the static CatalogManager
+   */
+  public static CatalogManager getStaticManager() {
+    return staticManager;
+  }
+
+  /**
+   * How are missing properties handled?
+   *
+   * <p>If true, missing or unreadable property files will
+   * not be reported. Otherwise, a message will be sent to System.err.
+   * </p>
+   */
+  public boolean getIgnoreMissingProperties() {
+    return ignoreMissingProperties;
+  }
+
+  /**
+   * How should missing properties be handled?
+   *
+   * <p>If ignore is true, missing or unreadable property files will
+   * not be reported. Otherwise, a message will be sent to System.err.
+   * </p>
+   */
+  public void setIgnoreMissingProperties(boolean ignore) {
+    ignoreMissingProperties = ignore;
+  }
+
+  /**
+   * How are missing properties handled?
+   *
+   * <p>If ignore is true, missing or unreadable property files will
+   * not be reported. Otherwise, a message will be sent to System.err.
+   * </p>
+   *
+   * @deprecated No longer static; use get/set methods.
+   */
+  public void ignoreMissingProperties(boolean ignore) {
+    setIgnoreMissingProperties(ignore);
+  }
+
+  /**
+   * Obtain the verbosity setting from the properties.
+   *
+   * @return The verbosity level from the propertyFile or the
+   * defaultVerbosity.
+   */
+  private int queryVerbosity () {
+    String defaultVerbStr = Integer.toString(defaultVerbosity);
+
+    String verbStr = System.getProperty(pVerbosity);
+
+    if (verbStr == null) {
+      if (resources==null) readProperties();
+      if (resources != null) {
+	try {
+	  verbStr = resources.getString("verbosity");
+	} catch (MissingResourceException e) {
+	  verbStr = defaultVerbStr;
+	}
+      } else {
+	verbStr = defaultVerbStr;
+      }
+    }
+
+    int verb = defaultVerbosity;
+
+    try {
+      verb = Integer.parseInt(verbStr.trim());
+    } catch (Exception e) {
+      System.err.println("Cannot parse verbosity: \"" + verbStr + "\"");
+    }
+
+    // This is a bit of a hack. After we've successfully got the verbosity,
+    // we have to use it to set the default debug level,
+    // if the user hasn't already set the default debug level.
+    if (verbosity == null) {
+      debug.setDebug(verb);
+      verbosity = new Integer(verb);
+    }
+
+    return verb;
+  }
+
+  /**
+   * What is the current verbosity?
+   */
+  public int getVerbosity() {
+    if (verbosity == null) {
+      verbosity = new Integer(queryVerbosity());
+    }
+
+    return verbosity.intValue();
+  }
+
+  /**
+   * Set the current verbosity.
+   */
+  public void setVerbosity (int verbosity) {
+    this.verbosity = new Integer(verbosity);
+    debug.setDebug(verbosity);
+  }
+
+  /**
+   * What is the current verbosity?
+   *
+   * @deprecated No longer static; use get/set methods.
+   */
+  public int verbosity () {
+    return getVerbosity();
+  }
+
+  /**
+   * Obtain the relativeCatalogs setting from the properties.
+   *
+   * @return The relativeCatalogs setting from the propertyFile or the
+   * defaultRelativeCatalogs.
+   */
+  private boolean queryRelativeCatalogs () {
+    if (resources==null) readProperties();
+
+    if (resources==null) return defaultRelativeCatalogs;
+
+    try {
+      String allow = resources.getString("relative-catalogs");
+      return (allow.equalsIgnoreCase("true")
+	      || allow.equalsIgnoreCase("yes")
+	      || allow.equalsIgnoreCase("1"));
+    } catch (MissingResourceException e) {
+      return defaultRelativeCatalogs;
+    }
+  }
+
+  /**
+   * Get the relativeCatalogs setting.
+   *
+   * <p>This property is used when the catalogFiles property is
+   * interrogated. If true, then relative catalog entry file names
+   * are returned. If false, relative catalog entry file names are
+   * made absolute with respect to the properties file before returning
+   * them.</p>
+   *
+   * <p>This property <emph>only applies</emph> when the catalog files
+   * come from a properties file. If they come from a system property or
+   * the default list, they are never considered relative. (What would
+   * they be relative to?)</p>
+   *
+   * <p>In the properties, a value of 'yes', 'true', or '1' is considered
+   * true, anything else is false.</p>
+   *
+   * @return The relativeCatalogs setting from the propertyFile or the
+   * defaultRelativeCatalogs.
+   */
+  public boolean getRelativeCatalogs () {
+    if (relativeCatalogs == null) {
+      relativeCatalogs = new Boolean(queryRelativeCatalogs());
+    }
+
+    return relativeCatalogs.booleanValue();
+  }
+
+  /**
+   * Set the relativeCatalogs setting.
+   *
+   * @see #getRelativeCatalogs()
+   */
+  public void setRelativeCatalogs (boolean relative) {
+    relativeCatalogs = new Boolean(relative);
+  }
+
+  /**
+   * Get the relativeCatalogs setting.
+   *
+   * @deprecated No longer static; use get/set methods.
+   */
+  public boolean relativeCatalogs () {
+    return getRelativeCatalogs();
+  }
+
+  /**
+   * Obtain the list of catalog files from the properties.
+   *
+   * @return A semicolon delimited list of catlog file URIs
+   */
+  private String queryCatalogFiles () {
+    String catalogList = System.getProperty(pFiles);
+    fromPropertiesFile = false;
+
+    if (catalogList == null) {
+      if (resources == null) readProperties();
+      if (resources != null) {
+	try {
+	  catalogList = resources.getString("catalogs");
+	  fromPropertiesFile = true;
+	} catch (MissingResourceException e) {
+	  System.err.println(propertyFile + ": catalogs not found.");
+	  catalogList = null;
+	}
+      }
+    }
+
+    if (catalogList == null) {
+      catalogList = defaultCatalogFiles;
+    }
+
+    return catalogList;
+  }
+
+  /**
+   * Return the current list of catalog files.
+   *
+   * @return A vector of the catalog file names or null if no catalogs
+   * are available in the properties.
+   */
+  public Vector getCatalogFiles() {
+    if (catalogFiles == null) {
+      catalogFiles = queryCatalogFiles();
+    }
+
+    StringTokenizer files = new StringTokenizer(catalogFiles, ";");
+    Vector catalogs = new Vector();
+    while (files.hasMoreTokens()) {
+      String catalogFile = files.nextToken();
+      URL absURI = null;
+
+      if (fromPropertiesFile && !relativeCatalogs()) {
+	try {
+	  absURI = new URL(propertyFileURI, catalogFile);
+	  catalogFile = absURI.toString();
+	} catch (MalformedURLException mue) {
+	  absURI = null;
+	}
+      }
+
+      catalogs.add(catalogFile);
+    }
+
+    return catalogs;
+  }
+
+  /**
+   * Set the list of catalog files.
+   */
+  public void setCatalogFiles(String fileList) {
+    catalogFiles = fileList;
+    fromPropertiesFile = false;
+  }
+
+  /**
+   * Return the current list of catalog files.
+   *
+   * @return A vector of the catalog file names or null if no catalogs
+   * are available in the properties.
+   *
+   * @deprecated No longer static; use get/set methods.
+   */
+  public Vector catalogFiles() {
+    return getCatalogFiles();
+  }
+
+  /**
+   * Obtain the preferPublic setting from the properties.
+   *
+   * <p>In the properties, a value of 'public' is true,
+   * anything else is false.</p>
+   *
+   * @return True if prefer is public or the
+   * defaultPreferSetting.
+   */
+  private boolean queryPreferPublic () {
+    String prefer = System.getProperty(pPrefer);
+
+    if (prefer == null) {
+      if (resources==null) readProperties();
+      if (resources==null) return defaultPreferPublic;
+      try {
+	prefer = resources.getString("prefer");
+      } catch (MissingResourceException e) {
+	return defaultPreferPublic;
+      }
+    }
+
+    if (prefer == null) {
+      return defaultPreferPublic;
+    }
+
+    return (prefer.equalsIgnoreCase("public"));
+  }
+
+  /**
+   * Return the current prefer public setting.
+   *
+   * @return True if public identifiers are preferred.
+   */
+  public boolean getPreferPublic () {
+    if (preferPublic == null) {
+      preferPublic = new Boolean(queryPreferPublic());
+    }
+    return preferPublic.booleanValue();
+  }
+
+  /**
+   * Set the prefer public setting.
+   */
+  public void setPreferPublic (boolean preferPublic) {
+    this.preferPublic = new Boolean(preferPublic);
+  }
+
+  /**
+   * Return the current prefer public setting.
+   *
+   * @return True if public identifiers are preferred.
+   *
+   * @deprecated No longer static; use get/set methods.
+   */
+  public boolean preferPublic () {
+    return getPreferPublic();
+  }
+
+  /**
+   * Obtain the static-catalog setting from the properties.
+   *
+   * <p>In the properties, a value of 'yes', 'true', or '1' is considered
+   * true, anything else is false.</p>
+   *
+   * @return The static-catalog setting from the propertyFile or the
+   * defaultUseStaticCatalog.
+   */
+  private boolean queryUseStaticCatalog () {
+    String staticCatalog = System.getProperty(pStatic);
+
+    if (staticCatalog == null) {
+      if (resources==null) readProperties();
+      if (resources==null) return defaultUseStaticCatalog;
+      try {
+	staticCatalog = resources.getString("static-catalog");
+      } catch (MissingResourceException e) {
+	return defaultUseStaticCatalog;
+      }
+    }
+
+    if (staticCatalog == null) {
+      return defaultUseStaticCatalog;
+    }
+
+    return (staticCatalog.equalsIgnoreCase("true")
+	    || staticCatalog.equalsIgnoreCase("yes")
+	    || staticCatalog.equalsIgnoreCase("1"));
+  }
+
+  /**
+   * Get the current use static catalog setting.
+   */
+  public boolean getUseStaticCatalog() {
+    if (useStaticCatalog == null) {
+      useStaticCatalog = new Boolean(queryUseStaticCatalog());
+    }
+
+    return useStaticCatalog.booleanValue();
+  }
+
+  /**
+   * Set the use static catalog setting.
+   */
+  public void setUseStaticCatalog(boolean useStatic) {
+    useStaticCatalog = new Boolean(useStatic);
+  }
+
+  /**
+   * Get the current use static catalog setting.
+   *
+   * @deprecated No longer static; use get/set methods.
+   */
+  public boolean staticCatalog() {
+    return getUseStaticCatalog();
+  }
+
+  /**
+   * Get a new catalog instance.
+   *
+   * This method always returns a new instance of the underlying catalog class.
+   */
+  public Catalog getPrivateCatalog() {
+    Catalog catalog = staticCatalog;
+
+    if (useStaticCatalog == null) {
+      useStaticCatalog = new Boolean(getUseStaticCatalog());
+    }
+
+    if (catalog == null || !useStaticCatalog.booleanValue()) {
+
+      try {
+	String catalogClassName = getCatalogClassName();
+
+	if (catalogClassName == null) {
+	  catalog = new Catalog();
+	} else {
+	  try {
+	    catalog = (Catalog) Class.forName(catalogClassName).newInstance();
+	  } catch (ClassNotFoundException cnfe) {
+	    debug.message(1,"Catalog class named '"
+			  + catalogClassName
+			  + "' could not be found. Using default.");
+	    catalog = new Catalog();
+	  } catch (ClassCastException cnfe) {
+	    debug.message(1,"Class named '"
+			  + catalogClassName
+			  + "' is not a Catalog. Using default.");
+	    catalog = new Catalog();
+	  }
+	}
+
+	catalog.setCatalogManager(this);
+	catalog.setupReaders();
+	catalog.loadSystemCatalogs();
+      } catch (Exception ex) {
+	ex.printStackTrace();
+      }
+
+      if (useStaticCatalog.booleanValue()) {
+	staticCatalog = catalog;
+      }
+    }
+
+    return catalog;
+  }
+
+  /**
+   * Get a catalog instance.
+   *
+   * If this manager uses static catalogs, the same static catalog will
+   * always be returned. Otherwise a new catalog will be returned.
+   */
+  public Catalog getCatalog() {
+    Catalog catalog = staticCatalog;
+
+    if (useStaticCatalog == null) {
+      useStaticCatalog = new Boolean(getUseStaticCatalog());
+    }
+
+    if (catalog == null || !useStaticCatalog.booleanValue()) {
+      catalog = getPrivateCatalog();
+      if (useStaticCatalog.booleanValue()) {
+	staticCatalog = catalog;
+      }
+    }
+
+    return catalog;
+  }
+
+  /**
+   * <p>Obtain the oasisXMLCatalogPI setting from the properties.</p>
+   *
+   * <p>In the properties, a value of 'yes', 'true', or '1' is considered
+   * true, anything else is false.</p>
+   *
+   * @return The oasisXMLCatalogPI setting from the propertyFile or the
+   * defaultOasisXMLCatalogPI.
+   */
+  public boolean queryAllowOasisXMLCatalogPI () {
+    String allow = System.getProperty(pAllowPI);
+
+    if (allow == null) {
+      if (resources==null) readProperties();
+      if (resources==null) return defaultOasisXMLCatalogPI;
+      try {
+	allow = resources.getString("allow-oasis-xml-catalog-pi");
+      } catch (MissingResourceException e) {
+	return defaultOasisXMLCatalogPI;
+      }
+    }
+
+    if (allow == null) {
+      return defaultOasisXMLCatalogPI;
+    }
+
+    return (allow.equalsIgnoreCase("true")
+	    || allow.equalsIgnoreCase("yes")
+	    || allow.equalsIgnoreCase("1"));
+  }
+
+  /**
+   * Get the current XML Catalog PI setting.
+   */
+  public boolean getAllowOasisXMLCatalogPI () {
+    if (oasisXMLCatalogPI == null) {
+      oasisXMLCatalogPI = new Boolean(queryAllowOasisXMLCatalogPI());
+    }
+
+    return oasisXMLCatalogPI.booleanValue();
+  }
+
+  /**
+   * Set the XML Catalog PI setting
+   */
+  public void setAllowOasisXMLCatalogPI(boolean allowPI) {
+    oasisXMLCatalogPI = new Boolean(allowPI);
+  }
+
+  /**
+   * Get the current XML Catalog PI setting.
+   *
+   * @deprecated No longer static; use get/set methods.
+   */
+  public boolean allowOasisXMLCatalogPI() {
+    return getAllowOasisXMLCatalogPI();
+  }
+
+  /**
+   * Obtain the Catalog class name setting from the properties.
+   *
+   */
+  public String queryCatalogClassName () {
+    String className = System.getProperty(pClassname);
+
+    if (className == null) {
+      if (resources==null) readProperties();
+      if (resources==null) return null;
+      try {
+	return resources.getString("catalog-class-name");
+      } catch (MissingResourceException e) {
+	return null;
+      }
+    }
+
+    return className;
+  }
+
+  /**
+   * Get the current Catalog class name.
+   */
+  public String getCatalogClassName() {
+    if (catalogClassName == null) {
+      catalogClassName = queryCatalogClassName();
+    }
+
+    return catalogClassName;
+  }
+
+  /**
+   * Set the Catalog class name.
+   */
+  public void setCatalogClassName(String className) {
+    catalogClassName = className;
+  }
+
+  /**
+   * Get the current Catalog class name.
+   *
+   * @deprecated No longer static; use get/set methods.
+   */
+  public String catalogClassName() {
+    return getCatalogClassName();
+  }
+}
diff --git a/o.apache.xml.resolver/patch-src/org/apache/xml/resolver/NbCatalogManager.java b/o.apache.xml.resolver/patch-src/org/apache/xml/resolver/NbCatalogManager.java
new file mode 100644
index 000000000..b6230d536
--- /dev/null
+++ b/o.apache.xml.resolver/patch-src/org/apache/xml/resolver/NbCatalogManager.java
@@ -0,0 +1,570 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.xml.resolver;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.MissingResourceException;
+import java.util.PropertyResourceBundle;
+import java.util.ResourceBundle;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import org.apache.xml.resolver.helpers.BootstrapResolver;
+import org.apache.xml.resolver.helpers.Debug;
+
+public class NbCatalogManager
+  extends CatalogManager
+{
+  private static String pFiles = "xml.catalog.files";
+  private static String pVerbosity = "xml.catalog.verbosity";
+  private static String pPrefer = "xml.catalog.prefer";
+  private static String pStatic = "xml.catalog.staticCatalog";
+  private static String pAllowPI = "xml.catalog.allowPI";
+  private static String pClassname = "xml.catalog.className";
+  private static String pIgnoreMissing = "xml.catalog.ignoreMissing";
+  private static NbCatalogManager staticManager = new NbCatalogManager();
+  private BootstrapResolver bResolver = new BootstrapResolver();
+  private boolean ignoreMissingProperties = (System.getProperty(pIgnoreMissing) != null) || (System.getProperty(pFiles) != null);
+  private ResourceBundle resources;
+  private String propertyFile = "CatalogManager.properties";
+  private URL propertyFileURI = null;
+  private String defaultCatalogFiles = "./xcatalog";
+  private String catalogFiles = null;
+  private boolean fromPropertiesFile = false;
+  private int defaultVerbosity = 1;
+  private Integer verbosity = null;
+  private boolean defaultPreferPublic = true;
+  private Boolean preferPublic = null;
+  private boolean defaultUseStaticCatalog = true;
+  private Boolean useStaticCatalog = null;
+  private static Catalog staticCatalog = null;
+  private boolean defaultOasisXMLCatalogPI = true;
+  private Boolean oasisXMLCatalogPI = null;
+  private boolean defaultRelativeCatalogs = true;
+  private Boolean relativeCatalogs = null;
+  private String catalogClassName = null;
+  public Debug debug = null;
+  
+  public NbCatalogManager()
+  {
+    this.debug = new Debug();
+  }
+  
+  public NbCatalogManager(String propertyFile)
+  {
+    this.propertyFile = propertyFile;
+    
+    this.debug = new Debug();
+  }
+  
+  public void setBootstrapResolver(BootstrapResolver resolver)
+  {
+    this.bResolver = resolver;
+  }
+  
+  public BootstrapResolver getBootstrapResolver()
+  {
+    return this.bResolver;
+  }
+  
+  private synchronized void readProperties()
+  {
+    if (this.propertyFile == null) {
+      return;
+    }
+    try
+    {
+      this.propertyFileURI = NbCatalogManager.class.getResource("/" + this.propertyFile);
+      InputStream in = NbCatalogManager.class.getResourceAsStream("/" + this.propertyFile);
+      if (in == null)
+      {
+        if (!this.ignoreMissingProperties)
+        {
+          this.debug.message(2, "Cannot find " + this.propertyFile);
+          
+          this.ignoreMissingProperties = true;
+        }
+        return;
+      }
+      this.resources = new PropertyResourceBundle(in);
+    }
+    catch (MissingResourceException mre)
+    {
+      if (!this.ignoreMissingProperties) {
+        System.err.println("Cannot read " + this.propertyFile);
+      }
+    }
+    catch (IOException e)
+    {
+      if (!this.ignoreMissingProperties) {
+        System.err.println("Failure trying to read " + this.propertyFile);
+      }
+    }
+    if (this.verbosity == null) {
+      try
+      {
+        String verbStr = this.resources.getString("verbosity");
+        int verb = Integer.parseInt(verbStr.trim());
+        this.debug.setDebug(verb);
+        this.verbosity = new Integer(verb);
+      }
+      catch (Exception e) {}
+    }
+  }
+  
+  public static CatalogManager getStaticManager()
+  {
+    return staticManager;
+  }
+  
+  public boolean getIgnoreMissingProperties()
+  {
+    return this.ignoreMissingProperties;
+  }
+  
+  public void setIgnoreMissingProperties(boolean ignore)
+  {
+    this.ignoreMissingProperties = ignore;
+  }
+  
+  /**
+   * @deprecated
+   */
+  public void ignoreMissingProperties(boolean ignore)
+  {
+    setIgnoreMissingProperties(ignore);
+  }
+  
+  private int queryVerbosity()
+  {
+    String defaultVerbStr = Integer.toString(this.defaultVerbosity);
+    
+    String verbStr = System.getProperty(pVerbosity);
+    if (verbStr == null)
+    {
+      if (this.resources == null) {
+        readProperties();
+      }
+      if (this.resources != null) {
+        try
+        {
+          verbStr = this.resources.getString("verbosity");
+        }
+        catch (MissingResourceException e)
+        {
+          verbStr = defaultVerbStr;
+        }
+      } else {
+        verbStr = defaultVerbStr;
+      }
+    }
+    int verb = this.defaultVerbosity;
+    try
+    {
+      verb = Integer.parseInt(verbStr.trim());
+    }
+    catch (Exception e)
+    {
+      System.err.println("Cannot parse verbosity: \"" + verbStr + "\"");
+    }
+    if (this.verbosity == null)
+    {
+      this.debug.setDebug(verb);
+      this.verbosity = new Integer(verb);
+    }
+    return verb;
+  }
+  
+  public int getVerbosity()
+  {
+    if (this.verbosity == null) {
+      this.verbosity = new Integer(queryVerbosity());
+    }
+    return this.verbosity.intValue();
+  }
+  
+  public void setVerbosity(int verbosity)
+  {
+    this.verbosity = new Integer(verbosity);
+    this.debug.setDebug(verbosity);
+  }
+  
+  /**
+   * @deprecated
+   */
+  public int verbosity()
+  {
+    return getVerbosity();
+  }
+  
+  private boolean queryRelativeCatalogs()
+  {
+    if (this.resources == null) {
+      readProperties();
+    }
+    if (this.resources == null) {
+      return this.defaultRelativeCatalogs;
+    }
+    try
+    {
+      String allow = this.resources.getString("relative-catalogs");
+      return (allow.equalsIgnoreCase("true")) || (allow.equalsIgnoreCase("yes")) || (allow.equalsIgnoreCase("1"));
+    }
+    catch (MissingResourceException e) {}
+    return this.defaultRelativeCatalogs;
+  }
+  
+  public boolean getRelativeCatalogs()
+  {
+    if (this.relativeCatalogs == null) {
+      this.relativeCatalogs = new Boolean(queryRelativeCatalogs());
+    }
+    return this.relativeCatalogs.booleanValue();
+  }
+  
+  public void setRelativeCatalogs(boolean relative)
+  {
+    this.relativeCatalogs = new Boolean(relative);
+  }
+  
+  /**
+   * @deprecated
+   */
+  public boolean relativeCatalogs()
+  {
+    return getRelativeCatalogs();
+  }
+  
+  private String queryCatalogFiles()
+  {
+    String catalogList = System.getProperty(pFiles);
+    this.fromPropertiesFile = false;
+    if (catalogList == null)
+    {
+      if (this.resources == null) {
+        readProperties();
+      }
+      if (this.resources != null) {
+        try
+        {
+          catalogList = this.resources.getString("catalogs");
+          this.fromPropertiesFile = true;
+        }
+        catch (MissingResourceException e)
+        {
+          System.err.println(this.propertyFile + ": catalogs not found.");
+          catalogList = null;
+        }
+      }
+    }
+    if (catalogList == null) {
+      catalogList = this.defaultCatalogFiles;
+    }
+    return catalogList;
+  }
+  
+  public Vector getCatalogFiles()
+  {
+    if (this.catalogFiles == null) {
+      this.catalogFiles = queryCatalogFiles();
+    }
+    StringTokenizer files = new StringTokenizer(this.catalogFiles, ";");
+    Vector catalogs = new Vector();
+    while (files.hasMoreTokens())
+    {
+      String catalogFile = files.nextToken();
+      URL absURI = null;
+      if ((this.fromPropertiesFile) && (!relativeCatalogs())) {
+        try
+        {
+          absURI = new URL(this.propertyFileURI, catalogFile);
+          catalogFile = absURI.toString();
+        }
+        catch (MalformedURLException mue)
+        {
+          absURI = null;
+        }
+      }
+      catalogs.add(catalogFile);
+    }
+    return catalogs;
+  }
+  
+  public void setCatalogFiles(String fileList)
+  {
+    this.catalogFiles = fileList;
+    this.fromPropertiesFile = false;
+  }
+  
+  /**
+   * @deprecated
+   */
+  public Vector catalogFiles()
+  {
+    return getCatalogFiles();
+  }
+  
+  private boolean queryPreferPublic()
+  {
+    String prefer = System.getProperty(pPrefer);
+    if (prefer == null)
+    {
+      if (this.resources == null) {
+        readProperties();
+      }
+      if (this.resources == null) {
+        return this.defaultPreferPublic;
+      }
+      try
+      {
+        prefer = this.resources.getString("prefer");
+      }
+      catch (MissingResourceException e)
+      {
+        return this.defaultPreferPublic;
+      }
+    }
+    if (prefer == null) {
+      return this.defaultPreferPublic;
+    }
+    return prefer.equalsIgnoreCase("public");
+  }
+  
+  public boolean getPreferPublic()
+  {
+    if (this.preferPublic == null) {
+      this.preferPublic = new Boolean(queryPreferPublic());
+    }
+    return this.preferPublic.booleanValue();
+  }
+  
+  public void setPreferPublic(boolean preferPublic)
+  {
+    this.preferPublic = new Boolean(preferPublic);
+  }
+  
+  /**
+   * @deprecated
+   */
+  public boolean preferPublic()
+  {
+    return getPreferPublic();
+  }
+  
+  private boolean queryUseStaticCatalog()
+  {
+    String staticCatalog = System.getProperty(pStatic);
+    if (staticCatalog == null)
+    {
+      if (this.resources == null) {
+        readProperties();
+      }
+      if (this.resources == null) {
+        return this.defaultUseStaticCatalog;
+      }
+      try
+      {
+        staticCatalog = this.resources.getString("static-catalog");
+      }
+      catch (MissingResourceException e)
+      {
+        return this.defaultUseStaticCatalog;
+      }
+    }
+    if (staticCatalog == null) {
+      return this.defaultUseStaticCatalog;
+    }
+    return (staticCatalog.equalsIgnoreCase("true")) || (staticCatalog.equalsIgnoreCase("yes")) || (staticCatalog.equalsIgnoreCase("1"));
+  }
+  
+  public boolean getUseStaticCatalog()
+  {
+    if (this.useStaticCatalog == null) {
+      this.useStaticCatalog = new Boolean(queryUseStaticCatalog());
+    }
+    return this.useStaticCatalog.booleanValue();
+  }
+  
+  public void setUseStaticCatalog(boolean useStatic)
+  {
+    this.useStaticCatalog = new Boolean(useStatic);
+  }
+  
+  /**
+   * @deprecated
+   */
+  public boolean staticCatalog()
+  {
+    return getUseStaticCatalog();
+  }
+  
+  public Catalog getPrivateCatalog()
+  {
+    Catalog catalog = staticCatalog;
+    if (this.useStaticCatalog == null) {
+      this.useStaticCatalog = new Boolean(getUseStaticCatalog());
+    }
+    if ((catalog == null) || (!this.useStaticCatalog.booleanValue()))
+    {
+      try
+      {
+        String catalogClassName = getCatalogClassName();
+        if (catalogClassName == null) {
+          catalog = new Catalog();
+        } else {
+          try
+          {
+            catalog = (Catalog)Class.forName(catalogClassName).newInstance();
+          }
+          catch (ClassNotFoundException cnfe)
+          {
+            this.debug.message(1, "Catalog class named '" + catalogClassName + "' could not be found. Using default.");
+            
+            catalog = new Catalog();
+          }
+          catch (ClassCastException cnfe)
+          {
+            this.debug.message(1, "Class named '" + catalogClassName + "' is not a Catalog. Using default.");
+            
+            catalog = new Catalog();
+          }
+        }
+        catalog.setCatalogManager(this);
+        catalog.setupReaders();
+        catalog.loadSystemCatalogs();
+      }
+      catch (Exception ex)
+      {
+        ex.printStackTrace();
+      }
+      if (this.useStaticCatalog.booleanValue()) {
+        staticCatalog = catalog;
+      }
+    }
+    return catalog;
+  }
+  
+  public Catalog getCatalog()
+  {
+    Catalog catalog = staticCatalog;
+    if (this.useStaticCatalog == null) {
+      this.useStaticCatalog = new Boolean(getUseStaticCatalog());
+    }
+    if ((catalog == null) || (!this.useStaticCatalog.booleanValue()))
+    {
+      catalog = getPrivateCatalog();
+      if (this.useStaticCatalog.booleanValue()) {
+        staticCatalog = catalog;
+      }
+    }
+    return catalog;
+  }
+  
+  public boolean queryAllowOasisXMLCatalogPI()
+  {
+    String allow = System.getProperty(pAllowPI);
+    if (allow == null)
+    {
+      if (this.resources == null) {
+        readProperties();
+      }
+      if (this.resources == null) {
+        return this.defaultOasisXMLCatalogPI;
+      }
+      try
+      {
+        allow = this.resources.getString("allow-oasis-xml-catalog-pi");
+      }
+      catch (MissingResourceException e)
+      {
+        return this.defaultOasisXMLCatalogPI;
+      }
+    }
+    if (allow == null) {
+      return this.defaultOasisXMLCatalogPI;
+    }
+    return (allow.equalsIgnoreCase("true")) || (allow.equalsIgnoreCase("yes")) || (allow.equalsIgnoreCase("1"));
+  }
+  
+  public boolean getAllowOasisXMLCatalogPI()
+  {
+    if (this.oasisXMLCatalogPI == null) {
+      this.oasisXMLCatalogPI = new Boolean(queryAllowOasisXMLCatalogPI());
+    }
+    return this.oasisXMLCatalogPI.booleanValue();
+  }
+  
+  public void setAllowOasisXMLCatalogPI(boolean allowPI)
+  {
+    this.oasisXMLCatalogPI = new Boolean(allowPI);
+  }
+  
+  /**
+   * @deprecated
+   */
+  public boolean allowOasisXMLCatalogPI()
+  {
+    return getAllowOasisXMLCatalogPI();
+  }
+  
+  public String queryCatalogClassName()
+  {
+    String className = System.getProperty(pClassname);
+    if (className == null)
+    {
+      if (this.resources == null) {
+        readProperties();
+      }
+      if (this.resources == null) {
+        return null;
+      }
+      try
+      {
+        return this.resources.getString("catalog-class-name");
+      }
+      catch (MissingResourceException e)
+      {
+        return null;
+      }
+    }
+    return className;
+  }
+  
+  public String getCatalogClassName()
+  {
+    if (this.catalogClassName == null) {
+      this.catalogClassName = queryCatalogClassName();
+    }
+    return this.catalogClassName;
+  }
+  
+  public void setCatalogClassName(String className)
+  {
+    this.catalogClassName = className;
+  }
+  
+  /**
+   * @deprecated
+   */
+  public String catalogClassName()
+  {
+    return getCatalogClassName();
+  }
+}
diff --git a/o.apache.xml.resolver/patch-src/org/apache/xml/resolver/tools/NbCatalogResolver.java b/o.apache.xml.resolver/patch-src/org/apache/xml/resolver/tools/NbCatalogResolver.java
new file mode 100644
index 000000000..8a2375e56
--- /dev/null
+++ b/o.apache.xml.resolver/patch-src/org/apache/xml/resolver/tools/NbCatalogResolver.java
@@ -0,0 +1,233 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.xml.resolver.tools;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.sax.SAXSource;
+import org.apache.xml.resolver.Catalog;
+import org.apache.xml.resolver.CatalogManager;
+import org.apache.xml.resolver.NbCatalogManager;
+import org.apache.xml.resolver.helpers.Debug;
+import org.apache.xml.resolver.helpers.FileURL;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+
+public class NbCatalogResolver
+  implements EntityResolver, URIResolver
+{
+  public boolean namespaceAware = true;
+  public boolean validating = false;
+  private Catalog catalog = null;
+  private CatalogManager catalogManager = NbCatalogManager.getStaticManager();
+  
+  public NbCatalogResolver()
+  {
+    initializeCatalogs(false);
+  }
+  
+  public NbCatalogResolver(boolean privateCatalog)
+  {
+    initializeCatalogs(privateCatalog);
+  }
+  
+  public NbCatalogResolver(NbCatalogManager manager)
+  {
+    this.catalogManager = manager;
+    initializeCatalogs(!this.catalogManager.getUseStaticCatalog());
+  }
+  
+  private void initializeCatalogs(boolean privateCatalog)
+  {
+    this.catalog = this.catalogManager.getCatalog();
+  }
+  
+  public Catalog getCatalog()
+  {
+    return this.catalog;
+  }
+  
+  public String getResolvedEntity(String publicId, String systemId)
+  {
+    String resolved = null;
+    if (this.catalog == null)
+    {
+      this.catalogManager.debug.message(1, "Catalog resolution attempted with null catalog; ignored");
+      return null;
+    }
+    if (systemId != null) {
+      try
+      {
+        resolved = this.catalog.resolveSystem(systemId);
+      }
+      catch (MalformedURLException me)
+      {
+        this.catalogManager.debug.message(1, "Malformed URL exception trying to resolve", publicId);
+        
+        resolved = null;
+      }
+      catch (IOException ie)
+      {
+        this.catalogManager.debug.message(1, "I/O exception trying to resolve", publicId);
+        resolved = null;
+      }
+    }
+    if (resolved == null)
+    {
+      if (publicId != null) {
+        try
+        {
+          resolved = this.catalog.resolvePublic(publicId, systemId);
+        }
+        catch (MalformedURLException me)
+        {
+          this.catalogManager.debug.message(1, "Malformed URL exception trying to resolve", publicId);
+        }
+        catch (IOException ie)
+        {
+          this.catalogManager.debug.message(1, "I/O exception trying to resolve", publicId);
+        }
+      }
+      if (resolved != null) {
+        this.catalogManager.debug.message(2, "Resolved public", publicId, resolved);
+      }
+    }
+    else
+    {
+      this.catalogManager.debug.message(2, "Resolved system", systemId, resolved);
+    }
+    return resolved;
+  }
+  
+  public InputSource resolveEntity(String publicId, String systemId)
+  {
+    String resolved = getResolvedEntity(publicId, systemId);
+    if (resolved != null)
+    {
+      InputSource iSource = new InputSource(resolved);
+      iSource.setPublicId(publicId);
+      return iSource;
+    }
+    return null;
+  }
+  
+  public Source resolve(String href, String base)
+    throws TransformerException
+  {
+    String uri = href;
+    String fragment = null;
+    int hashPos = href.indexOf("#");
+    if (hashPos >= 0)
+    {
+      uri = href.substring(0, hashPos);
+      fragment = href.substring(hashPos + 1);
+    }
+    String result = null;
+    try
+    {
+      result = this.catalog.resolveURI(href);
+    }
+    catch (Exception e) {}
+    if (result == null) {
+      try
+      {
+        URL url = null;
+        if (base == null)
+        {
+          url = new URL(uri);
+          result = url.toString();
+        }
+        else
+        {
+          URL baseURL = new URL(base);
+          url = href.length() == 0 ? baseURL : new URL(baseURL, uri);
+          result = url.toString();
+        }
+      }
+      catch (MalformedURLException mue)
+      {
+        String absBase = makeAbsolute(base);
+        if (!absBase.equals(base)) {
+          return resolve(href, absBase);
+        }
+        throw new TransformerException("Malformed URL " + href + "(base " + base + ")", mue);
+      }
+    }
+    this.catalogManager.debug.message(2, "Resolved URI", href, result);
+    
+    SAXSource source = new SAXSource();
+    source.setInputSource(new InputSource(result));
+    setEntityResolver(source);
+    return source;
+  }
+  
+  private void setEntityResolver(SAXSource source)
+    throws TransformerException
+  {
+    XMLReader reader = source.getXMLReader();
+    if (reader == null)
+    {
+      SAXParserFactory spFactory = SAXParserFactory.newInstance();
+      spFactory.setNamespaceAware(true);
+      try
+      {
+        reader = spFactory.newSAXParser().getXMLReader();
+      }
+      catch (ParserConfigurationException ex)
+      {
+        throw new TransformerException(ex);
+      }
+      catch (SAXException ex)
+      {
+        throw new TransformerException(ex);
+      }
+    }
+    reader.setEntityResolver(this);
+    source.setXMLReader(reader);
+  }
+  
+  private String makeAbsolute(String uri)
+  {
+    if (uri == null) {
+      uri = "";
+    }
+    try
+    {
+      URL url = new URL(uri);
+      return url.toString();
+    }
+    catch (MalformedURLException mue)
+    {
+      try
+      {
+        URL fileURL = FileURL.makeURL(uri);
+        return fileURL.toString();
+      }
+      catch (MalformedURLException mue2) {}
+    }
+    return uri;
+  }
+}


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

Mime
View raw message