diff --git a/java/src/manifest.resolver b/java/src/manifest.resolver
new file mode 100644
index 0000000..e6c6b14
--- /dev/null
+++ b/java/src/manifest.resolver
@@ -0,0 +1,11 @@
+Manifest-Version: 1.0
+Created-By: 1.2.2 (Sun Microsystems Inc.)
+Main-Class: org.apache.xml.resolver.Catalog
+
+Name: org/apache/xml/resolver
+Comment: xml-commons Resolver code
+Implementation-Title: org.apache.xml.resolver.Catalog
+Implementation-Version: 1.0
+Implementation-Vendor: Apache Software Foundation
+Implementation-URL: http://xml.apache.org/commons/
+
diff --git a/java/src/org/apache/xml/resolver/Catalog.java b/java/src/org/apache/xml/resolver/Catalog.java
new file mode 100644
index 0000000..875643c
--- /dev/null
+++ b/java/src/org/apache/xml/resolver/Catalog.java
@@ -0,0 +1,2071 @@
+// Catalog.java - Represents OASIS Open Catalog files.
+
+/*
+ * The Apache Software License, Version 1.1
+ *
+ *
+ * Copyright (c) 2001 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ * if any, must include the following acknowledgment:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowledgment may appear in the software itself,
+ * if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Xalan" and "Apache Software Foundation" must
+ * not be used to endorse or promote products derived from this
+ * software without prior written permission. For written
+ * permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache",
+ * nor may "Apache" appear in their name, without prior written
+ * permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation and was
+ * originally based on software copyright (c) 2001, International
+ * Business Machines Corporation., http://www.ibm.com. For more
+ * information on the Apache Software Foundation, please see
+ *
Represents OASIS Open Catalog files.
+ * + *This class implements the semantics of OASIS Open Catalog files + * (defined by + * OASIS Technical + * Resolution 9401:1997 (Amendment 2 to TR 9401)).
+ * + *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.)
+ * + *The system identifiers returned by the resolution methods in this + * class are valid, i.e. usable by, and in fact constructed by, the + * java.net.URL 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. + * + *
This class recognizes all of the Catalog entries defined in + * TR9401:1997:
+ * + *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/". + *
+ * + *Note that subordinate catalogs (all catalogs except the first, + * including CATALOG and DELEGATE* catalogs) are only loaded if and when + * they are required.
+ * + *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.
+ * + *Additional catalogs may also be loaded with the + * {@link #parseCatalog} method.
+ * + * + * + *Change Log:
+ *Rewrite to use CatalogReaders.
Allow quoted components in xml.catalog.files + * so that URLs containing colons can be used on Unix. + * The string passed to xml.catalog.files can now have the form:
+ *+ * unquoted-path-with-no-sep-chars:"double-quoted path with or without sep chars":'single-quoted path with or without sep chars' + *+ *
(Where ":" is the separater character in this example.)
+ *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.
+ *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.
+ *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.
+ *Derived from public domain code originally published by Arbortext, + * Inc.
+ */ +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 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; + + /** + *A vector of catalog files to be loaded.
+ * + *This list is initially established by
+ * loadSystemCatalogs when
+ * it parses the system catalog list, but CATALOG entries may
+ * contribute to it during the course of parsing.
A vector of catalog files constructed during processing of + * CATALOG entries in the current catalog.
+ * + *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 at the end 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.
+ * + *When processing reaches the end of each catalog files, any
+ * elements on this vector are added to the front of the
+ * catalogFiles vector.
A vector of Catalogs.
+ * + *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).
+ * + *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.
+ * + *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.
+ */ + protected Vector catalogs = new Vector(); + + /** + *A vector of DELEGATE* Catalog entries constructed during + * processing of the Catalog.
+ * + *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.
+ * + *When processing reaches the end of each catalog file, any
+ * elements on this vector are added to the end of the
+ * catalogEntries vector. This assures that matching
+ * PUBLIC keywords are encountered before DELEGATE* entries.
A hash of CatalogReaders.
+ * + *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.
+ */ + protected Hashtable readerMap = new Hashtable(); + + /** + *A vector of CatalogReaders.
+ * + *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.
+ */ + protected Vector readerArr = new Vector(); + + /** + *Setup readers.
+ */ + public void setupReaders() { + SAXParserFactory spf = SAXParserFactory.newInstance(); + spf.setNamespaceAware(true); + spf.setValidating(false); + + SAXCatalogReader saxReader = new SAXCatalogReader(spf); + + saxReader.setCatalogParser(null, "XMLCatalog", + "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.
+ * + *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.
+ * + *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.
+ * + *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.
+ * + * @param mimeType The MIME type associated with this reader. + * @param reader The CatalogReader to use. + */ + public void addReader(String mimeType, CatalogReader reader) { + if (readerMap.contains(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
+ * + *This method is used internally when constructing a new catalog. + * It copies the current reader associations over to the new catalog. + *
+ * + * @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 enum = readerMap.keys(); + while (enum.hasMoreElements()) { + String mimeType = (String) enum.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.
+ * + *This method constructs a new instance of the running Catalog + * class (which might be a subtype of org.apache.xml.resolver.Catalog). + *
+ * + *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.
+ */ + protected Catalog newCatalog() { + String catalogClass = this.getClass().getName(); + + try { + Catalog c = (Catalog) (Class.forName(catalogClass).newInstance()); + copyReaders(c); + return c; + } catch (ClassNotFoundException cnfe) { + Debug.message(1, "Class Not Found Exception: " + catalogClass); + } catch (IllegalAccessException iae) { + Debug.message(1, "Illegal Access Exception: " + catalogClass); + } catch (InstantiationException ie) { + Debug.message(1, "Instantiation Exception: " + catalogClass); + } catch (ClassCastException cce) { + Debug.message(1, "Class Cast Exception: " + catalogClass); + } catch (Exception e) { + Debug.message(1, "Other Exception: " + catalogClass); + } + + Catalog c = new Catalog(); + copyReaders(c); + return c; + } + + /** + *Constructs an empty Catalog.
+ * + *The constructor interrogates the relevant system properties + * and initializes the catalog data structures.
+ */ + public Catalog() { + default_override = CatalogManager.preferPublic(); + } + + /** + *Returns the current base URI.
+ */ + public String getCurrentBase() { + return base.toString(); + } + + /** + *Returns the default override setting associated with this + * catalog.
+ * + *All catalog files loaded by this catalog will have the + * initial override setting specified by this default.
+ */ + public String getDefaultOverride() { + if (default_override) { + return "yes"; + } else { + return "no"; + } + } + + /** + *Load the system catalog files.
+ * + *The method adds all of the + * catalogs specified in the xml.catalog.files property + * to the Catalog list.
+ * + * @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.catalogFiles(); + 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 { + + 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
+ * + *Catalogs retrieved over the net may have an associated MIME type. + * The MIME type can be used to select an appropriate reader.
+ * + * @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 { + + 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; + Debug.message(2, msg); + throw new CatalogException(CatalogException.UNPARSEABLE, msg); + } + + reader.readCatalog(this, is); + + // Now process all the pending catalogs... + parsePendingCatalogs(); + } + + /** + *Parse all of the pending catalogs.
+ * + *Catalogs may refer to other catalogs, this method parses + * all of the currently pending catalog files.
+ */ + 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 + String userdir = fixSlashes(System.getProperty("user.dir")); + catalogCwd = new URL("file:///" + userdir + "/basename"); + } catch (MalformedURLException e) { + String userdir = fixSlashes(System.getProperty("user.dir")); + 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) { + Debug.message(1, "Malformed URL on catalog filename", + fixSlashes(fileName)); + base = null; + } + } + + Debug.message(2, "Loading catalog", fileName); + 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) { + Debug.message(3, "Catalog does not exist", fileName); + } else { + Debug.message(1, "Failed to parse catalog", fileName); + } + } + } + + /** + *Cleanup and process a Catalog entry.
+ * + *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.
+ * + * @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; + + Debug.message(5, "BASE CUR", base.toString()); + 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) { + Debug.message(1, "Malformed URL on base", value); + newbase = null; + } + } + + if (newbase != null) { + base = newbase; + } + + Debug.message(5, "BASE NEW", base.toString()); + } else if (type == CATALOG) { + String fsi = makeAbsolute(entry.getEntryArg(0)); + + 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); + + 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); + + 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); + + Debug.message(4, "URI", uri, altURI); + + catalogEntries.addElement(entry); + } else if (type == DOCUMENT) { + String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(0))); + entry.setEntryArg(0, fsi); + + Debug.message(4, "DOCUMENT", fsi); + + catalogEntries.addElement(entry); + } else if (type == OVERRIDE) { + 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); + + 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); + + 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); + + 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); + + 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); + + 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); + + Debug.message(4, "REWRITE_URI", pui, upx); + + catalogEntries.addElement(entry); + } else if (type == DOCTYPE) { + String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1))); + entry.setEntryArg(1, fsi); + + 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); + + Debug.message(4, "DTDDECL", fpi, fsi); + + catalogEntries.addElement(entry); + } else if (type == ENTITY) { + String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1))); + entry.setEntryArg(1, fsi); + + 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); + + 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); + + Debug.message(4, "NOTATION", entry.getEntryArg(0), fsi); + + catalogEntries.addElement(entry); + } else { + catalogEntries.addElement(entry); + } + } + + /** + *Handle unknown CatalogEntry types.
+ * + *This method exists to allow subclasses to deal with unknown + * entry types.
+ */ + public void unknownEntry(Vector strings) { + if (strings != null && strings.size() > 0) { + String keyword = (String) strings.elementAt(0); + Debug.message(2, "Unrecognized token parsing catalog", keyword); + } + } + + /** + *Parse all subordinate catalogs.
+ * + *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:
+ * + *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).
+ *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.
+ *If you add new catalogs with parseCatalog, they
+ * won't be loaded until they are needed or until you call
+ * parseAllCatalogs again.
On the other hand, if you don't call this method, you may + * successfully parse documents without having to load all possible + * catalogs.
+ * + * @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 enum = catalogEntries.elements(); + while (enum.hasMoreElements()) { + CatalogEntry e = (CatalogEntry) enum.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; + + 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)) { + 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 enum = catalogEntries.elements(); + while (enum.hasMoreElements()) { + CatalogEntry e = (CatalogEntry) enum.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 + + Debug.message(3, "resolveDocument"); + + Enumeration enum = catalogEntries.elements(); + while (enum.hasMoreElements()) { + CatalogEntry e = (CatalogEntry) enum.nextElement(); + if (e.getEntryType() == DOCUMENT) { + return e.getEntryArg(1); //FIXME check this + } + } + + 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; + + 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)) { + 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 enum = catalogEntries.elements(); + while (enum.hasMoreElements()) { + CatalogEntry e = (CatalogEntry) enum.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; + + 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)) { + 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 enum = catalogEntries.elements(); + while (enum.hasMoreElements()) { + CatalogEntry e = (CatalogEntry) enum.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.
+ * + *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.
+ * + * @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 { + + 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)) { + 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
+ * + *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.
+ * + *There are four possible cases:
+ * + *Return the applicable SYSTEM system identifier
+ * + *If a SYSTEM entry exists in the Catalog + * for the system ID specified, return the mapped value.
+ * + *On Windows-based operating systems, the comparison between + * the system identifier provided and the SYSTEM entries in the + * Catalog is case-insensitive.
+ * + * @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 { + + 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.
+ * + *If a SYSTEM entry exists in the catalog file + * for the system ID specified, return the mapped value.
+ * + * @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 enum = catalogEntries.elements(); + while (enum.hasMoreElements()) { + CatalogEntry e = (CatalogEntry) enum.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 + enum = catalogEntries.elements(); + String startString = null; + String prefix = null; + while (enum.hasMoreElements()) { + CatalogEntry e = (CatalogEntry) enum.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 DELEGATE_SYSTEM entry in this catalog, use it + enum = catalogEntries.elements(); + Vector delCats = new Vector(); + while (enum.hasMoreElements()) { + CatalogEntry e = (CatalogEntry) enum.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 enumCats = delCats.elements(); + + if (Debug.getDebug() > 1) { + Debug.message(2, "Switching to delegated catalog(s):"); + while (enumCats.hasMoreElements()) { + String delegatedCatalog = (String) enumCats.nextElement(); + Debug.message(2, "\t" + delegatedCatalog); + } + } + + Catalog dcat = newCatalog(); + + enumCats = delCats.elements(); + while (enumCats.hasMoreElements()) { + String delegatedCatalog = (String) enumCats.nextElement(); + dcat.parseCatalog(delegatedCatalog); + } + + return dcat.resolveSystem(systemId); + } + + return null; + } + + /** + *Return the applicable URI
+ * + *If a URI entry exists in the Catalog + * for the URI specified, return the mapped value.
+ * + *URI comparison is case sensitive.
+ * + * @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 { + + 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.
+ * + *If a URI entry exists in the catalog file + * for the URI specified, return the mapped value.
+ * + * @param uri The URI to locate in the catalog + * + * @return The mapped URI or null + */ + protected String resolveLocalURI(String uri) + throws MalformedURLException, IOException { + Enumeration enum = catalogEntries.elements(); + while (enum.hasMoreElements()) { + CatalogEntry e = (CatalogEntry) enum.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 + enum = catalogEntries.elements(); + String startString = null; + String prefix = null; + while (enum.hasMoreElements()) { + CatalogEntry e = (CatalogEntry) enum.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 systemId with the new prefix + return prefix + uri.substring(startString.length()); + } + } + + // If there's a DELEGATE_URI entry in this catalog, use it + enum = catalogEntries.elements(); + Vector delCats = new Vector(); + while (enum.hasMoreElements()) { + CatalogEntry e = (CatalogEntry) enum.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 enumCats = delCats.elements(); + + if (Debug.getDebug() > 1) { + Debug.message(2, "Switching to delegated catalog(s):"); + while (enumCats.hasMoreElements()) { + String delegatedCatalog = (String) enumCats.nextElement(); + Debug.message(2, "\t" + delegatedCatalog); + } + } + + Catalog dcat = newCatalog(); + + enumCats = delCats.elements(); + while (enumCats.hasMoreElements()) { + String delegatedCatalog = (String) enumCats.nextElement(); + dcat.parseCatalog(delegatedCatalog); + } + + return dcat.resolveURI(uri); + } + + return null; + } + + /** + *Search the subordinate catalogs, in order, looking for a + * match.
+ * + *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.
+ * + * @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) { + Debug.message(1, "Malformed Catalog URL", catfile); + } catch (FileNotFoundException fnfe) { + Debug.message(1, "Failed to load catalog, file not found", + catfile); + } catch (IOException ioe) { + 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) { + 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 + 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.
+ * + *This method always constructs the {@link #localDelegate} + * vector so that it is ordered by length of partial + * public identifier.
+ * + * @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/java/src/org/apache/xml/resolver/CatalogEntry.java b/java/src/org/apache/xml/resolver/CatalogEntry.java new file mode 100644 index 0000000..a7340ab --- /dev/null +++ b/java/src/org/apache/xml/resolver/CatalogEntry.java @@ -0,0 +1,289 @@ +// CatalogEntry.java - Represents Catalog entries + +/* + * The Apache Software License, Version 1.1 + * + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Xalan" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 2001, International + * Business Machines Corporation., http://www.ibm.com. For more + * information on the Apache Software Foundation, please see + *Represents a Catalog entry.
+ * + *Instances of this class represent individual entries + * in a Catalog.
+ * + *Each catalog entry has a unique name and is associated with + * an arbitrary number of arguments (all strings). For example, the + * TR9401 catalog entry "PUBLIC" has two arguments, a public identifier + * and a system identifier. Each entry has a unique numeric type, + * assigned automatically when the entry type is created.
+ * + *The number and type of catalog entries is maintained + * statically. Catalog classes, or their subclasses, can add + * new entry types, but all Catalog objects share the same global pool + * of types.
+ * + *Initially there are no valid entries.
+ * + * @see Catalog + * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * + * @version 1.0 + */ +public class CatalogEntry { + /** The nextEntry is the ordinal number of the next entry type. */ + protected static int nextEntry = 0; + + /** + * The entryTypes vector maps catalog entry names + * (e.g., 'BASE' or 'SYSTEM') to their type (1, 2, etc.). + * Names are case sensitive. + */ + protected static Hashtable entryTypes = new Hashtable(); + + /** The entryTypes vector maps catalog entry types to the + number of arguments they're required to have. */ + protected static Vector entryArgs = new Vector(); + + /** + *Adds a new catalog entry type.
+ * + * @param name The name of the catalog entry type. This must be + * unique among all types and is case-sensitive. (Adding a duplicate + * name effectively replaces the old type with the new type.) + * @param numArgs The number of arguments that this entry type + * is required to have. There is no provision for variable numbers + * of arguments. + * @return The type for the new entry. + */ + public static int addEntryType(String name, int numArgs) { + entryTypes.put(name, new Integer(nextEntry)); + entryArgs.add(nextEntry, new Integer(numArgs)); + nextEntry++; + + return nextEntry-1; + } + + /** + *Lookup an entry type
+ * + * @param name The name of the catalog entry type. + * @return The type of the catalog entry with the specified name. + * @throws InvalidCatalogEntryTypeException if no entry has the + * specified name. + */ + public static int getEntryType(String name) + throws CatalogException { + if (!entryTypes.containsKey(name)) { + throw new CatalogException(CatalogException.INVALID_ENTRY_TYPE); + } + + Integer iType = (Integer) entryTypes.get(name); + + if (iType == null) { + throw new CatalogException(CatalogException.INVALID_ENTRY_TYPE); + } + + return iType.intValue(); + } + + /** + *Find out how many arguments an entry is required to have.
+ * + * @param name The name of the catalog entry type. + * @return The number of arguments that entry type is required to have. + * @throws InvalidCatalogEntryTypeException if no entry has the + * specified name. + */ + public static int getEntryArgCount(String name) + throws CatalogException { + return getEntryArgCount(getEntryType(name)); + } + + /** + *Find out how many arguments an entry is required to have.
+ * + * @param type A valid catalog entry type. + * @return The number of arguments that entry type is required to have. + * @throws InvalidCatalogEntryTypeException if the type is invalid. + */ + public static int getEntryArgCount(int type) + throws CatalogException { + try { + Integer iArgs = (Integer) entryArgs.get(type); + return iArgs.intValue(); + } catch (ArrayIndexOutOfBoundsException e) { + throw new CatalogException(CatalogException.INVALID_ENTRY_TYPE); + } + } + + /** The entry type of this entry */ + protected int entryType = 0; + + /** The arguments associated with this entry */ + protected Vector args = null; + + /** + *Null constructor; something for subclasses to call.
+ */ + public CatalogEntry() {} + + /** + *Construct a catalog entry of the specified type.
+ * + * @param name The name of the entry type + * @param args A String Vector of arguments + * @throws InvalidCatalogEntryTypeException if no such entry type + * exists. + * @throws InvalidCatalogEntryException if the wrong number of arguments + * is passed. + */ + public CatalogEntry(String name, Vector args) + throws CatalogException { + Integer iType = (Integer) entryTypes.get(name); + + if (iType == null) { + throw new CatalogException(CatalogException.INVALID_ENTRY_TYPE); + } + + int type = iType.intValue(); + + try { + Integer iArgs = (Integer) entryArgs.get(type); + if (iArgs.intValue() != args.size()) { + throw new CatalogException(CatalogException.INVALID_ENTRY); + } + } catch (ArrayIndexOutOfBoundsException e) { + throw new CatalogException(CatalogException.INVALID_ENTRY_TYPE); + } + + entryType = type; + this.args = args; + } + + /** + *Construct a catalog entry of the specified type.
+ * + * @param name The name of the entry type + * @param args A String Vector of arguments + * @throws InvalidCatalogEntryTypeException if no such entry type + * exists. + * @throws InvalidCatalogEntryException if the wrong number of arguments + * is passed. + */ + public CatalogEntry(int type, Vector args) + throws CatalogException { + try { + Integer iArgs = (Integer) entryArgs.get(type); + if (iArgs.intValue() != args.size()) { + throw new CatalogException(CatalogException.INVALID_ENTRY); + } + } catch (ArrayIndexOutOfBoundsException e) { + throw new CatalogException(CatalogException.INVALID_ENTRY_TYPE); + } + + entryType = type; + this.args = args; + } + + /** + *Get the entry type.
+ * + * @return The entry type of the CatalogEntry + */ + public int getEntryType() { + return entryType; + } + + /** + *Get an entry argument.
+ * + * @param argNum The argument number (arguments are numbered from 0). + * @return The specified argument or null if an invalid argNum is + * provided. + */ + public String getEntryArg(int argNum) { + try { + String arg = (String) args.get(argNum); + return arg; + } catch (ArrayIndexOutOfBoundsException e) { + return null; + } + } + + /** + *Set an entry argument.
+ * + *Catalogs sometimes need to adjust the catlog entry parameters, + * for example to make a relative URI absolute with respect to the + * current base URI. But in general, this function should only be + * called shortly after object creation to do some sort of cleanup. + * Catalog entries should not mutate over time.
+ * + * @param argNum The argument number (arguments are numbered from 0). + * @throws ArrayIndexOutOfBoundsException if an invalid argument + * number is provided. + */ + public void setEntryArg(int argNum, String newspec) + throws ArrayIndexOutOfBoundsException { + args.set(argNum, newspec); + } +} diff --git a/java/src/org/apache/xml/resolver/CatalogException.java b/java/src/org/apache/xml/resolver/CatalogException.java new file mode 100644 index 0000000..27170e6 --- /dev/null +++ b/java/src/org/apache/xml/resolver/CatalogException.java @@ -0,0 +1,203 @@ +// CatalogException.java - Catalog exception + +/* + * The Apache Software License, Version 1.1 + * + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Xalan" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 2001, International + * Business Machines Corporation., http://www.ibm.com. For more + * information on the Apache Software Foundation, please see + *Signal Catalog exception.
+ * + *This exception is thrown if an error occurs loading a + * catalog file.
+ * + * @see Catalog + * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * + * @version 1.0 + */ +public class CatalogException extends Exception { + /** A wrapper around another exception */ + public static final int WRAPPER = 1; + /** An invalid entry */ + public static final int INVALID_ENTRY = 2; + /** An invalid entry type */ + public static final int INVALID_ENTRY_TYPE = 3; + /** Could not instantiate an XML parser */ + public static final int NO_XML_PARSER = 4; + /** Unknown XML format */ + public static final int UNKNOWN_FORMAT = 5; + /** Unparseable XML catalog (not XML)*/ + public static final int UNPARSEABLE = 6; + /** XML but parse failed */ + public static final int PARSE_FAILED = 7; + + /** + * The embedded exception if tunnelling, or null. + */ + private Exception exception = null; + private int exceptionType = 0; + + /** + * Create a new CatalogException. + * + * @param type The exception type + * @param message The error or warning message. + */ + public CatalogException (int type, String message) { + super(message); + this.exceptionType = type; + this.exception = null; + } + + /** + * Create a new CatalogException. + * + * @param type The exception type + */ + public CatalogException (int type) { + super("Catalog Exception " + type); + this.exceptionType = type; + this.exception = null; + } + + /** + * Create a new CatalogException wrapping an existing exception. + * + *The existing exception will be embedded in the new + * one, and its message will become the default message for + * the CatalogException.
+ * + * @param e The exception to be wrapped in a CatalogException. + */ + public CatalogException (Exception e) { + super(); + this.exceptionType = WRAPPER; + this.exception = e; + } + + /** + * Create a new CatalogException from an existing exception. + * + *The existing exception will be embedded in the new + * one, but the new exception will have its own message.
+ * + * @param message The detail message. + * @param e The exception to be wrapped in a CatalogException. + */ + public CatalogException (String message, Exception e) { + super(message); + this.exceptionType = WRAPPER; + this.exception = e; + } + + /** + * Return a detail message for this exception. + * + *If there is an embedded exception, and if the CatalogException + * has no detail message of its own, this method will return + * the detail message from the embedded exception.
+ * + * @return The error or warning message. + */ + public String getMessage () + { + String message = super.getMessage(); + + if (message == null && exception != null) { + return exception.getMessage(); + } else { + return message; + } + } + + /** + * Return the embedded exception, if any. + * + * @return The embedded exception, or null if there is none. + */ + public Exception getException () + { + return exception; + } + + /** + * Return the exception type + * + * @return The exception type + */ + public int getExceptionType () + { + return exceptionType; + } + + /** + * Override toString to pick up any embedded exception. + * + * @return A string representation of this exception. + */ + public String toString () + { + if (exception != null) { + return exception.toString(); + } else { + return super.toString(); + } + } +} diff --git a/java/src/org/apache/xml/resolver/CatalogManager.java b/java/src/org/apache/xml/resolver/CatalogManager.java new file mode 100644 index 0000000..67063ce --- /dev/null +++ b/java/src/org/apache/xml/resolver/CatalogManager.java @@ -0,0 +1,463 @@ +// CatalogManager.java - Access CatalogManager.properties + +/* + * The Apache Software License, Version 1.1 + * + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Xalan" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 2001, International + * Business Machines Corporation., http://www.ibm.com. For more + * information on the Apache Software Foundation, please see + *CatalogManager provides an interface to the catalog properties.
+ * + *Properties can come from two places: from system properties or + * from a CatalogManager.properties file. This class provides a transparent + * interface to both, with system properties preferred over property file values.
+ * + *The following table summarizes the properties:
+ * + *| System Property | + *CatalogManager.properties Property |
+ * Description | + *
| xml.catalog.ignoreMissing | + *+ * | If true, a missing CatalogManager.properties file or missing properties + * within that file will not generate warning messages. See also the + * ignoreMissingProperties method. | + *
| xml.catalog.files | + *catalogs | + *The |
+ *
| + * | relative-catalogs | + *If false, relative catalog URIs are made absolute with respect to the base URI of
+ * the CatalogManager.properties file. This setting only applies to catalog
+ * URIs obtained from the catalogs property |
+ *
| xml.catalog.verbosity | + *verbosity | + *If non-zero, the Catalog classes will print informative and debugging messages. + * The higher the number, the more messages. | + *
| xml.catalog.prefer | + *prefer | + *Which identifier is preferred, "public" or "system"? | + *
| xml.catalog.staticCatalog | + *static-catalog | + *Should a single catalog be constructed for all parsing, or should a different + * catalog be created for each parser? | + *
| xml.catalog.allowPI | + *allow-oasis-xml-catalog-pi | + *If the source document contains "oasis-xml-catalog" processing instructions, + * should they be used? | + *
| xml.catalog.className | + *catalog-class-name | + *If you're using the convenience classes + * org.apache.xml.resolver.tools.*), this setting + * allows you to specify an alternate class name to use for the underlying + * catalog. | + *
Load the properties from the propertyFile and build the + * resources from it.
+ */ + private synchronized static void readProperties() { + try { + propertyFileURI = CatalogManager.class.getResource("/"+propertyFile); + InputStream in = + CatalogManager.class.getResourceAsStream("/"+propertyFile); + if (in==null) { + if (!ignoreMissingProperties) { + System.err.println("Cannot find "+propertyFile); + } + 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); + } + } + } + + /** + *Tell the CatalogManager how to handle missing properties
+ * + *If ignore is true, missing or unreadable property files will + * not be reported. Otherwise, a message will be sent to System.err. + *
+ */ + public static void ignoreMissingProperties(boolean ignore) { + ignoreMissingProperties = ignore; + } + + /** + *Obtain the verbosity setting from the properties.
+ * + * @returns The verbosity level from the propertyFile or the + * defaultVerbosity. + */ + public static int verbosity () { + String verbStr = System.getProperty(pVerbosity); + + if (verbStr == null) { + if (resources==null) readProperties(); + if (resources==null) return defaultVerbosity; + try { + verbStr = resources.getString("verbosity"); + } catch (MissingResourceException e) { + return defaultVerbosity; + } + } + + try { + int verb = Integer.parseInt(verbStr.trim()); + return verb; + } catch (Exception e) { + System.err.println("Cannot parse verbosity: \"" + verbStr + "\""); + return defaultVerbosity; + } + } + + /** + *Obtain the relativeCatalogs setting from the properties.
+ * + *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.
+ * + *This property
In the properties, a value of 'yes', 'true', or '1' is considered + * true, anything else is false.
+ * + * @returns The relativeCatalogs setting from the propertyFile or the + * defaultRelativeCatalogs. + */ + public static boolean relativeCatalogs () { + 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; + } + } + + /** + *Obtain the list of catalog files from the properties.
+ * + *Note that the list of catalog files is always a semicolon + * delimited list, even on Unix systems where a colon delimited list + * might be considered more natural.
+ * + * @returns A vector of the catalog file names or null if no catalogs + * are available in the properties. + */ + public static Vector catalogFiles () { + String catalogList = System.getProperty(pFiles); + boolean 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; + } + + StringTokenizer files = new StringTokenizer(catalogList, ";"); + 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; + } + + /** + *Obtain the preferPublic setting from the properties.
+ * + *In the properties, a value of 'public' is true, + * anything else is false.
+ * + * @returns True if prefer is public or the + * defaultPreferSetting. + */ + public static boolean preferPublic () { + 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")); + } + + /** + *Obtain the static-catalog setting from the properties.
+ * + *In the properties, a value of 'yes', 'true', or '1' is considered + * true, anything else is false.
+ * + * @returns The static-catalog setting from the propertyFile or the + * defaultStaticCatalog. + */ + public static boolean staticCatalog () { + String staticCatalog = System.getProperty(pStatic); + + if (staticCatalog == null) { + if (resources==null) readProperties(); + if (resources==null) return defaultStaticCatalog; + try { + staticCatalog = resources.getString("static-catalog"); + } catch (MissingResourceException e) { + return defaultStaticCatalog; + } + } + + if (staticCatalog == null) { + return defaultStaticCatalog; + } + + return (staticCatalog.equalsIgnoreCase("true") + || staticCatalog.equalsIgnoreCase("yes") + || staticCatalog.equalsIgnoreCase("1")); + } + + /** + *Obtain the oasisXMLCatalogPI setting from the properties.
+ * + *In the properties, a value of 'yes', 'true', or '1' is considered + * true, anything else is false.
+ * + * @returns The oasisXMLCatalogPI setting from the propertyFile or the + * defaultOasisXMLCatalogPI. + */ + public static boolean allowOasisXMLCatalogPI () { + 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")); + } + + /** + *Obtain the Catalog class name setting from the properties.
+ * + */ + public static String catalogClassName () { + 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; + } +} diff --git a/java/src/org/apache/xml/resolver/LICENSE b/java/src/org/apache/xml/resolver/LICENSE new file mode 100644 index 0000000..558c10d --- /dev/null +++ b/java/src/org/apache/xml/resolver/LICENSE @@ -0,0 +1,57 @@ +/* + * The Apache Software License, Version 1.1 + * + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Xalan" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 2001, International + * Business Machines Corporation., http://www.ibm.com. For more + * information on the Apache Software Foundation, please see + *An extension to OASIS Open Catalog files, this class supports + * suffix-based matching and an external RFC2483 resolver.
+ * + * @see Catalog + * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * + * @version 1.0 + */ +public class Resolver extends Catalog { + /** + *The URISUFFIX Catalog Entry type.
+ * + *URI suffix entries match URIs that end in a specified suffix.
+ */ + public static final int URISUFFIX = CatalogEntry.addEntryType("URISUFFIX", 2); + + /** + *The SYSTEMSUFFIX Catalog Entry type.
+ * + *System suffix entries match system identifiers that end in a + * specified suffix.
+ */ + public static final int SYSTEMSUFFIX = CatalogEntry.addEntryType("SYSTEMSUFFIX", 2); + + /** + *The RESOLVER Catalog Entry type.
+ * + *A hook for providing support for web-based backup resolvers.
+ */ + public static final int RESOLVER = CatalogEntry.addEntryType("RESOLVER", 1); + + /** + *The SYSTEMREVERSE Catalog Entry type.
+ * + *This is a bit of a hack. There's no actual SYSTEMREVERSE entry, + * but this entry type is used to indicate that a reverse lookup is + * being performed. (This allows the Resolver to implement + * RFC2483 I2N and I2NS.) + */ + public static final int SYSTEMREVERSE + = CatalogEntry.addEntryType("SYSTEMREVERSE", 1); + + /** + *
Setup readers.
+ */ + public void setupReaders() { + SAXParserFactory spf = SAXParserFactory.newInstance(); + spf.setNamespaceAware(true); + spf.setValidating(false); + + SAXCatalogReader saxReader = new SAXCatalogReader(spf); + + saxReader.setCatalogParser(null, "XMLCatalog", + "org.apache.xml.resolver.readers.XCatalogReader"); + + saxReader.setCatalogParser(OASISXMLCatalogReader.namespaceName, + "catalog", + "org.apache.xml.resolver.readers.ExtendedXMLCatalogReader"); + + addReader("application/xml", saxReader); + + TR9401CatalogReader textReader = new TR9401CatalogReader(); + addReader("text/plain", textReader); + } + + /** + *Cleanup and process a Catalog entry.
+ * + *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.
+ * + * @param entry The CatalogEntry to process. + */ + public void addEntry(CatalogEntry entry) { + int type = entry.getEntryType(); + + if (type == URISUFFIX) { + String suffix = normalizeURI(entry.getEntryArg(0)); + String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1))); + + entry.setEntryArg(1, fsi); + + Debug.message(4, "URISUFFIX", suffix, fsi); + } else if (type == SYSTEMSUFFIX) { + String suffix = normalizeURI(entry.getEntryArg(0)); + String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1))); + + entry.setEntryArg(1, fsi); + + Debug.message(4, "SYSTEMSUFFIX", suffix, fsi); + } + + super.addEntry(entry); + } + + /** + *Return the applicable URI
+ * + *If a URI entry exists in the Catalog + * for the URI specified, return the mapped value.
+ * + *In the Resolver (as opposed to the Catalog) class, if the + * URI isn't found by the usual algorithm, URISUFFIX entries are + * considered.
+ * + *URI comparison is case sensitive.
+ * + * @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 { + + String resolved = super.resolveURI(uri); + if (resolved != null) { + return resolved; + } + + Enumeration enum = catalogEntries.elements(); + while (enum.hasMoreElements()) { + CatalogEntry e = (CatalogEntry) enum.nextElement(); + if (e.getEntryType() == RESOLVER) { + resolved = resolveExternalSystem(uri, e.getEntryArg(0)); + if (resolved != null) { + return resolved; + } + } else if (e.getEntryType() == URISUFFIX) { + String suffix = e.getEntryArg(0); + String result = e.getEntryArg(1); + + if (suffix.length() <= uri.length() + && uri.substring(uri.length()-suffix.length()).equals(suffix)) { + return result; + } + } + } + + // Otherwise, look in the subordinate catalogs + return resolveSubordinateCatalogs(Catalog.URI, + null, + null, + uri); + } + + /** + *Return the applicable SYSTEM system identifier, resorting + * to external RESOLVERs if necessary.
+ * + *If a SYSTEM entry exists in the Catalog + * for the system ID specified, return the mapped value.
+ * + *In the Resolver (as opposed to the Catalog) class, if the + * URI isn't found by the usual algorithm, SYSTEMSUFFIX entries are + * considered.
+ * + *On Windows-based operating systems, the comparison between + * the system identifier provided and the SYSTEM entries in the + * Catalog is case-insensitive.
+ * + * @param systemId The system ID to locate in the catalog. + * + * @return The system identifier to use for systemId. + * + * @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 { + + String resolved = super.resolveSystem(systemId); + if (resolved != null) { + return resolved; + } + + Enumeration enum = catalogEntries.elements(); + while (enum.hasMoreElements()) { + CatalogEntry e = (CatalogEntry) enum.nextElement(); + if (e.getEntryType() == RESOLVER) { + resolved = resolveExternalSystem(systemId, e.getEntryArg(0)); + if (resolved != null) { + return resolved; + } + } else if (e.getEntryType() == SYSTEMSUFFIX) { + String suffix = e.getEntryArg(0); + String result = e.getEntryArg(1); + + if (suffix.length() <= systemId.length() + && systemId.substring(systemId.length()-suffix.length()).equals(suffix)) { + return result; + } + } + } + + return resolveSubordinateCatalogs(Catalog.SYSTEM, + null, + null, + systemId); + } + + /** + *Return the applicable PUBLIC or SYSTEM identifier, resorting + * to external resolvers if necessary.
+ * + *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.
+ * + *Note that a system or public identifier in the current catalog + * (or subordinate catalogs) will be used in preference to an + * external resolver. Further, if a systemId is present, the external + * resolver(s) will be queried for that before the publicId.
+ * + * @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 { + + String resolved = super.resolvePublic(publicId, systemId); + if (resolved != null) { + return resolved; + } + + Enumeration enum = catalogEntries.elements(); + while (enum.hasMoreElements()) { + CatalogEntry e = (CatalogEntry) enum.nextElement(); + if (e.getEntryType() == RESOLVER) { + if (systemId != null) { + resolved = resolveExternalSystem(systemId, + e.getEntryArg(0)); + if (resolved != null) { + return resolved; + } + } + resolved = resolveExternalPublic(publicId, e.getEntryArg(0)); + if (resolved != null) { + return resolved; + } + } + } + + return resolveSubordinateCatalogs(Catalog.PUBLIC, + null, + publicId, + systemId); + } + + /** + *Query an external RFC2483 resolver for a system identifier.
+ * + * @param systemId The system ID to locate. + * @param resolver The name of the resolver to use. + * + * @return The system identifier to use for the systemId. + */ + protected String resolveExternalSystem(String systemId, String resolver) + throws MalformedURLException, IOException { + Resolver r = queryResolver(resolver, "i2l", systemId, null); + if (r != null) { + return r.resolveSystem(systemId); + } else { + return null; + } + } + + /** + *Query an external RFC2483 resolver for a public identifier.
+ * + * @param publicId The system ID to locate. + * @param resolver The name of the resolver to use. + * + * @return The system identifier to use for the systemId. + */ + protected String resolveExternalPublic(String publicId, String resolver) + throws MalformedURLException, IOException { + Resolver r = queryResolver(resolver, "fpi2l", publicId, null); + if (r != null) { + return r.resolvePublic(publicId, null); + } else { + return null; + } + } + + /** + *Query an external RFC2483 resolver.
+ * + * @param resolver The URL of the RFC2483 resolver. + * @param command The command to send the resolver. + * @param arg1 The first argument to the resolver. + * @param arg2 The second argument to the resolver, usually null. + * + * @return The Resolver constructed. + */ + protected Resolver queryResolver(String resolver, + String command, + String arg1, + String arg2) { + InputStream iStream = null; + String RFC2483 = resolver + "?command=" + command + + "&format=tr9401&uri=" + arg1 + + "&uri2=" + arg2; + String line = null; + + try { + URL url = new URL(RFC2483); + + URLConnection urlCon = url.openConnection(); + + urlCon.setUseCaches(false); + + Resolver r = (Resolver) newCatalog(); + + String cType = urlCon.getContentType(); + + // I don't care about the character set or subtype + if (cType.indexOf(";") > 0) { + cType = cType.substring(0, cType.indexOf(";")); + } + + r.parseCatalog(cType, urlCon.getInputStream()); + + return r; + } catch (CatalogException cex) { + if (cex.getExceptionType() == CatalogException.UNPARSEABLE) { + Debug.message(1, "Unparseable catalog: " + RFC2483); + } else if (cex.getExceptionType() + == CatalogException.UNKNOWN_FORMAT) { + Debug.message(1, "Unknown catalog format: " + RFC2483); + } + return null; + } catch (MalformedURLException mue) { + Debug.message(1, "Malformed resolver URL: " + RFC2483); + return null; + } catch (IOException ie) { + Debug.message(1, "I/O Exception opening resolver: " + RFC2483); + return null; + } + } + + /** + *Append two vectors, returning the result.
+ * + * @param vec The first vector + * @param appvec The vector to be appended + * @return The vector vec, with appvec's elements appended to it + */ + private Vector appendVector(Vector vec, Vector appvec) { + if (appvec != null) { + for (int count = 0; count < appvec.size(); count++) { + vec.addElement(appvec.elementAt(count)); + } + } + return vec; + } + + /** + *Find the URNs for a given system identifier in all catalogs.
+ * + * @param systemId The system ID to locate. + * + * @return A vector of URNs that map to the systemId. + */ + public Vector resolveAllSystemReverse(String systemId) + throws MalformedURLException, IOException { + Vector resolved = new Vector(); + + // If there's a SYSTEM entry in this catalog, use it + if (systemId != null) { + Vector localResolved = resolveLocalSystemReverse(systemId); + resolved = appendVector(resolved, localResolved); + } + + // Otherwise, look in the subordinate catalogs + Vector subResolved = resolveAllSubordinateCatalogs(SYSTEMREVERSE, + null, + null, + systemId); + + return appendVector(resolved, subResolved); + } + + /** + *Find the URN for a given system identifier.
+ * + * @param systemId The system ID to locate. + * + * @return A (single) URN that maps to the systemId. + */ + public String resolveSystemReverse(String systemId) + throws MalformedURLException, IOException { + Vector resolved = resolveAllSystemReverse(systemId); + if (resolved != null && resolved.size() > 0) { + return (String) resolved.elementAt(0); + } else { + return null; + } + } + + /** + *Return the applicable SYSTEM system identifiers
+ * + *If one or more SYSTEM entries exists in the Catalog + * for the system ID specified, return the mapped values.
+ * + *The caller is responsible for doing any necessary + * normalization of the system identifier before calling + * this method. For example, a relative system identifier in + * a document might be converted to an absolute system identifier + * before attempting to resolve it.
+ * + *Note that this function will force all subordinate catalogs + * to be loaded.
+ * + *On Windows-based operating systems, the comparison between + * the system identifier provided and the SYSTEM entries in the + * Catalog is case-insensitive.
+ * + * @param systemId The system ID to locate in the catalog. + * + * @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 Vector resolveAllSystem(String systemId) + throws MalformedURLException, IOException { + Vector resolutions = new Vector(); + + // If there are SYSTEM entries in this catalog, start with them + if (systemId != null) { + Vector localResolutions = resolveAllLocalSystem(systemId); + resolutions = appendVector(resolutions, localResolutions); + } + + // Then look in the subordinate catalogs + Vector subResolutions = resolveAllSubordinateCatalogs(SYSTEM, + null, + null, + systemId); + resolutions = appendVector(resolutions, subResolutions); + + if (resolutions.size() > 0) { + return resolutions; + } else { + return null; + } + } + + /** + *Return all applicable SYSTEM system identifiers in this + * catalog.
+ * + *If one or more SYSTEM entries exists in the catalog file + * for the system ID specified, return the mapped values.
+ * + * @param systemId The system ID to locate in the catalog + * + * @return A vector of the mapped system identifiers or null + */ + private Vector resolveAllLocalSystem(String systemId) { + Vector map = new Vector(); + String osname = System.getProperty("os.name"); + boolean windows = (osname.indexOf("Windows") >= 0); + Enumeration enum = catalogEntries.elements(); + while (enum.hasMoreElements()) { + CatalogEntry e = (CatalogEntry) enum.nextElement(); + if (e.getEntryType() == SYSTEM + && (e.getEntryArg(0).equals(systemId) + || (windows + && e.getEntryArg(0).equalsIgnoreCase(systemId)))) { + map.addElement(e.getEntryArg(1)); + } + } + if (map.size() == 0) { + return null; + } else { + return map; + } + } + + /** + *Find the URNs for a given system identifier in the current catalog.
+ * + * @param systemId The system ID to locate. + * + * @return A vector of URNs that map to the systemId. + */ + private Vector resolveLocalSystemReverse(String systemId) { + Vector map = new Vector(); + String osname = System.getProperty("os.name"); + boolean windows = (osname.indexOf("Windows") >= 0); + Enumeration enum = catalogEntries.elements(); + while (enum.hasMoreElements()) { + CatalogEntry e = (CatalogEntry) enum.nextElement(); + if (e.getEntryType() == SYSTEM + && (e.getEntryArg(1).equals(systemId) + || (windows + && e.getEntryArg(1).equalsIgnoreCase(systemId)))) { + map.addElement(e.getEntryArg(0)); + } + } + if (map.size() == 0) { + return null; + } else { + return map; + } + } + + /** + *Search the subordinate catalogs, in order, looking for all + * match.
+ * + *This method searches the Catalog and returns all of the system + * identifiers specified for the given entity type with the given + * name, public, and system identifiers. In some contexts, these + * may be null.
+ * + * @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). + * + * @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. + */ + private synchronized Vector resolveAllSubordinateCatalogs(int entityType, + String entityName, + String publicId, + String systemId) + throws MalformedURLException, IOException { + + Vector resolutions = new Vector(); + + for (int catPos = 0; catPos < catalogs.size(); catPos++) { + Resolver c = null; + + try { + c = (Resolver) catalogs.elementAt(catPos); + } catch (ClassCastException e) { + String catfile = (String) catalogs.elementAt(catPos); + c = (Resolver) newCatalog(); + + try { + c.parseCatalog(catfile); + } catch (MalformedURLException mue) { + Debug.message(1, "Malformed Catalog URL", catfile); + } catch (FileNotFoundException fnfe) { + Debug.message(1, "Failed to load catalog, file not found", + catfile); + } catch (IOException ioe) { + 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); + if (resolved != null) { + // Only find one DOCTYPE resolution + resolutions.addElement(resolved); + return resolutions; + } + } else if (entityType == DOCUMENT) { + resolved = c.resolveDocument(); + if (resolved != null) { + // Only find one DOCUMENT resolution + resolutions.addElement(resolved); + return resolutions; + } + } else if (entityType == ENTITY) { + resolved = c.resolveEntity(entityName, + publicId, + systemId); + if (resolved != null) { + // Only find one ENTITY resolution + resolutions.addElement(resolved); + return resolutions; + } + } else if (entityType == NOTATION) { + resolved = c.resolveNotation(entityName, + publicId, + systemId); + if (resolved != null) { + // Only find one NOTATION resolution + resolutions.addElement(resolved); + return resolutions; + } + } else if (entityType == PUBLIC) { + resolved = c.resolvePublic(publicId, systemId); + if (resolved != null) { + // Only find one PUBLIC resolution + resolutions.addElement(resolved); + return resolutions; + } + } else if (entityType == SYSTEM) { + Vector localResolutions = c.resolveAllSystem(systemId); + resolutions = appendVector(resolutions, localResolutions); + break; + } else if (entityType == SYSTEMREVERSE) { + Vector localResolutions = c.resolveAllSystemReverse(systemId); + resolutions = appendVector(resolutions, localResolutions); + } + } + + if (resolutions != null) { + return resolutions; + } else { + return null; + } + } +} + + + + diff --git a/java/src/org/apache/xml/resolver/apps/XParseError.java b/java/src/org/apache/xml/resolver/apps/XParseError.java new file mode 100644 index 0000000..7a18839 --- /dev/null +++ b/java/src/org/apache/xml/resolver/apps/XParseError.java @@ -0,0 +1,200 @@ +// XParseError.java - An error handler for xparse + +/* + * The Apache Software License, Version 1.1 + * + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Xalan" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 2001, International + * Business Machines Corporation., http://www.ibm.com. For more + * information on the Apache Software Foundation, please see + *An ErrorHandler for xparse.
+ * + *This class is just the error handler for xparse.
+ * + * @see xparse + * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * + * @version 1.0 + */ +public class XParseError implements ErrorHandler { + /** Show errors? */ + private boolean showErrors = true; + + /** Show warnings? */ + private boolean showWarnings = false; + + /** How many messages should be presented? */ + private int maxMessages = 10; + + /** The number of fatal errors seen so far. */ + private int fatalCount = 0; + + /** The number of errors seen so far. */ + private int errorCount = 0; + + /** The number of warnings seen so far. */ + private int warningCount = 0; + + /** The base URI of the running application. */ + private String baseURI = ""; + + /** Constructor */ + public XParseError(boolean errors, boolean warnings) { + showErrors = errors; + showWarnings = warnings; + + String dir = System.getProperty("user.dir"); + String file = ""; + + if (dir.endsWith("/")) { + file = "file:" + dir + "file"; + } else { + file = "file:" + dir + "/" + file; + } + + try { + URL url = new URL(file); + baseURI = url.toString(); + } catch (MalformedURLException mue) { + // nop; + } + } + + /** Return the error count */ + public int getErrorCount() { + return errorCount; + } + + /** Return the fatal error count */ + public int getFatalCount() { + return fatalCount; + } + + /** Return the warning count */ + public int getWarningCount() { + return warningCount; + } + + /** Return the number of messages to display */ + public int getMaxMessages() { + return maxMessages; + } + + /** Set the number of messages to display */ + public void setMaxMessages(int max) { + maxMessages = max; + } + + /** SAX2 API */ + public void error(SAXParseException exception) { + if (showErrors) { + if (errorCount+warningCount < maxMessages) { + message("Error", exception); + } + errorCount++; + } + } + + /** SAX2 API */ + public void fatalError(SAXParseException exception) { + if (showErrors) { + if (errorCount+warningCount < maxMessages) { + message("Fatal error", exception); + } + errorCount++; + fatalCount++; + } + } + + /** SAX2 API */ + public void warning(SAXParseException exception) { + if (showWarnings) { + if ((errorCount+warningCount < maxMessages)) { + message("Warning", exception); + } + warningCount++; + } + } + + /** Display a message to the user */ + private void message(String type, SAXParseException exception) { + String filename = exception.getSystemId(); + if (filename.startsWith(baseURI)) { + filename = filename.substring(baseURI.length()); + } + + System.out.print(type + + ":" + + filename + + ":" + + exception.getLineNumber()); + + if (exception.getColumnNumber() > 0) { + System.out.print(":" + exception.getColumnNumber()); + } + + System.out.println(":" + exception.getMessage()); + } +} diff --git a/java/src/org/apache/xml/resolver/apps/package.html b/java/src/org/apache/xml/resolver/apps/package.html new file mode 100644 index 0000000..a04bf36 --- /dev/null +++ b/java/src/org/apache/xml/resolver/apps/package.html @@ -0,0 +1,12 @@ + + +Apache XML Commons Resolver Applications package
+ +This package provides a few simple command-line tools for exploring +the facilities of the resolver.
+ + + diff --git a/java/src/org/apache/xml/resolver/apps/resolver.java b/java/src/org/apache/xml/resolver/apps/resolver.java new file mode 100644 index 0000000..73e86d7 --- /dev/null +++ b/java/src/org/apache/xml/resolver/apps/resolver.java @@ -0,0 +1,338 @@ +// resolver.java - A simple command-line test tool for the resolver + +/* + * The Apache Software License, Version 1.1 + * + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Xalan" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 2001, International + * Business Machines Corporation., http://www.ibm.com. For more + * information on the Apache Software Foundation, please see + *A simple command-line resolver.
+ * + *This class implements a simple command-line resolver. It takes + * some parameters and passes them through the resolver, printing the + * result. + *
+ * + *Usage: resolver [options] keyword
+ * + *Where options are:
+ * + *-c catalogfile-n name-p publicId-s systemId-a-u uri-d integerAnd keyword is one of: doctype, document, entity, notation, public, + * system, or uri.
+ * + *The process ends with error-level 1, if there errors.
+ * + * @see org.apache.xml.resolver.tools.ResolvingParser + * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * + * @version 1.0 */ +public class resolver { + /** The main entry point */ + public static void main (String[] args) + throws FileNotFoundException, IOException { + + int debug = 0; + Vector catalogFiles = new Vector(); + int resType = 0; + String resTypeStr = null; + String name = null; + String publicId = null; + String systemId = null; + String uri = null; + boolean absoluteSystem = false; + + for (int i=0; iA simple command-line XML parsing application.
+ * + *This class implements a simple command-line XML Parser. It's + * just a little wrapper around the JAXP Parser with support for + * catalogs. + *
+ * + *Usage: xparse [options] document.xml
+ * + *Where:
+ * + *-c catalogfile-w-v (the default)-n-N (the default)-d integer-E integerThe process ends with error-level 1, if there errors.
+ * + * @deprecated This interface has been replaced by the + * {@link org.apache.xml.resolver.tools.ResolvingXMLReader} for SAX2. + * + * @see org.apache.xml.resolver.tools.ResolvingParser + * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * + * @version 1.0 + */ +public class xparse +{ + /** The main entry point */ + public static void main (String[] args) + throws FileNotFoundException, IOException { + + String xmlfile = null; + int debug = 0; + int maxErrs = 10; + boolean nsAware = true; + boolean validating = true; + boolean showWarnings = (debug > 2); + boolean showErrors = true; + Vector catalogFiles = new Vector(); + + for (int i=0; iA simple command-line XML parsing application.
+ * + *This class implements a simple command-line XML Parser. It's + * just a little wrapper around the JAXP XMLReader with support for + * catalogs. + *
+ * + *Usage: xread [options] document.xml
+ * + *Where:
+ * + *-c catalogfile-w-v (the default)-n-N (the default)-d integer-E integerThe process ends with error-level 1, if there errors.
+ * + * @see org.apache.xml.resolver.tools.ResolvingXMLReader + * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * + * @version 1.0 + */ +public class xread +{ + /** The main entry point */ + public static void main (String[] args) + throws FileNotFoundException, IOException { + + String xmlfile = null; + int debug = 0; + int maxErrs = 10; + boolean nsAware = true; + boolean validating = true; + boolean showWarnings = (debug > 2); + boolean showErrors = true; + Vector catalogFiles = new Vector(); + + for (int i=0; iStatic debugging/messaging class for Catalogs.
+ * + *This class defines a set of static methods that can be called + * to produce debugging messages. Messages have an associated "debug + * level" and messages below the current setting are not displayed.
+ * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * + * @version 1.0 + */ +public class Debug { + /** The internal debug level. */ + protected static int debug = CatalogManager.verbosity(); + + /** Set the debug level for future messages. */ + public static void setDebug(int newDebug) { + debug = newDebug; + } + + /** Get the current debug level. */ + public static int getDebug() { + return debug; + } + + /** + *Print debug message (if the debug level is high enough).
+ * + *Prints "the message"
+ * + * @param level The debug level of this message. This message + * will only be + * displayed if the current debug level is at least equal to this + * value. + * @param message The text of the message. + */ + public static void message(int level, String message) { + if (debug >= level) { + System.out.println(message); + } + } + + /** + *Print debug message (if the debug level is high enough).
+ * + *Prints "the message: spec"
+ * + * @param level The debug level of this message. This message + * will only be + * displayed if the current debug level is at least equal to this + * value. + * @param message The text of the message. + * @param spec An argument to the message. + */ + public static void message(int level, String message, String spec) { + if (debug >= level) { + System.out.println(message + ": " + spec); + } + } + + /** + *Print debug message (if the debug level is high enough).
+ * + *Prints "the message: spec1" and "spec2" indented on the next line.
+ * + * @param level The debug level of this message. This message + * will only be + * displayed if the current debug level is at least equal to this + * value. + * @param message The text of the message. + * @param spec1 An argument to the message. + * @param spec2 Another argument to the message. + */ + public static void message(int level, String message, + String spec1, String spec2) { + if (debug >= level) { + System.out.println(message + ": " + spec1); + System.out.println("\t" + spec2); + } + } +} diff --git a/java/src/org/apache/xml/resolver/helpers/Namespaces.java b/java/src/org/apache/xml/resolver/helpers/Namespaces.java new file mode 100644 index 0000000..ffd2fba --- /dev/null +++ b/java/src/org/apache/xml/resolver/helpers/Namespaces.java @@ -0,0 +1,151 @@ +// Namespaces.java - Analyze namespace nodes in a DOM tree + +/* + * The Apache Software License, Version 1.1 + * + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Xalan" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 2001, International + * Business Machines Corporation., http://www.ibm.com. For more + * information on the Apache Software Foundation, please see + *Static Namespace query methods.
+ * + *This class defines a set of static methods that can be called + * to analyze the namespace properties of DOM nodes.
+ * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * + * @version 1.0 + */ +public class Namespaces { + /** + *Returns the "prefix" part of a QName or the empty string (not + * null) if the name has no prefix.
+ * + * @param element The QName of an element. + * @return The prefix part of the element name. + */ + public static String getPrefix(Element element) { + String name = element.getTagName(); + String prefix = ""; + + if (name.indexOf(':') > 0) { + prefix = name.substring(0, name.indexOf(':')); + } + + return prefix; + } + + /** + *Returns the "localname" part of a QName, which is the whole + * name if it has no prefix.
+ * + * @param element The QName of an element. + * @return The local part of a QName. + */ + public static String getLocalName(Element element) { + String name = element.getTagName(); + + if (name.indexOf(':') > 0) { + name = name.substring(name.indexOf(':')+1); + } + + return name; + } + + /** + *Returns the namespace URI for the specified prefix at the + * specified context node.
+ * + * @param node The context node. + * @param prefix The prefix. + * @return The namespace URI associated with the prefix, or + * null if no namespace declaration exists for the prefix. + */ + public static String getNamespaceURI(Node node, String prefix) { + if (node == null || node.getNodeType() != Node.ELEMENT_NODE) { + return null; + } + + if (prefix.equals("")) { + if (((Element) node).hasAttribute("xmlns")) { + return ((Element) node).getAttribute("xmlns"); + } + } else { + String nsattr = "xmlns:" + prefix; + if (((Element) node).hasAttribute(nsattr)) { + return ((Element) node).getAttribute(nsattr); + } + } + + return getNamespaceURI(node.getParentNode(), prefix); + } + + /** + *Returns the namespace URI for the namespace to which the + * element belongs.
+ * + * @param element The element. + * @return The namespace URI associated with the namespace of the + * element, or null if no namespace declaration exists for it. + */ + public static String getNamespaceURI(Element element) { + String prefix = getPrefix(element); + return getNamespaceURI(element, prefix); + } +} diff --git a/java/src/org/apache/xml/resolver/helpers/PublicId.java b/java/src/org/apache/xml/resolver/helpers/PublicId.java new file mode 100644 index 0000000..b964d90 --- /dev/null +++ b/java/src/org/apache/xml/resolver/helpers/PublicId.java @@ -0,0 +1,198 @@ +// PublicId.java - Information about public identifiers + +/* + * The Apache Software License, Version 1.1 + * + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Xalan" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 2001, International + * Business Machines Corporation., http://www.ibm.com. For more + * information on the Apache Software Foundation, please see + *Static methods for dealing with public identifiers.
+ * + *This class defines a set of static methods that can be called + * to handle public identifiers.
+ * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * + * @version 1.0 + */ +public abstract class PublicId { + protected PublicId() { } + + /** + *Normalize a public identifier.
+ * + *Public identifiers must be normalized according to the following + * rules before comparisons between them can be made:
+ * + *This method is declared static so that other classes + * can use it directly.
+ * + * @param publicId The unnormalized public identifier. + * + * @return The normalized identifier. + */ + public static String normalize(String publicId) { + String normal = publicId.replace('\t', ' '); + normal = normal.replace('\r', ' '); + normal = normal.replace('\n', ' '); + normal = normal.trim(); + + int pos; + + while ((pos = normal.indexOf(" ")) >= 0) { + normal = normal.substring(0, pos) + normal.substring(pos+1); + } + + return normal; + } + + /** + *Encode a public identifier as a "publicid" URN.
+ * + *This method is declared static so that other classes + * can use it directly.
+ * + * @param publicId The unnormalized public identifier. + * + * @return The normalized identifier. + */ + public static String encodeURN(String publicId) { + String urn = PublicId.normalize(publicId); + + urn = PublicId.stringReplace(urn, "%", "%25"); + urn = PublicId.stringReplace(urn, ";", "%3B"); + urn = PublicId.stringReplace(urn, "'", "%27"); + urn = PublicId.stringReplace(urn, "?", "%3F"); + urn = PublicId.stringReplace(urn, "#", "%23"); + urn = PublicId.stringReplace(urn, "+", "%2B"); + urn = PublicId.stringReplace(urn, " ", "+"); + urn = PublicId.stringReplace(urn, "::", ";"); + urn = PublicId.stringReplace(urn, ":", "%3A"); + urn = PublicId.stringReplace(urn, "//", ":"); + urn = PublicId.stringReplace(urn, "/", "%2F"); + + return "urn:publicid:" + urn; + } + + /** + *Decode a "publicid" URN into a public identifier.
+ * + *This method is declared static so that other classes + * can use it directly.
+ * + * @param publicId The unnormalized public identifier. + * + * @return The normalized identifier. + */ + public static String decodeURN(String urn) { + String publicId = ""; + + if (urn.startsWith("urn:publicid:")) { + publicId = urn.substring(13); + } else { + return urn; + } + + publicId = PublicId.stringReplace(publicId, "%2F", "/"); + publicId = PublicId.stringReplace(publicId, ":", "//"); + publicId = PublicId.stringReplace(publicId, "%3A", ":"); + publicId = PublicId.stringReplace(publicId, ";", "::"); + publicId = PublicId.stringReplace(publicId, "+", " "); + publicId = PublicId.stringReplace(publicId, "%2B", "+"); + publicId = PublicId.stringReplace(publicId, "%23", "#"); + publicId = PublicId.stringReplace(publicId, "%3F", "?"); + publicId = PublicId.stringReplace(publicId, "%27", "'"); + publicId = PublicId.stringReplace(publicId, "%3B", ";"); + publicId = PublicId.stringReplace(publicId, "%25", "%"); + + return publicId; + } + + /** + *Replace one string with another.
+ * + */ + private static String stringReplace(String str, + String oldStr, + String newStr) { + + String result = ""; + int pos = str.indexOf(oldStr); + + // System.out.println(str + ": " + oldStr + " => " + newStr); + + while (pos >= 0) { + // System.out.println(str + " (" + pos + ")"); + result += str.substring(0, pos); + result += newStr; + str = str.substring(pos+1); + + pos = str.indexOf(oldStr); + } + + return result + str; + } +} diff --git a/java/src/org/apache/xml/resolver/helpers/package.html b/java/src/org/apache/xml/resolver/helpers/package.html new file mode 100644 index 0000000..a0ad685 --- /dev/null +++ b/java/src/org/apache/xml/resolver/helpers/package.html @@ -0,0 +1,12 @@ + + +Apache XML Commons Resolver Helpers package
+ +This package provides a collection of static helper functions used +across a number of different classes.
+ + + diff --git a/java/src/org/apache/xml/resolver/overview.html b/java/src/org/apache/xml/resolver/overview.html new file mode 100644 index 0000000..0f5d2c6 --- /dev/null +++ b/java/src/org/apache/xml/resolver/overview.html @@ -0,0 +1,41 @@ + + +The Apache XML Commons Resolver classes implement Catalog-based entity and +URI resolution.
+ +The resolver class implements the full semantics of OASIS +Technical +Resolution 9401:1997 (Amendment 2 to TR 9401) catalogs and +the 06 Aug 2001 Committee Specification of OASIS +XML +Catalogs.
+ +It also +includes a framework of classes designed to read catalog files in +a number of formats: +
+ +The tools +classes provide "out-of-the-box" SAX1 Parser, SAX2 XMLReader, +and JAXP URIResolvers. If you're just interested in hooking a +parser or URI resolver into your application, this is the place to start. +
+ + + diff --git a/java/src/org/apache/xml/resolver/package.html b/java/src/org/apache/xml/resolver/package.html new file mode 100644 index 0000000..9e80df3 --- /dev/null +++ b/java/src/org/apache/xml/resolver/package.html @@ -0,0 +1,34 @@ + + +Apache XML Commons Resolver package
+ +The classes in this package implement the full semantics of +OASIS Technical +Resolution 9401:1997 (Amendment 2 to TR 9401) catalogs. Also included +are a framework of classes designed to read catalog files in +a number of formats. +
+ +The principal classes in this package are:
+ +The CatalogReader interface.
+ * + *The Catalog class requires that classes implement this interface + * in order to be used to read catalogs. Examples of CatalogReaders + * include the TextCatalogReader, the SAXCatalogReader, and the + * DOMCatalogReader.
+ * + * @see Catalog + * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * + * @version 1.0 + */ +public interface CatalogReader { + /** + *Read a catalog from a file
+ * + *This class reads a catalog from a URL.
+ * + * @param catalog The catalog for which this reader is called. + * @param fileUrl The URL of a document to be read. + * @throws MalformedURLException if the specified URL cannot be + * turned into a URL object. + * @throws IOException if the URL cannot be read. + * @throws UnknownCatalogFormatException if the catalog format is + * not recognized. + * @throws UnparseableCatalogException if the catalog cannot be parsed. + * (For example, if it is supposed to be XML and isn't well-formed.) + */ + public void readCatalog(Catalog catalog, String fileUrl) + throws MalformedURLException, IOException, CatalogException; + + /** + *Read a catalog from an input stream
+ * + *This class reads a catalog from an input stream.
+ * + * @param catalog The catalog for which this reader is called. + * @param is The input stream that is to be read. + * @throws IOException if the URL cannot be read. + * @throws UnknownCatalogFormatException if the catalog format is + * not recognized. + * @throws UnparseableCatalogException if the catalog cannot be parsed. + * (For example, if it is supposed to be XML and isn't well-formed.) + */ + public void readCatalog(Catalog catalog, InputStream is) + throws IOException, CatalogException; +} diff --git a/java/src/org/apache/xml/resolver/readers/DOMCatalogParser.java b/java/src/org/apache/xml/resolver/readers/DOMCatalogParser.java new file mode 100644 index 0000000..3095eea --- /dev/null +++ b/java/src/org/apache/xml/resolver/readers/DOMCatalogParser.java @@ -0,0 +1,90 @@ +// DOMCatalogParser.java - An interface for reading catalog files + +/* + * The Apache Software License, Version 1.1 + * + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Xalan" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 2001, International + * Business Machines Corporation., http://www.ibm.com. For more + * information on the Apache Software Foundation, please see + *The DOMCatalogParser interface.
+ * + *This interface must be implemented in order for a class to + * participate as a parser for the DOMCatalogReader. + * + * @see Catalog + * @see DOMCatalogReader + * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * + * @version 1.0 + */ +public interface DOMCatalogParser { + /** + *
Parse a DOM node as a catalog entry
+ * + *This method is expected to analyze the specified node and + * construct appropriate catalog entry(ies) from it.
+ * + * @param catalog The catalog for which this node is being considered. + * @param node The DOM Node from the catalog. + */ + public void parseCatalogEntry(Catalog catalog, Node node); +} diff --git a/java/src/org/apache/xml/resolver/readers/DOMCatalogReader.java b/java/src/org/apache/xml/resolver/readers/DOMCatalogReader.java new file mode 100644 index 0000000..9a67bd2 --- /dev/null +++ b/java/src/org/apache/xml/resolver/readers/DOMCatalogReader.java @@ -0,0 +1,288 @@ +// DOMCatalogReader.java - Read XML Catalog files + +/* + * The Apache Software License, Version 1.1 + * + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Xalan" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 2001, International + * Business Machines Corporation., http://www.ibm.com. For more + * information on the Apache Software Foundation, please see + *A DOM-based CatalogReader
+ * + *This class is used to read XML Catalogs using the DOM. This reader + * has an advantage over the SAX-based reader that it can analyze the + * DOM tree rather than simply a series of SAX events. It has the disadvantage + * that it requires all of the code necessary to build and walk a DOM + * tree.
+ * + *Since the choice of CatalogReaders (in the InputStream case) can only + * be made on the basis of MIME type, the following problem occurs: only + * one CatalogReader can exist for all XML mime types. In order to get + * around this problem, the DOMCatalogReader relies on a set of external + * CatalogParsers to actually build the catalog.
+ * + *The selection of CatalogParsers is made on the basis of the QName + * of the root element of the document.
+ * + *This class requires the Java API for XML Parsing.
+ * + * @see Catalog + * @see CatalogReader + * @see SAXCatalogReader + * @see TextCatalogReader + * @see DOMCatalogParser + * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * + * @version 1.0 + */ +public class DOMCatalogReader implements CatalogReader { + /** + *Mapping table from QNames to CatalogParser classes.
+ * + *Each key in this hash table has the form "elementname" + * or "{namespaceuri}elementname". The former is used if the + * namespace URI is null.
+ */ + protected Hashtable namespaceMap = new Hashtable(); + + /** + *Add a new parser to the reader.
+ * + *This method associates the specified parserClass with the + * namespaceURI/rootElement names specified.
+ * + * @param namespaceURI The namespace URI. Not the prefix. + * @param rootElement The name of the root element. + * @param parserClass The name of the parserClass to instantiate + * for this kind of catalog. + */ + public void setCatalogParser(String namespaceURI, + String rootElement, + String parserClass) { + if (namespaceURI == null) { + namespaceMap.put(rootElement, parserClass); + } else { + namespaceMap.put("{"+namespaceURI+"}"+rootElement, parserClass); + } + } + + /** + *Get the name of the parser class for a given catalog type.
+ * + *This method returns the parserClass associated with the + * namespaceURI/rootElement names specified.
+ * + * @param namespaceURI The namespace URI. Not the prefix. + * @param rootElement The name of the root element. + * @return The parser class. + */ + public String getCatalogParser(String namespaceURI, + String rootElement) { + if (namespaceURI == null) { + return (String) namespaceMap.get(rootElement); + } else { + return (String) namespaceMap.get("{"+namespaceURI+"}"+rootElement); + } + } + + /** + *Null constructor; something for subclasses to call.
+ */ + public DOMCatalogReader() { } + + /** + *Read a catalog from an input stream
+ * + *This class reads a catalog from an input stream:
+ * + *Read the catalog behind the specified URL.
+ * + * @see #readCatalog(Catalog, InputStream) + * + * @param catalog The catalog for which we are reading. + * @param fileUrl The URL of the document that should be read. + * + * @throws MalformedURLException if the specified URL cannot be + * turned into a URL object. + * @throws IOException if the URL cannot be read. + * @throws UnknownCatalogFormatException if the catalog format is + * not recognized. + * @throws UnparseableCatalogException if the catalog cannot be parsed. + * (For example, if it is supposed to be XML and isn't well-formed.) + */ + public void readCatalog(Catalog catalog, String fileUrl) + throws MalformedURLException, IOException, CatalogException { + URL url = new URL(fileUrl); + URLConnection urlCon = url.openConnection(); + readCatalog(catalog, urlCon.getInputStream()); + } +} diff --git a/java/src/org/apache/xml/resolver/readers/ExtendedXMLCatalogReader.java b/java/src/org/apache/xml/resolver/readers/ExtendedXMLCatalogReader.java new file mode 100644 index 0000000..86e0d28 --- /dev/null +++ b/java/src/org/apache/xml/resolver/readers/ExtendedXMLCatalogReader.java @@ -0,0 +1,235 @@ +// ExtendedXMLCatalogReader.java - Read XML Catalog files + +/* + * The Apache Software License, Version 1.1 + * + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Xalan" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 2001, International + * Business Machines Corporation., http://www.ibm.com. For more + * information on the Apache Software Foundation, please see + *Parse Extended OASIS Entity Resolution Technical Committee + * XML Catalog files.
+ * + * @see Catalog + * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * + * @version 1.0 + */ +public class ExtendedXMLCatalogReader extends OASISXMLCatalogReader { + /** The namespace name of extended catalog elements */ + public static final String extendedNamespaceName = "http://nwalsh.com/xcatalog/1.0"; + + /** + *The SAX startElement method recognizes elements
+ * from the plain catalog format and instantiates CatalogEntry
+ * objects for them.
The SAX endElement method does nothing.
Parse OASIS Entity Resolution Technical Committee + * XML Catalog files.
+ * + * @see Catalog + * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * + * @version 1.0 + */ +public class OASISXMLCatalogReader extends SAXCatalogReader implements SAXCatalogParser { + /** The catalog object needs to be stored by the object so that + * SAX callbacks can use it. + */ + protected Catalog catalog = null; + + /** The namespace name of OASIS ERTC catalogs */ + public static final String namespaceName = "urn:oasis:names:tc:entity:xmlns:xml:catalog"; + + /** The namespace name of OASIS ERTC TR9401 catalog extension */ + public static final String tr9401NamespaceName = "urn:oasis:names:tc:entity:xmlns:tr9401:catalog"; + + protected Stack baseURIStack = new Stack(); + protected Stack overrideStack = new Stack(); + protected Stack namespaceStack = new Stack(); + + /** Set the current catalog. */ + public void setCatalog (Catalog catalog) { + this.catalog = catalog; + } + + /** Get the current catalog. */ + public Catalog getCatalog () { + return catalog; + } + + /** + *Are we in an extension namespace?
+ * + * @returns true if the current stack of open namespaces includes + * an extension namespace. + */ + protected boolean inExtensionNamespace() { + boolean inExtension = false; + + Enumeration elements = namespaceStack.elements(); + while (!inExtension && elements.hasMoreElements()) { + String ns = (String) elements.nextElement(); + if (ns == null) { + inExtension = true; + } else { + inExtension = (!ns.equals(tr9401NamespaceName) + && !ns.equals(namespaceName)); + } + } + + return inExtension; + } + + // ---------------------------------------------------------------------- + // Implement the SAX ContentHandler interface + + /**The SAX setDocumentLocator method does nothing.
The SAX startDocument method does nothing.
The SAX endDocument method does nothing.
The SAX startElement method recognizes elements
+ * from the plain catalog format and instantiates CatalogEntry
+ * objects for them.
The SAX endElement method does nothing.
The SAX characters method does nothing.
The SAX ignorableWhitespace method does nothing.
The SAX processingInstruction method does nothing.
The SAX skippedEntity method does nothing.
The SAX startPrefixMapping method does nothing.
The SAX endPrefixMapping method does nothing.
The SAXCatalogParser interface.
+ * + *This interface must be implemented in order for a class to
+ * participate as a parser for the SAXCatalogReader.
+ *
+ * @see Catalog
+ * @see SAXCatalogReader
+ *
+ * @author Norman Walsh
+ * Norman.Walsh@Sun.COM
+ *
+ * @version 1.0
+ */
+public interface SAXCatalogParser extends ContentHandler, DocumentHandler {
+ /** Set the Catalog for which parsing is being performed. */
+ public void setCatalog(Catalog catalog);
+}
diff --git a/java/src/org/apache/xml/resolver/readers/SAXCatalogReader.java b/java/src/org/apache/xml/resolver/readers/SAXCatalogReader.java
new file mode 100644
index 0000000..ff2a471
--- /dev/null
+++ b/java/src/org/apache/xml/resolver/readers/SAXCatalogReader.java
@@ -0,0 +1,515 @@
+// SAXCatalogReader.java - Read XML Catalog files
+
+/*
+ * The Apache Software License, Version 1.1
+ *
+ *
+ * Copyright (c) 2001 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ * if any, must include the following acknowledgment:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowledgment may appear in the software itself,
+ * if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Xalan" and "Apache Software Foundation" must
+ * not be used to endorse or promote products derived from this
+ * software without prior written permission. For written
+ * permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache",
+ * nor may "Apache" appear in their name, without prior written
+ * permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation and was
+ * originally based on software copyright (c) 2001, International
+ * Business Machines Corporation., http://www.ibm.com. For more
+ * information on the Apache Software Foundation, please see
+ *
A SAX-based CatalogReader
+ * + *This class is used to read XML Catalogs using the SAX. This reader + * has an advantage over the DOM-based reader in that it functions on + * the stream of SAX events. It has the disadvantage + * that it cannot look around in the tree.
+ * + *Since the choice of CatalogReaders (in the InputStream case) can only + * be made on the basis of MIME type, the following problem occurs: only + * one CatalogReader can exist for all XML mime types. In order to get + * around this problem, the SAXCatalogReader relies on a set of external + * CatalogParsers to actually build the catalog.
+ * + *The selection of CatalogParsers is made on the basis of the QName + * of the root element of the document.
+ * + * @see Catalog + * @see CatalogReader + * @see SAXCatalogReader + * @see TextCatalogReader + * @see DOMCatalogParser + * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * + * @version 1.0 + */ +public class SAXCatalogReader implements CatalogReader, ContentHandler, DocumentHandler { + /** The SAX Parser Factory */ + protected SAXParserFactory parserFactory = null; + + /** The SAX Parser Class */ + protected String parserClass = null; + + /** + *Mapping table from QNames to CatalogParser classes.
+ * + *Each key in this hash table has the form "elementname" + * or "{namespaceuri}elementname". The former is used if the + * namespace URI is null.
+ */ + protected Hashtable namespaceMap = new Hashtable(); + + /** The parser in use for the current catalog. */ + private SAXCatalogParser saxParser = null; + + /** Set if something goes horribly wrong. It allows the class to + * ignore the rest of the events that are received. + */ + private boolean abandonHope = false; + + /** The Catalog that we're working for. */ + private Catalog catalog; + + /** Set the XML SAX Parser Factory. + */ + public void setParserFactory(SAXParserFactory parserFactory) { + this.parserFactory = parserFactory; + } + + /** Set the XML SAX Parser Class + */ + public void setParserClass(String parserClass) { + this.parserClass = parserClass; + } + + /** Get the parser factory currently in use. */ + public SAXParserFactory getParserFactory() { + return parserFactory; + } + + /** Get the parser class currently in use. */ + public String getParserClass() { + return parserClass; + } + + /** Set the SAXCatalogParser class for the given namespace/root + * element type. + */ + public void setCatalogParser(String namespaceURI, + String rootElement, + String parserClass) { + if (namespaceURI == null) { + namespaceMap.put(rootElement, parserClass); + } else { + namespaceMap.put("{"+namespaceURI+"}"+rootElement, parserClass); + } + } + + /** Get the SAXCatalogParser class for the given namespace/root + * element type. + */ + public String getCatalogParser(String namespaceURI, + String rootElement) { + if (namespaceURI == null) { + return (String) namespaceMap.get(rootElement); + } else { + return (String) namespaceMap.get("{"+namespaceURI+"}"+rootElement); + } + } + + /** The constructor */ + public SAXCatalogReader() { + parserFactory = null; + parserClass = null; + } + + /** The constructor */ + public SAXCatalogReader(SAXParserFactory parserFactory) { + this.parserFactory = parserFactory; + } + + /** The constructor */ + public SAXCatalogReader(String parserClass) { + this.parserClass = parserClass; + } + + /** + *Parse an XML Catalog file.
+ * + * @param catalog The catalog to which this catalog file belongs + * @param fileUrl The URL or filename of the catalog file to process + * + * @throws MalformedURLException Improper fileUrl + * @throws IOException Error reading catalog file + */ + public void readCatalog(Catalog catalog, String fileUrl) + throws MalformedURLException, IOException, + CatalogException { + + URL url = null; + + try { + url = new URL(fileUrl); + } catch (MalformedURLException e) { + url = new URL("file:///" + fileUrl); + } + + try { + URLConnection urlCon = url.openConnection(); + readCatalog(catalog, urlCon.getInputStream()); + } catch (FileNotFoundException e) { + Debug.message(1, "Failed to load catalog, file not found", + url.toString()); + } + } + + /** + *Parse an XML Catalog stream.
+ * + * @param catalog The catalog to which this catalog file belongs + * @param is The input stream from which the catalog will be read + * + * @throws MalformedURLException Improper fileUrl + * @throws IOException Error reading catalog file + * @throws CatalogException A Catalog exception + */ + public void readCatalog(Catalog catalog, InputStream is) + throws IOException, CatalogException { + + // Create an instance of the parser + if (parserFactory == null && parserClass == null) { + Debug.message(1, "Cannot read SAX catalog without a parser"); + throw new CatalogException(CatalogException.UNPARSEABLE); + } + + this.catalog = catalog; + + try { + if (parserFactory != null) { + SAXParser parser = parserFactory.newSAXParser(); + SAXParserHandler spHandler = new SAXParserHandler(); + spHandler.setContentHandler(this); + parser.parse(new InputSource(is), spHandler); + } else { + Parser parser = (Parser) Class.forName(parserClass).newInstance(); + parser.setDocumentHandler(this); + parser.parse(new InputSource(is)); + } + } catch (ClassNotFoundException cnfe) { + throw new CatalogException(CatalogException.UNPARSEABLE); + } catch (IllegalAccessException iae) { + throw new CatalogException(CatalogException.UNPARSEABLE); + } catch (InstantiationException ie) { + throw new CatalogException(CatalogException.UNPARSEABLE); + } catch (ParserConfigurationException pce) { + throw new CatalogException(CatalogException.UNKNOWN_FORMAT); + } catch (SAXException se) { + Exception e = se.getException(); + // FIXME: there must be a better way + UnknownHostException uhe = new UnknownHostException(); + FileNotFoundException fnfe = new FileNotFoundException(); + if (e != null) { + if (e.getClass() == uhe.getClass()) { + throw new CatalogException(CatalogException.PARSE_FAILED, + e.toString()); + } else if (e.getClass() == fnfe.getClass()) { + throw new CatalogException(CatalogException.PARSE_FAILED, + e.toString()); + } + } + throw new CatalogException(se); + } + } + + // ---------------------------------------------------------------------- + // Implement the SAX ContentHandler interface + + /**The SAX setDocumentLocator method. Does nothing.
The SAX startDocument method. Does nothing.
The SAX endDocument method. Does nothing.
The SAX startElement method.
The catalog parser is selected based on the namespace of the + * first element encountered in the catalog.
+ */ + public void startElement (String name, + AttributeList atts) + throws SAXException { + + if (abandonHope) { + return; + } + + if (saxParser == null) { + String prefix = ""; + if (name.indexOf(':') > 0) { + prefix = name.substring(0, name.indexOf(':')); + } + + String localName = name; + if (localName.indexOf(':') > 0) { + localName = localName.substring(localName.indexOf(':')+1); + } + + String namespaceURI = null; + if (prefix.equals("")) { + namespaceURI = atts.getValue("xmlns"); + } else { + namespaceURI = atts.getValue("xmlns:" + prefix); + } + + String saxParserClass = getCatalogParser(namespaceURI, + localName); + + if (saxParserClass == null) { + abandonHope = true; + if (namespaceURI == null) { + Debug.message(2, "No Catalog parser for " + name); + } else { + Debug.message(2, "No Catalog parser for " + + "{" + namespaceURI + "}" + + name); + } + return; + } + + try { + saxParser = (SAXCatalogParser) + Class.forName(saxParserClass).newInstance(); + + saxParser.setCatalog(catalog); + saxParser.startDocument(); + saxParser.startElement(name, atts); + } catch (ClassNotFoundException cnfe) { + saxParser = null; + abandonHope = true; + Debug.message(2, cnfe.toString()); + } catch (InstantiationException ie) { + saxParser = null; + abandonHope = true; + Debug.message(2, ie.toString()); + } catch (IllegalAccessException iae) { + saxParser = null; + abandonHope = true; + Debug.message(2, iae.toString()); + } catch (ClassCastException cce ) { + saxParser = null; + abandonHope = true; + Debug.message(2, cce.toString()); + } + } else { + saxParser.startElement(name, atts); + } + } + + /** + *The SAX2 startElement method.
The catalog parser is selected based on the namespace of the + * first element encountered in the catalog.
+ */ + public void startElement (String namespaceURI, + String localName, + String qName, + Attributes atts) + throws SAXException { + + if (abandonHope) { + return; + } + + if (saxParser == null) { + String saxParserClass = getCatalogParser(namespaceURI, + localName); + + if (saxParserClass == null) { + abandonHope = true; + if (namespaceURI == null) { + Debug.message(2, "No Catalog parser for " + localName); + } else { + Debug.message(2, "No Catalog parser for " + + "{" + namespaceURI + "}" + + localName); + } + return; + } + + try { + saxParser = (SAXCatalogParser) + Class.forName(saxParserClass).newInstance(); + + saxParser.setCatalog(catalog); + saxParser.startDocument(); + saxParser.startElement(namespaceURI, localName, qName, atts); + } catch (ClassNotFoundException cnfe) { + saxParser = null; + abandonHope = true; + Debug.message(2, cnfe.toString()); + } catch (InstantiationException ie) { + saxParser = null; + abandonHope = true; + Debug.message(2, ie.toString()); + } catch (IllegalAccessException iae) { + saxParser = null; + abandonHope = true; + Debug.message(2, iae.toString()); + } catch (ClassCastException cce ) { + saxParser = null; + abandonHope = true; + Debug.message(2, cce.toString()); + } + } else { + saxParser.startElement(namespaceURI, localName, qName, atts); + } + } + + /**The SAX endElement method. Does nothing.
The SAX2 endElement method. Does nothing.
The SAX characters method. Does nothing.
The SAX ignorableWhitespace method. Does nothing.
The SAX processingInstruction method. Does nothing.
The SAX startPrefixMapping method. Does nothing.
The SAX endPrefixMapping method. Does nothing.
The SAX skippedentity method. Does nothing.
An entity-resolving DefaultHandler
+ * + *This class provides a SAXParser DefaultHandler that performs + * entity resolution. + *
+ * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * @version 1.0 + */ +public class SAXParserHandler extends DefaultHandler { + private EntityResolver er = null; + private ContentHandler ch = null; + + public SAXParserHandler() { + super(); + } + + public void setEntityResolver(EntityResolver er) { + this.er = er; + } + + public void setContentHandler(ContentHandler ch) { + this.ch = ch; + } + + // Entity Resolver + public InputSource resolveEntity(String publicId, String systemId) + throws SAXException { + + if (er != null) { + try { + return er.resolveEntity(publicId, systemId); + } catch (IOException e) { + System.out.println("resolveEntity threw IOException!"); + return null; + } + } else { + return null; + } + } + + // Content Handler + public void characters(char[] ch, int start, int length) + throws SAXException { + if (this.ch != null) { + this.ch.characters(ch, start, length); + } + } + + public void endDocument() + throws SAXException { + if (ch != null) { + ch.endDocument(); + } + } + + public void endElement(String namespaceURI, String localName, String qName) + throws SAXException { + if (ch != null) { + ch.endElement(namespaceURI, localName, qName); + } + } + + public void endPrefixMapping(String prefix) + throws SAXException { + if (ch != null) { + ch.endPrefixMapping(prefix); + } + } + + public void ignorableWhitespace(char[] ch, int start, int length) + throws SAXException { + if (this.ch != null) { + this.ch.ignorableWhitespace(ch, start, length); + } + } + + public void processingInstruction(String target, String data) + throws SAXException { + if (ch != null) { + ch.processingInstruction(target, data); + } + } + + public void setDocumentLocator(Locator locator) { + if (ch != null) { + ch.setDocumentLocator(locator); + } + } + + public void skippedEntity(String name) + throws SAXException { + if (ch != null) { + ch.skippedEntity(name); + } + } + + public void startDocument() + throws SAXException { + if (ch != null) { + ch.startDocument(); + } + } + + public void startElement(String namespaceURI, String localName, + String qName, Attributes atts) + throws SAXException { + if (ch != null) { + ch.startElement(namespaceURI, localName, qName, atts); + } + } + + public void startPrefixMapping(String prefix, String uri) + throws SAXException { + if (ch != null) { + ch.startPrefixMapping(prefix, uri); + } + } +} diff --git a/java/src/org/apache/xml/resolver/readers/TR9401CatalogReader.java b/java/src/org/apache/xml/resolver/readers/TR9401CatalogReader.java new file mode 100644 index 0000000..67ad4ee --- /dev/null +++ b/java/src/org/apache/xml/resolver/readers/TR9401CatalogReader.java @@ -0,0 +1,179 @@ +// TR9401CatalogReader.java - Read OASIS Catalog files + +/* + * The Apache Software License, Version 1.1 + * + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Xalan" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 2001, International + * Business Machines Corporation., http://www.ibm.com. For more + * information on the Apache Software Foundation, please see + *Parses OASIS Open Catalog files.
+ * + *This class reads OASIS Open Catalog files, returning a stream + * of tokens.
+ * + *This code interrogates the following non-standard system properties:
+ * + *Sets the debug level. A value of 0 is assumed if the + * property is not set or is not a number.
Start parsing an OASIS TR9401 Open Catalog file. The file is
+ * actually read and parsed
+ * as needed by nextEntry.
In a TR9401 Catalog the 'DELEGATE' entry delegates public + * identifiers. There is no delegate entry for system identifiers + * or URIs.
+ * + * @param fileUrl The URL or filename of the catalog file to process + * + * @throws MalformedURLException Improper fileUrl + * @throws IOException Error reading catalog file + */ + public void readCatalog(Catalog catalog, InputStream is) + throws MalformedURLException, IOException { + + catfile = is; + + if (catfile == null) { + return; + } + + Vector unknownEntry = null; + + while (true) { + String token = nextToken(); + + if (token == null) { + if (unknownEntry != null) { + catalog.unknownEntry(unknownEntry); + unknownEntry = null; + } + catfile.close(); + catfile = null; + return; + } + + String entryToken = null; + if (caseSensitive) { + entryToken = token; + } else { + entryToken = token.toUpperCase(); + } + + if (entryToken.equals("DELEGATE")) { + entryToken = "DELEGATE_PUBLIC"; + } + + try { + int type = CatalogEntry.getEntryType(entryToken); + int numArgs = CatalogEntry.getEntryArgCount(type); + Vector args = new Vector(); + + if (unknownEntry != null) { + catalog.unknownEntry(unknownEntry); + unknownEntry = null; + } + + for (int count = 0; count < numArgs; count++) { + args.addElement(nextToken()); + } + + catalog.addEntry(new CatalogEntry(entryToken, args)); + } catch (CatalogException cex) { + if (cex.getExceptionType() == CatalogException.INVALID_ENTRY_TYPE) { + if (unknownEntry == null) { + unknownEntry = new Vector(); + } + unknownEntry.addElement(token); + } else if (cex.getExceptionType() == CatalogException.INVALID_ENTRY) { + Debug.message(1, "Invalid catalog entry", token); + unknownEntry = null; + } + } + } + } +} diff --git a/java/src/org/apache/xml/resolver/readers/TextCatalogReader.java b/java/src/org/apache/xml/resolver/readers/TextCatalogReader.java new file mode 100644 index 0000000..8f69f82 --- /dev/null +++ b/java/src/org/apache/xml/resolver/readers/TextCatalogReader.java @@ -0,0 +1,328 @@ +// TextCatalogReader.java - Read text/plain Catalog files + +/* + * The Apache Software License, Version 1.1 + * + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Xalan" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 2001, International + * Business Machines Corporation., http://www.ibm.com. For more + * information on the Apache Software Foundation, please see + *Parses plain text Catalog files.
+ * + *This class reads plain text Open Catalog files.
+ * + * @see Catalog + * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * + * @version 1.0 + */ +public class TextCatalogReader implements CatalogReader { + /** The input stream used to read the catalog */ + protected InputStream catfile = null; + + /** + * Character lookahead stack. Reading a catalog sometimes requires + * up to two characters of lookahead. + */ + protected int[] stack = new int[3]; + + /** + * Token stack. Recognizing an unexpected catalog entry requires + * the ability to "push back" a token. + */ + protected Stack tokenStack = new Stack(); + + /** The current position on the lookahead stack */ + protected int top = -1; + + /** Are keywords in the catalog case sensitive? */ + protected boolean caseSensitive = false; + + /** + *Construct a CatalogReader object.
+ */ + public TextCatalogReader() { } + + public void setCaseSensitive(boolean isCaseSensitive) { + caseSensitive = isCaseSensitive; + } + + public boolean getCaseSensitive() { + return caseSensitive; + } + + /** + *Start parsing a text catalog file. The file is
+ * actually read and parsed
+ * as needed by nextEntry.
The destructor.
+ * + *Makes sure the catalog file is closed.
+ */ + protected void finalize() { + if (catfile != null) { + try { + catfile.close(); + } catch (IOException e) { + // whatever... + } + } + catfile = null; + } + + // ----------------------------------------------------------------- + + /** + *Return the next token in the catalog file.
+ * + * @return The Catalog file token from the input stream. + * @throws IOException If an error occurs reading from the stream. + */ + protected String nextToken() throws IOException { + String token = ""; + int ch, nextch; + + if (!tokenStack.empty()) { + return (String) tokenStack.pop(); + } + + // Skip over leading whitespace and comments + while (true) { + // skip leading whitespace + ch = catfile.read(); + while (ch <= ' ') { // all ctrls are whitespace + ch = catfile.read(); + if (ch < 0) { + return null; + } + } + + // now 'ch' is the current char from the file + nextch = catfile.read(); + if (nextch < 0) { + return null; + } + + if (ch == '-' && nextch == '-') { + // we've found a comment, skip it... + ch = ' '; + nextch = nextChar(); + while (ch != '-' || nextch != '-') { + ch = nextch; + nextch = nextChar(); + } + + // Ok, we've found the end of the comment, + // loop back to the top and start again... + } else { + stack[++top] = nextch; + stack[++top] = ch; + break; + } + } + + ch = nextChar(); + if (ch == '"' || ch == '\'') { + int quote = ch; + while ((ch = nextChar()) != quote) { + char[] chararr = new char[1]; + chararr[0] = (char) ch; + String s = new String(chararr); + token = token.concat(s); + } + return token; + } else { + // return the next whitespace or comment delimited + // string + while (ch > ' ') { + nextch = nextChar(); + if (ch == '-' && nextch == '-') { + stack[++top] = ch; + stack[++top] = nextch; + return token; + } else { + char[] chararr = new char[1]; + chararr[0] = (char) ch; + String s = new String(chararr); + token = token.concat(s); + ch = nextch; + } + } + return token; + } + } + + /** + *Return the next logical character from the input stream.
+ * + * @return The next (logical) character from the input stream. The + * character may be buffered from a previous lookahead. + * + * @throws IOException If an error occurs reading from the stream. + */ + protected int nextChar() throws IOException { + if (top < 0) { + return catfile.read(); + } else { + return stack[top--]; + } + } +} diff --git a/java/src/org/apache/xml/resolver/readers/XCatalogReader.java b/java/src/org/apache/xml/resolver/readers/XCatalogReader.java new file mode 100644 index 0000000..e396b37 --- /dev/null +++ b/java/src/org/apache/xml/resolver/readers/XCatalogReader.java @@ -0,0 +1,234 @@ +// XCatalogReader.java - Read XML Catalog files + +/* + * The Apache Software License, Version 1.1 + * + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Xalan" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 2001, International + * Business Machines Corporation., http://www.ibm.com. For more + * information on the Apache Software Foundation, please see + *Parse "xcatalog" XML Catalog files, this is the XML Catalog format + * developed by John Cowan and supported by Apache.
+ * + * @see Catalog + * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * + * @version 1.0 + */ +public class XCatalogReader extends SAXCatalogReader implements SAXCatalogParser { + /** The catalog object needs to be stored by the object so that + * SAX callbacks can use it. + */ + protected Catalog catalog = null; + + /** Set the current catalog. */ + public void setCatalog (Catalog catalog) { + this.catalog = catalog; + } + + /** Get the current catalog. */ + public Catalog getCatalog () { + return catalog; + } + + /** The constructor */ + public XCatalogReader(SAXParserFactory parserFactory) { + super(parserFactory); + } + + // ---------------------------------------------------------------------- + // Implement the SAX DocumentHandler interface + + /**The SAX setDocumentLocator method does nothing.
The SAX startDocument method does nothing.
The SAX endDocument method does nothing.
The SAX startElement method recognizes elements
+ * from the plain catalog format and instantiates CatalogEntry
+ * objects for them.
The SAX endElement method does nothing.
The SAX characters method does nothing.
The SAX ignorableWhitespace method does nothing.
The SAX processingInstruction method does nothing.
Apache XML Commons Resolver Readers package
+ +This package provides the set of classes that read catalog files +and extract catalog entries from them.
+ + + diff --git a/java/src/org/apache/xml/resolver/tools/CatalogResolver.java b/java/src/org/apache/xml/resolver/tools/CatalogResolver.java new file mode 100644 index 0000000..5934f43 --- /dev/null +++ b/java/src/org/apache/xml/resolver/tools/CatalogResolver.java @@ -0,0 +1,371 @@ +// CatalogResolver.java - A SAX EntityResolver/JAXP URI Resolver + +/* + * The Apache Software License, Version 1.1 + * + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Xalan" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 2001, International + * Business Machines Corporation., http://www.ibm.com. For more + * information on the Apache Software Foundation, please see + *A SAX EntityResolver/JAXP URIResolver that uses catalogs.
+ * + *This class implements both a SAX EntityResolver and a JAXP URIResolver. + *
+ * + *This resolver understands OASIS TR9401 catalogs, XCatalogs, and the + * current working draft of the OASIS Entity Resolution Technical + * Committee specification.
+ * + * @see Catalog + * @see org.xml.sax.EntityResolver + * @see javax.xml.transform.URIResolver + * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * + * @version 1.0 + */ +public class CatalogResolver implements EntityResolver, URIResolver { + /** Make the parser Namespace aware? */ + public boolean namespaceAware = true; + + /** Make the parser validating? */ + public boolean validating = false; + + /** The underlying catalog */ + private static Catalog staticCatalog = null; + private Catalog catalog = null; + + /** Constructor */ + public CatalogResolver() { + initializeCatalogs(false); + } + + /** Constructor */ + public CatalogResolver(boolean privateCatalog) { + initializeCatalogs(privateCatalog); + } + + /** Initialize catalog */ + private void initializeCatalogs(boolean privateCatalog) { + catalog = staticCatalog; + + if (privateCatalog || catalog == null) { + try { + String catalogClassName = CatalogManager.catalogClassName(); + 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.setupReaders(); + + if (!privateCatalog) { + catalog.loadSystemCatalogs(); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + if (!privateCatalog && catalog != null && CatalogManager.staticCatalog()) { + staticCatalog = catalog; + } + } + + /** Return the underlying catalog */ + public Catalog getCatalog() { + return catalog; + } + + /** + *Implements the guts of the resolveEntity method
+ * for the SAX interface.
Presented with an optional public identifier and a system + * identifier, this function attempts to locate a mapping in the + * catalogs.
+ * + *If such a mapping is found, it is returned. If no mapping is + * found, null is returned.
+ * + * @param publicId The public identifier for the entity in question. + * This may be null. + * + * @param systemId The system identifier for the entity in question. + * XML requires a system identifier on all external entities, so this + * value is always specified. + * + * @return The resolved identifier (a URI reference). + */ + public String getResolvedEntity (String publicId, String systemId) { + String resolved = null; + + if (catalog == null) { + Debug.message(1, "Catalog resolution attempted with null catalog; ignored"); + return null; + } + + if (systemId != null) { + try { + resolved = catalog.resolveSystem(systemId); + } catch (MalformedURLException me) { + Debug.message(1, "Malformed URL exception trying to resolve", + publicId); + resolved = null; + } catch (IOException ie) { + Debug.message(1, "I/O exception trying to resolve", publicId); + resolved = null; + } + } + + if (resolved == null) { + if (publicId != null) { + try { + resolved = catalog.resolvePublic(publicId, systemId); + } catch (MalformedURLException me) { + Debug.message(1, "Malformed URL exception trying to resolve", + publicId); + } catch (IOException ie) { + Debug.message(1, "I/O exception trying to resolve", publicId); + } + } + + if (resolved != null) { + Debug.message(2, "Resolved public", publicId, resolved); + } + } else { + Debug.message(2, "Resolved system", systemId, resolved); + } + + return resolved; + } + + /** + *Implements the resolveEntity method
+ * for the SAX interface.
Presented with an optional public identifier and a system + * identifier, this function attempts to locate a mapping in the + * catalogs.
+ * + *If such a mapping is found, the resolver attempts to open + * the mapped value as an InputSource and return it. Exceptions are + * ignored and null is returned if the mapped value cannot be opened + * as an input source.
+ * + *If no mapping is found (or an error occurs attempting to open + * the mapped value as an input source), null is returned and the system + * will use the specified system identifier as if no entityResolver + * was specified.
+ * + * @param publicId The public identifier for the entity in question. + * This may be null. + * + * @param systemId The system identifier for the entity in question. + * XML requires a system identifier on all external entities, so this + * value is always specified. + * + * @return An InputSource for the mapped identifier, or null. + */ + public InputSource resolveEntity (String publicId, String systemId) { + String resolved = getResolvedEntity(publicId, systemId); + + if (resolved != null) { + try { + InputSource iSource = new InputSource(resolved); + iSource.setPublicId(publicId); + + // Ideally this method would not attempt to open the + // InputStream, but there is a bug (in Xerces, at least) + // that causes the parser to mistakenly open the wrong + // system identifier if the returned InputSource does + // not have a byteStream. + // + // It could be argued that we still shouldn't do this here, + // but since the purpose of calling the entityResolver is + // almost certainly to open the input stream, it seems to + // do little harm. + // + URL url = new URL(resolved); + InputStream iStream = url.openStream(); + iSource.setByteStream(iStream); + + return iSource; + } catch (Exception e) { + Debug.message(1, "Failed to create InputSource", resolved); + return null; + } + } + + return null; + } + + /** JAXP URIResolver API */ + 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 = catalog.resolveURI(href); + } catch (Exception e) { + // nop; + } + + 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 (java.net.MalformedURLException mue) { + // try to make an absolute URI from the current base + String absBase = makeAbsolute(base); + if (!absBase.equals(base)) { + // don't bother if the absBase isn't different! + return resolve(href, absBase); + } else { + throw new TransformerException("Malformed URL " + + href + "(base " + base + ")", + mue); + } + } + } + + // if (!href.equals(result)) { + Debug.message(2, "Resolved URI", href, result); + // } + + SAXSource source = new SAXSource(); + source.setInputSource(new InputSource(result)); + return source; + } + + /** Attempt to construct an absolute URI */ + private String makeAbsolute(String uri) { + if (uri == null) { + uri = ""; + } + + try { + URL url = new URL(uri); + return url.toString(); + } catch (MalformedURLException mue) { + String dir = System.getProperty("user.dir"); + String file = ""; + + if (dir.endsWith("/")) { + file = "file://" + dir + uri; + } else { + file = "file://" + dir + "/" + uri; + } + + try { + URL fileURL = new URL(file); + return fileURL.toString(); + } catch (MalformedURLException mue2) { + // bail + return uri; + } + } + } +} diff --git a/java/src/org/apache/xml/resolver/tools/ResolvingParser.java b/java/src/org/apache/xml/resolver/tools/ResolvingParser.java new file mode 100644 index 0000000..64661b0 --- /dev/null +++ b/java/src/org/apache/xml/resolver/tools/ResolvingParser.java @@ -0,0 +1,459 @@ +// ResolvingParser.java - An interface for reading catalog files + +/* + * The Apache Software License, Version 1.1 + * + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Xalan" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 2001, International + * Business Machines Corporation., http://www.ibm.com. For more + * information on the Apache Software Foundation, please see + *A SAX Parser that performs catalog-based entity resolution.
+ * + *This class implements a SAX Parser that performs entity resolution + * using the CatalogResolver. The actual, underlying parser is obtained + * from a SAXParserFactory.
+ * + * + * @deprecated This interface has been replaced by the + * {@link org.apache.xml.resolver.tools.ResolvingXMLReader} for SAX2. + * @see CatalogResolver + * @see org.xml.sax.Parser + * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * + * @version 1.0 + */ +public class ResolvingParser + implements Parser, DTDHandler, DocumentHandler, EntityResolver { + /** Make the parser Namespace aware? */ + public static boolean namespaceAware = true; + + /** Make the parser validating? */ + public static boolean validating = false; + + /** Suppress explanatory message? + * + * @see #parse(InputSource) + */ + public static boolean suppressExplanation = false; + + /** The underlying parser */ + private SAXParser saxParser = null; + + /** The underlying reader */ + private Parser parser = null; + + /** The underlying DocumentHandler */ + private DocumentHandler documentHandler = null; + + /** The underlying DTDHandler */ + private DTDHandler dtdHandler = null; + + /** The underlying catalog resolver */ + private CatalogResolver catalogResolver = new CatalogResolver(); + + /** A separate resolver for oasis-xml-pi catalogs */ + private CatalogResolver piCatalogResolver = null; + + /** Are we in the prolog? Is an oasis-xml-catalog PI valid now? */ + private boolean allowXMLCatalogPI = false; + + /** Has an oasis-xml-catalog PI been seen? */ + private boolean oasisXMLCatalogPI = false; + + /** The base URI of the input document, if known. */ + private URL baseURL = null; + + /** Constructor */ + public ResolvingParser() { + SAXParserFactory spf = SAXParserFactory.newInstance(); + spf.setNamespaceAware(namespaceAware); + spf.setValidating(validating); + + try { + saxParser = spf.newSAXParser(); + parser = saxParser.getParser(); + documentHandler = null; + dtdHandler = null; + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + /** Return the Catalog being used */ + public Catalog getCatalog() { + return catalogResolver.getCatalog(); + } + + /** + *SAX Parser API
+ * + *Note that the JAXP 1.1ea2 parser crashes with an InternalError if + * it encounters a system identifier that appears to be a relative URI + * that begins with a slash. For example, the declaration:
+ * + *+ * <!DOCTYPE book SYSTEM "/path/to/dtd/on/my/system/docbookx.dtd"> + *+ * + *
would cause such an error. As a convenience, this method catches + * that error and prints an explanation. (Unfortunately, it's not possible + * to identify the particular system identifier that causes the problem.) + *
+ * + *The underlying error is forwarded after printing the explanatory
+ * message. The message is only every printed once and if
+ * suppressExplanation is set to false before
+ * parsing, it will never be printed.
SAX Parser API
+ * + *The purpose of this class is to implement an entity resolver. + * Attempting to set a different one is pointless (and ignored).
+ */ + public void setEntityResolver(EntityResolver resolver) { + // nop + } + + /** SAX Parser API */ + public void setErrorHandler(ErrorHandler handler) { + parser.setErrorHandler(handler); + } + + /** SAX Parser API */ + public void setLocale(Locale locale) throws SAXException { + parser.setLocale(locale); + } + + /** SAX DocumentHandler API */ + public void characters(char[] ch, int start, int length) + throws SAXException { + if (documentHandler != null) { + documentHandler.characters(ch,start,length); + } + } + + /** SAX DocumentHandler API */ + public void endDocument() throws SAXException { + if (documentHandler != null) { + documentHandler.endDocument(); + } + } + + /** SAX DocumentHandler API */ + public void endElement(String name) throws SAXException { + if (documentHandler != null) { + documentHandler.endElement(name); + } + } + + /** SAX DocumentHandler API */ + public void ignorableWhitespace(char[] ch, int start, int length) + throws SAXException { + if (documentHandler != null) { + documentHandler.ignorableWhitespace(ch,start,length); + } + } + + /** SAX DocumentHandler API */ + public void processingInstruction(String target, String pidata) + throws SAXException { + + if (target.equals("oasis-xml-catalog")) { + URL catalog = null; + String data = pidata; + + int pos = data.indexOf("catalog="); + if (pos >= 0) { + data = data.substring(pos+8); + if (data.length() > 1) { + String quote = data.substring(0,1); + data = data.substring(1); + pos = data.indexOf(quote); + if (pos >= 0) { + data = data.substring(0, pos); + try { + if (baseURL != null) { + catalog = new URL(baseURL, data); + } else { + catalog = new URL(data); + } + } catch (MalformedURLException mue) { + // nevermind + } + } + } + } + + if (allowXMLCatalogPI) { + if (CatalogManager.allowOasisXMLCatalogPI()) { + Debug.message(4,"oasis-xml-catalog PI", pidata); + + if (catalog != null) { + try { + Debug.message(4,"oasis-xml-catalog", catalog.toString()); + oasisXMLCatalogPI = true; + + if (piCatalogResolver == null) { + piCatalogResolver = new CatalogResolver(true); + } + + piCatalogResolver.getCatalog().parseCatalog(catalog.toString()); + } catch (Exception e) { + Debug.message(3, "Exception parsing oasis-xml-catalog: " + + catalog.toString()); + } + } else { + Debug.message(3, "PI oasis-xml-catalog unparseable: " + pidata); + } + } else { + Debug.message(4,"PI oasis-xml-catalog ignored: " + pidata); + } + } else { + Debug.message(3, "PI oasis-xml-catalog occurred in an invalid place: " + + pidata); + } + } else { + if (documentHandler != null) { + documentHandler.processingInstruction(target, pidata); + } + } + } + + /** SAX DocumentHandler API */ + public void setDocumentLocator(Locator locator) { + if (documentHandler != null) { + documentHandler.setDocumentLocator(locator); + } + } + + /** SAX DocumentHandler API */ + public void startDocument() throws SAXException { + if (documentHandler != null) { + documentHandler.startDocument(); + } + } + + /** SAX DocumentHandler API */ + public void startElement(String name, AttributeList atts) + throws SAXException { + allowXMLCatalogPI = false; + if (documentHandler != null) { + documentHandler.startElement(name,atts); + } + } + + /** SAX DTDHandler API */ + public void notationDecl (String name, String publicId, String systemId) + throws SAXException { + allowXMLCatalogPI = false; + if (dtdHandler != null) { + dtdHandler.notationDecl(name,publicId,systemId); + } + } + + /** SAX DTDHandler API */ + public void unparsedEntityDecl (String name, + String publicId, + String systemId, + String notationName) + throws SAXException { + allowXMLCatalogPI = false; + if (dtdHandler != null) { + dtdHandler.unparsedEntityDecl (name, publicId, systemId, notationName); + } + } + + /** + *Implements the resolveEntity method
+ * for the SAX interface, using an underlying CatalogResolver
+ * to do the real work.
A SAX XMLFilter that performs catalog-based entity resolution.
+ * + *This class implements a SAX XMLFilter that performs entity resolution + * using the CatalogResolver. The actual, underlying parser is obtained + * from a SAXParserFactory.
+ * + * + * @see CatalogResolver + * @see org.xml.sax.XMLFilter + * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * + * @version 1.0 + */ +public class ResolvingXMLFilter extends XMLFilterImpl { + /** Suppress explanatory message? + * + * @see #parse(InputSource) + */ + public static boolean suppressExplanation = false; + + /** The underlying catalog resolver */ + private CatalogResolver catalogResolver = new CatalogResolver(); + + /** A separate resolver for oasis-xml-pi catalogs */ + private CatalogResolver piCatalogResolver = null; + + /** Are we in the prolog? Is an oasis-xml-catalog PI valid now? */ + private boolean allowXMLCatalogPI = false; + + /** Has an oasis-xml-catalog PI been seen? */ + private boolean oasisXMLCatalogPI = false; + + /** The base URI of the input document, if known. */ + private URL baseURL = null; + + /** Construct an empty XML Filter with no parent */ + public ResolvingXMLFilter() { + super(); + } + + /** Construct an XML filter with the specified parent */ + public ResolvingXMLFilter(XMLReader parent) { + super(parent); + } + + /** + *Provide accessto the underlying Catalog
+ */ + public Catalog getCatalog() { + return catalogResolver.getCatalog(); + } + + /** + *SAX XMLReader API
+ * + *Note that the JAXP 1.1ea2 parser crashes with an InternalError if + * it encounters a system identifier that appears to be a relative URI + * that begins with a slash. For example, the declaration:
+ * + *+ * <!DOCTYPE book SYSTEM "/path/to/dtd/on/my/system/docbookx.dtd"> + *+ * + *
would cause such an error. As a convenience, this method catches + * that error and prints an explanation. (Unfortunately, it's not possible + * to identify the particular system identifier that causes the problem.) + *
+ * + *The underlying error is forwarded after printing the explanatory
+ * message. The message is only every printed once and if
+ * suppressExplanation is set to false before
+ * parsing, it will never be printed.
Implements the resolveEntity method
+ * for the SAX interface, using an underlying CatalogResolver
+ * to do the real work.
Captured here only to detect the end of the prolog so that + * we can ignore subsequent oasis-xml-catalog PIs. Otherwise + * the events are just passed through.
+ */ + public void notationDecl (String name, String publicId, String systemId) + throws SAXException { + allowXMLCatalogPI = false; + super.notationDecl(name,publicId,systemId); + } + + /** SAX DTDHandler API + * + *Captured here only to detect the end of the prolog so that + * we can ignore subsequent oasis-xml-catalog PIs. Otherwise + * the events are just passed through.
+ */ + public void unparsedEntityDecl (String name, + String publicId, + String systemId, + String notationName) + throws SAXException { + allowXMLCatalogPI = false; + super.unparsedEntityDecl (name, publicId, systemId, notationName); + } + + /** SAX ContentHandler API + * + *Captured here only to detect the end of the prolog so that + * we can ignore subsequent oasis-xml-catalog PIs. Otherwise + * the events are just passed through.
+ */ + public void startElement (String uri, String localName, String qName, + Attributes atts) + throws SAXException { + allowXMLCatalogPI = false; + super.startElement(uri,localName,qName,atts); + } + + /** SAX ContentHandler API + * + *Detect and use the oasis-xml-catalog PI if it occurs.
+ */ + public void processingInstruction(String target, String pidata) + throws SAXException { + if (target.equals("oasis-xml-catalog")) { + URL catalog = null; + String data = pidata; + + int pos = data.indexOf("catalog="); + if (pos >= 0) { + data = data.substring(pos+8); + if (data.length() > 1) { + String quote = data.substring(0,1); + data = data.substring(1); + pos = data.indexOf(quote); + if (pos >= 0) { + data = data.substring(0, pos); + try { + if (baseURL != null) { + catalog = new URL(baseURL, data); + } else { + catalog = new URL(data); + } + } catch (MalformedURLException mue) { + // nevermind + } + } + } + } + + if (allowXMLCatalogPI) { + if (CatalogManager.allowOasisXMLCatalogPI()) { + Debug.message(4,"oasis-xml-catalog PI", pidata); + + if (catalog != null) { + try { + Debug.message(4,"oasis-xml-catalog", catalog.toString()); + oasisXMLCatalogPI = true; + + if (piCatalogResolver == null) { + piCatalogResolver = new CatalogResolver(true); + } + + piCatalogResolver.getCatalog().parseCatalog(catalog.toString()); + } catch (Exception e) { + Debug.message(3, "Exception parsing oasis-xml-catalog: " + + catalog.toString()); + } + } else { + Debug.message(3, "PI oasis-xml-catalog unparseable: " + pidata); + } + } else { + Debug.message(4,"PI oasis-xml-catalog ignored: " + pidata); + } + } else { + Debug.message(3, "PI oasis-xml-catalog occurred in an invalid place: " + + pidata); + } + } else { + super.processingInstruction(target, pidata); + } + } + + /** Save the base URI of the document being parsed. */ + private void setupBaseURI(String systemId) { + String userdir = System.getProperty("user.dir"); + URL cwd = null; + + userdir.replace('\\', '/'); + try { + cwd = new URL("file:///" + userdir + "/basename"); + } catch (MalformedURLException mue) { + cwd = null; + } + + try { + baseURL = new URL(systemId); + } catch (MalformedURLException mue) { + if (cwd != null) { + try { + baseURL = new URL(cwd, systemId); + } catch (MalformedURLException mue2) { + // give up + baseURL = null; + } + } else { + // give up + baseURL = null; + } + } + } + + /** Provide one possible explanation for an InternalError */ + private void explain(String systemId) { + if (!suppressExplanation) { + System.out.println("XMLReader probably encountered bad URI in " + systemId); + System.out.println("For example, replace '/some/uri' with 'file:/some/uri'."); + } + suppressExplanation = true; + } +} + diff --git a/java/src/org/apache/xml/resolver/tools/ResolvingXMLReader.java b/java/src/org/apache/xml/resolver/tools/ResolvingXMLReader.java new file mode 100644 index 0000000..b613bd3 --- /dev/null +++ b/java/src/org/apache/xml/resolver/tools/ResolvingXMLReader.java @@ -0,0 +1,107 @@ +// ResolvingXMLReader.java - An XMLReader that performs catalog resolution + +/* + * The Apache Software License, Version 1.1 + * + * + * Copyright (c) 2001 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Xalan" and "Apache Software Foundation" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation and was + * originally based on software copyright (c) 2001, International + * Business Machines Corporation., http://www.ibm.com. For more + * information on the Apache Software Foundation, please see + *A SAX XMLReader that performs catalog-based entity resolution.
+ * + *This class implements a SAX XMLReader that performs entity resolution + * using the CatalogResolver. The actual, underlying parser is obtained + * from a SAXParserFactory.
+ * + * + * @see CatalogResolver + * @see org.xml.sax.XMLReader + * + * @author Norman Walsh + * Norman.Walsh@Sun.COM + * + * @version 1.0 + */ +public class ResolvingXMLReader extends ResolvingXMLFilter { + /** + *Construct a new reader from the JAXP factory
+ * + *In order to do its job, a ResolvingXMLReader must in fact be + * a filter. So the only difference between this code and the filter + * code is that a zero-argument constructor builds a new reader.
+ */ + public ResolvingXMLReader() { + super(); + SAXParserFactory spf = SAXParserFactory.newInstance(); + try { + SAXParser parser = spf.newSAXParser(); + setParent(parser.getXMLReader()); + } catch (Exception ex) { + ex.printStackTrace(); + } + } +} + diff --git a/java/src/org/apache/xml/resolver/tools/package.html b/java/src/org/apache/xml/resolver/tools/package.html new file mode 100644 index 0000000..82eb95e --- /dev/null +++ b/java/src/org/apache/xml/resolver/tools/package.html @@ -0,0 +1,47 @@ + + +Apache XML Commons Resolver Tools package
+ +This package implements several tool classes for catalog-based +entity resolution:
+ +org.apache.xml.resolver.tools.CatalogResolverorg.apache.xml.resolver.tools.ResolvingXMLReaderorg.apache.xml.resolver.tools.ResolvingParserTypically, these classes can be used in other applications. For example, +running the Saxon +XSLT processor with the arguments:
+ ++ -x org.apache.xml.resolver.tools.ResolvingXMLReader + -y org.apache.xml.resolver.tools.ResolvingXMLReader + -r org.apache.xml.resolver.tools.CatalogResolver ++ +
will provide catalog-based entity resolution for all of the entities +and stylesheets that you use. (The CatalogResolver can only be used +with Saxon 6.1 or later.)
+ +In order to use these classes, you must have the JAXP 1.1 jar files +in your CLASSPATH.
+ + +