blob: d0ed96a3ca125fe33ef971b71a67d2a890362ee5 [file] [log] [blame]
/*
* 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>&lt;!DOCTYPE&gt;</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>
* &lt;!DOCTYPE XCatalog PUBLIC "-//DTD XCatalog//EN" "org/apache/xerces/readers/xcatalog.dtd"&gt;
* </pre>
* <li>The enclosing <tt>&lt;XCatalog&gt;</tt> document root
* element is <b>not</b> optional -- it <b>must</b> be specified.
* <li>The <tt>Version</tt> attribute of the <tt>&lt;XCatalog&gt;</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