| /* |
| * The Apache Software License, Version 1.1 |
| * |
| * |
| * Copyright (c) 1999,2000 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 "Xerces" 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) 1999, International |
| * Business Machines, Inc., http://www.apache.org. For more |
| * information on the Apache Software Foundation, please see |
| * <http://www.apache.org/>. |
| */ |
| |
| package org.apache.xerces.readers; |
| |
| import org.apache.xerces.framework.XMLParser; |
| import org.apache.xerces.parsers.SAXParser; |
| import org.apache.xerces.utils.StringPool; |
| |
| import org.xml.sax.AttributeList; |
| import org.xml.sax.DocumentHandler; |
| import org.xml.sax.EntityResolver; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.SAXException; |
| |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.IOException; |
| import java.util.Enumeration; |
| import java.util.Hashtable; |
| import java.util.Vector; |
| |
| /** |
| * This catalog supports the XCatalog proposal draft 0.2 posted |
| * to the xml-dev mailing list by |
| * <a href="mailto:cowan@locke.ccil.org">John Cowan</a>. XCatalog |
| * is an XML representation of the SGML Open TR9401:1997 catalog |
| * format. The current proposal supports public identifier maps, |
| * system identifier aliases, and public identifier prefix |
| * delegates. Refer to the XCatalog DTD for the full specification |
| * of this catalog format at |
| * <a href="http://www.ccil.org/~cowan/XML/XCatalog.html">http://www.ccil.org/~cowan/XML/XCatalog.html</a>. |
| * <p> |
| * In order to use XCatalogs, you must write the catalog files |
| * with the following restrictions: |
| * <ul> |
| * <li>You must follow the XCatalog grammar. |
| * <li>You must specify the <tt><!DOCTYPE></tt> line with |
| * the <tt>PUBLIC</tt> specified as "-//DTD XCatalog//EN" or |
| * make sure that the system identifier is able to locate the |
| * XCatalog 0.2 DTD (which is included in the Jar file containing |
| * the org.apache.xerces.readers.XCatalog class). |
| * For example: |
| * <pre> |
| * <!DOCTYPE XCatalog PUBLIC "-//DTD XCatalog//EN" "org/apache/xerces/readers/xcatalog.dtd"> |
| * </pre> |
| * <li>The enclosing <tt><XCatalog></tt> document root |
| * element is <b>not</b> optional -- it <b>must</b> be specified. |
| * <li>The <tt>Version</tt> attribute of the <tt><XCatalog></tt> |
| * has been modified from '<tt><i>#FIXED "1.0"</i></tt>' to |
| * '<tt><i>(0.1|0.2) "0.2"</i></tt>'. |
| * </ul> |
| * <p> |
| * To use this catalog in a parser, set an XCatalog instance as the |
| * parser's <tt>EntityResolver</tt>. For example: |
| * <pre> |
| * XMLParser parser = new AnyParser(); |
| * Catalog catalog = <font color="blue">new XCatalog()</font>; |
| * <font color="blue">parser.getEntityHandler().setEntityResolver(catalog);</font> |
| * </pre> |
| * <p> |
| * Once installed, catalog files that conform to the XCatalog grammar |
| * can be appended to the catalog by calling the <tt>loadCatalog</tt> |
| * method on the parser or the catalog instance. The following example |
| * loads the contents of two catalog files: |
| * <pre> |
| * parser.loadCatalog(new InputSource("catalogs/cat1.xml")); |
| * parser.loadCatalog(new InputSource("http://host/catalogs/cat2.xml")); |
| * </pre> |
| * <p> |
| * <b>Limitations:</b> The following are the current limitations |
| * of this XCatalog implementation: |
| * <ul> |
| * <li>No error checking is done to avoid circular <tt>Delegate</tt> |
| * or <tt>Extend</tt> references. Do not specify a combination of |
| * catalog files that reference each other. |
| * </ul> |
| * |
| * @author Andy Clark, IBM |
| * @version $Id$ |
| */ |
| public class XCatalog |
| extends XMLCatalogHandler |
| { |
| |
| // |
| // Constants |
| // |
| |
| // public |
| |
| /** XCatalog public identifier string ("-//DTD XCatalog//EN"). */ |
| public static final String XCATALOG_DTD_PUBLICID = "-//DTD XCatalog//EN"; |
| |
| // "default" |
| |
| /** XCatalog DTD resource name ("xcatalog.dtd"). */ |
| static final String DTD = "xcatalog.dtd"; |
| |
| /** XCatalog element name ("XCatalog"). */ |
| static final String XCATALOG = "XCatalog"; |
| |
| /** Map element name ("Map"). */ |
| static final String MAP = "Map"; |
| |
| /** PublicID attribute name ("PublicID"). */ |
| static final String PUBLICID = "PublicID"; |
| |
| /** HRef attribute name ("HRef"). */ |
| static final String HREF = "HRef"; |
| |
| /** Delegate element name ("Delegate"). */ |
| static final String DELEGATE = "Delegate"; |
| |
| /** Extend element name ("Extend"). */ |
| static final String EXTEND = "Extend"; |
| |
| /** Base element name ("Base"). */ |
| static final String BASE = "Base"; |
| |
| /** Remap element name ("Remap"). */ |
| static final String REMAP = "Remap"; |
| |
| /** SystemID attribute name ("SystemID"). */ |
| static final String SYSTEMID = "SystemID"; |
| |
| // private |
| |
| /** Set to true and recompile to include debugging code in class. */ |
| private static final boolean DEBUG = false; |
| |
| // |
| // Data |
| // |
| |
| /** Delegates. */ |
| private Hashtable delegate = new Hashtable(); |
| |
| /** Delegates ordering. */ |
| private Vector delegateOrder = new Vector(); |
| |
| // |
| // Constructors |
| // |
| |
| /** |
| * Constructs an XCatalog instance. |
| */ |
| public XCatalog() { |
| } |
| |
| // |
| // Catalog methods |
| // |
| |
| /** |
| * Loads the catalog stream specified by the given input source and |
| * appends the contents to the catalog. |
| * |
| * @param source The catalog source. |
| * |
| * @exception org.xml.sax.SAXException Throws exception on SAX error. |
| * @exception java.io.IOException Throws exception on i/o error. |
| */ |
| public void loadCatalog(InputSource source) |
| throws SAXException, IOException |
| { |
| |
| new Parser(source); |
| |
| /*** |
| if (DEBUG) { |
| print(""); |
| } |
| /***/ |
| |
| } // loadCatalog(InputSource) |
| |
| /*** |
| void print(String indent) { |
| System.out.println(indent+"# "+this); |
| Enumeration maps = getMapKeys(); |
| while (maps.hasMoreElements()) { |
| String key = (String)maps.nextElement(); |
| String value = getMapValue(key); |
| System.out.println(indent+"MAP \""+key+"\" -> \""+value+"\""); |
| } |
| Enumeration delegates = getDelegateKeys(); |
| while (delegates.hasMoreElements()) { |
| String key = (String)delegates.nextElement(); |
| XCatalog value = getDelegateValue(key); |
| System.out.println(indent+"DELEGATE \""+key+"\" -> "+value); |
| value.print(indent+" "); |
| } |
| Enumeration remaps = getRemapKeys(); |
| while (remaps.hasMoreElements()) { |
| String key = (String)remaps.nextElement(); |
| String value = getRemapValue(key); |
| System.out.println(indent+"REMAP \""+key+"\" -> \""+value+"\""); |
| } |
| } |
| /***/ |
| |
| // |
| // EntityResolver methods |
| // |
| |
| /** |
| * Resolves external entities. |
| * |
| * @param publicId The public identifier used for entity resolution. |
| * @param systemId If the publicId is not null, this systemId is |
| * to be considered the default system identifier; |
| * else a system identifier alias mapping is |
| * requested. |
| * |
| * @return Returns the input source of the resolved entity or null |
| * if no resolution is possible. |
| * |
| * @exception org.xml.sax.SAXException Exception thrown on SAX error. |
| * @exception java.io.IOException Exception thrown on i/o error. |
| */ |
| public InputSource resolveEntity(String publicId, String systemId) |
| throws SAXException, IOException |
| { |
| |
| if (DEBUG) { |
| System.out.println("resolveEntity(\""+publicId+"\", \""+systemId+"\")"); |
| } |
| |
| // public identifier resolution |
| if (publicId != null) { |
| // direct public id mappings |
| String value = getPublicMapping(publicId); |
| if (DEBUG) { |
| System.out.println(" map: \""+publicId+"\" -> \""+value+"\""); |
| } |
| if (value != null) { |
| InputSource source = resolveEntity(null, value); |
| if (source == null) { |
| source = new InputSource(value); |
| } |
| source.setPublicId(publicId); |
| return source; |
| } |
| |
| // delegates |
| Enumeration delegates = getDelegateCatalogKeys(); |
| while (delegates.hasMoreElements()) { |
| String key = (String)delegates.nextElement(); |
| if (DEBUG) { |
| System.out.println(" delegate: \""+key+"\""); |
| } |
| if (publicId.startsWith(key)) { |
| XMLCatalogHandler catalog = getDelegateCatalog(key); |
| InputSource source = catalog.resolveEntity(publicId, systemId); |
| if (source != null) { |
| return source; |
| } |
| } |
| } |
| } |
| |
| // system identifier resolution |
| String value = getSystemMapping(systemId); |
| if (value != null) { |
| if (DEBUG) { |
| System.out.println(" remap: \""+systemId+"\" -> \""+value+"\""); |
| } |
| InputSource source = new InputSource(value); |
| source.setPublicId(publicId); |
| return source; |
| } |
| |
| // use default behavior |
| if (DEBUG) { |
| System.out.println(" returning null!"); |
| } |
| return null; |
| |
| } // resolveEntity(String,String):InputSource |
| |
| // |
| // Public methods |
| // |
| |
| /** |
| * Adds a delegate mapping. If the prefix of a public identifier |
| * matches a delegate prefix, then the delegate catalog is |
| * searched in order to resolve the identifier. |
| * <p> |
| * This method makes sure that prefixes that match each other |
| * are inserted into the delegate list in order of longest prefix |
| * length first. |
| * |
| * @param prefix The delegate prefix. |
| * @param catalog The delegate catalog. |
| */ |
| public void addDelegateCatalog(String prefix, XCatalog catalog) { |
| |
| synchronized (delegate) { |
| // insert prefix in proper order |
| if (!delegate.containsKey(prefix)) { |
| int size = delegateOrder.size(); |
| boolean found = false; |
| for (int i = 0; i < size; i++) { |
| String element = (String)delegateOrder.elementAt(i); |
| if (prefix.startsWith(element) || prefix.compareTo(element) < 0) { |
| delegateOrder.insertElementAt(prefix, i); |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| delegateOrder.addElement(prefix); |
| } |
| } |
| |
| // replace (or add new) prefix mapping |
| delegate.put(prefix, catalog); |
| } |
| |
| } // addDelegateCatalog(String,XCatalog) |
| |
| /** |
| * Removes a delegate. |
| * |
| * @param prefix The delegate prefix to remove. |
| */ |
| public void removeDelegateCatalog(String prefix) { |
| |
| synchronized (delegate) { |
| delegate.remove(prefix); |
| delegateOrder.removeElement(prefix); |
| } |
| |
| } // removeDelegateCatalog(String) |
| |
| /** Returns an enumeration of delegate prefixes. */ |
| public Enumeration getDelegateCatalogKeys() { |
| return delegateOrder.elements(); |
| } |
| |
| /** Returns the catalog for the given delegate prefix. */ |
| public XCatalog getDelegateCatalog(String prefix) { |
| return (XCatalog)delegate.get(prefix); |
| } |
| |
| // |
| // "default" methods |
| // |
| |
| /** Returns true if the string is a valid URL. */ |
| boolean isURL(String str) { |
| try { |
| new java.net.URL(str); |
| return true; |
| } |
| catch (java.net.MalformedURLException e) { |
| // assume the worst |
| } |
| return false; |
| } |
| |
| // |
| // Classes |
| // |
| |
| /** Parser for XCatalog document instances. */ |
| class Parser |
| extends SAXParser |
| implements DocumentHandler |
| { |
| |
| // |
| // Data |
| // |
| |
| /** The base. */ |
| private String base; |
| |
| // |
| // Constructors |
| // |
| |
| /** Parses the specified input source. */ |
| public Parser(InputSource source) |
| throws SAXException, IOException |
| { |
| |
| // setup parser |
| setEntityResolver(new Resolver()); |
| setDocumentHandler((DocumentHandler)this); |
| |
| // set base and parse |
| setBase(source.getSystemId()); |
| parse(source); |
| |
| } // <init>(InputSource) |
| |
| // |
| // Protected methods |
| // |
| |
| /** |
| * Sets the base from the given system identifier. The base is |
| * the same as the system identifier with the least significant |
| * part (the filename) removed. |
| */ |
| protected void setBase(String systemId) throws SAXException { |
| |
| // normalize system id |
| if (systemId == null) { |
| systemId = ""; |
| } |
| |
| // expand system id |
| systemId = fEntityHandler.expandSystemId(systemId); |
| |
| // cut off the least significant part |
| int index = systemId.lastIndexOf('/'); |
| if (index != -1) { |
| systemId = systemId.substring(0, index + 1); |
| } |
| |
| // save base |
| base = systemId; |
| |
| } // setBase(String) |
| |
| // |
| // DocumentHandler methods |
| // |
| |
| /** Not implemented. */ |
| public void processingInstruction(String target, String data) {} |
| |
| /** Not implemented. */ |
| public void setDocumentLocator(org.xml.sax.Locator locator) {} |
| |
| /** Not implemented. */ |
| public void startDocument() {} |
| |
| /** Not implemented. */ |
| public void endElement(String elementName) {} |
| |
| /** Not implemented. */ |
| public void endDocument() {} |
| |
| /** Not implemented. */ |
| public void characters(char ch[], int start, int length) {} |
| |
| /** Not implemented. */ |
| public void ignorableWhitespace(char ch[], int start, int length) {} |
| |
| /** The start of an element. */ |
| public void startElement(String elementName, AttributeList attrList) |
| throws SAXException |
| { |
| |
| try { |
| // <XCatalog Version="..."> |
| if (elementName.equals(XCATALOG)) { |
| return; |
| } |
| |
| // <Map PublicID="..." HRef="..."/> |
| if (elementName.equals(MAP)) { |
| // get attributes |
| String publicId = attrList.getValue(PUBLICID); |
| String href = attrList.getValue(HREF); |
| if (DEBUG) { |
| System.out.println("MAP \""+publicId+"\" \""+href+"\""); |
| } |
| |
| // create mapping |
| if (!isURL(href)) { |
| href = base + href; |
| } |
| addPublicMapping(publicId, href); |
| } |
| |
| // <Delegate PublicId="..." HRef="..."/> |
| else if (elementName.equals(DELEGATE)) { |
| // get attributes |
| String publicId = attrList.getValue(PUBLICID); |
| String href = attrList.getValue(HREF); |
| if (DEBUG) { |
| System.out.println("DELEGATE \""+publicId+"\" \""+href+"\""); |
| } |
| |
| // expand system id |
| if (!isURL(href)) { |
| href = base + href; |
| } |
| String systemId = fEntityHandler.expandSystemId(href); |
| |
| // create delegate |
| XCatalog catalog = new XCatalog(); |
| catalog.loadCatalog(new InputSource(systemId)); |
| addDelegateCatalog(publicId, catalog); |
| } |
| |
| // <Extend HRef="..."/> |
| else if (elementName.equals(EXTEND)) { |
| // get attributes |
| String href = attrList.getValue(HREF); |
| if (DEBUG) { |
| System.out.println("EXTEND \""+href+"\""); |
| } |
| |
| // expand system id |
| if (!isURL(href)) { |
| href = base + href; |
| } |
| String systemId = fEntityHandler.expandSystemId(href); |
| |
| // create catalog |
| XCatalog.this.loadCatalog(new InputSource(systemId)); |
| } |
| |
| // <Base HRef="..."/> |
| else if (elementName.equals(BASE)) { |
| // get attributes |
| String href = attrList.getValue(HREF); |
| |
| // set new base |
| setBase(href); |
| if (DEBUG) { |
| System.out.println("BASE \""+href+"\" -> \""+base+"\""); |
| } |
| } |
| |
| // <Remap SystemID="..." HRef="..."/> |
| else if (elementName.equals(REMAP)) { |
| // get attributes |
| String systemId = attrList.getValue(SYSTEMID); |
| String href = attrList.getValue(HREF); |
| if (DEBUG) { |
| System.out.println("REMAP \""+systemId+"\" \""+href+"\""); |
| } |
| |
| // create mapping |
| if (!isURL(href)) { |
| href = base + href; |
| } |
| addSystemMapping(systemId, href); |
| } |
| } |
| catch (Exception e) { |
| throw new SAXException(e); |
| } |
| |
| } // startElement(String,AttributeList) |
| |
| // |
| // Classes |
| // |
| |
| /** Resolver for locating the XCatalog DTD resource. */ |
| class Resolver |
| implements EntityResolver |
| { |
| |
| /** Resolves the XCatalog DTD entity. */ |
| public InputSource resolveEntity(String publicId, String systemId) |
| throws SAXException, IOException |
| { |
| |
| // resolve the XCatalog DTD? |
| if (publicId != null && publicId.equals(XCATALOG_DTD_PUBLICID)) { |
| InputSource src = new InputSource(); |
| src.setPublicId(publicId); |
| InputStream is = getClass().getResourceAsStream(DTD); |
| src.setByteStream(is); |
| src.setCharacterStream(new InputStreamReader(is)); |
| return src; |
| } |
| |
| // no resolution possible |
| return null; |
| |
| } // resolveEntity(String,String):InputSource |
| |
| } // class Resolver |
| |
| } // class Parser |
| |
| } // class XCatalog |