package gnu.xml.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Stack;
import java.util.Vector;
import org.xml.sax.Attributes;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.DefaultHandler2;
import org.xml.sax.ext.EntityResolver2;
import org.xml.sax.helpers.XMLReaderFactory;
public class XCat implements EntityResolver2
{
private Catalog catalogs [];
private boolean usingPublic = true;
private boolean loadingPermitted = true;
private boolean unified = true;
private String parserClass;
private ErrorHandler errorHandler;
public XCat () { }
public XCat (String uri)
throws SAXException, IOException
{ loadCatalog (uri); }
public synchronized void loadCatalog (String uri)
throws SAXException, IOException
{
Catalog catalog;
int index = -1;
if (!loadingPermitted)
throw new IllegalStateException ();
uri = normalizeURI (uri);
if (catalogs != null) {
for (index = 0; index < catalogs.length; index++)
if (uri.equals (catalogs [index].catalogURI))
break;
}
catalog = loadCatalog (parserClass, errorHandler, uri, unified);
if (catalogs == null) {
index = 0;
catalogs = new Catalog [1];
} else if (index == catalogs.length) {
Catalog tmp [];
tmp = new Catalog [index + 1];
System.arraycopy (catalogs, 0, tmp, 0, index);
catalogs = tmp;
}
catalogs [index] = catalog;
}
public InputSource resolveEntity (
String name, String publicId,
String baseURI, String systemId
) throws SAXException, IOException
{
if (loadingPermitted)
disableLoading ();
try {
for (int i = 0; i < catalogs.length; i++) {
InputSource retval;
retval = catalogs [i].resolve (usingPublic, publicId, systemId);
if (retval != null)
return retval;;
}
} catch (DoneDelegation x) {
}
return null;
}
public InputSource getExternalSubset (String name, String baseURI)
throws SAXException, IOException
{
if (loadingPermitted)
disableLoading ();
try {
for (int i = 0; i < catalogs.length; i++) {
InputSource retval = catalogs [i].getExternalSubset (name);
if (retval != null)
return retval;
}
} catch (DoneDelegation x) {
}
return null;
}
final public InputSource resolveEntity (String publicId, String systemId)
throws SAXException, IOException
{
return resolveEntity (null, publicId, null, systemId);
}
public InputSource resolveURI (String baseURI, String uri)
throws SAXException, IOException
{
if (loadingPermitted)
disableLoading ();
try {
for (int i = 0; i < catalogs.length; i++) {
InputSource tmp = catalogs [i].resolveURI (uri);
if (tmp != null)
return tmp;
}
} catch (DoneDelegation x) {
}
return null;
}
public synchronized void disableLoading ()
{
loadingPermitted = false;
}
public ErrorHandler getErrorHandler ()
{ return errorHandler; }
public void setErrorHandler (ErrorHandler handler)
{ errorHandler = handler; }
public String getParserClass ()
{ return parserClass; }
public void setParserClass (String parser)
{ parserClass = parser; }
public boolean isUnified ()
{ return unified; }
public void setUnified (boolean value)
{ unified = value; }
public boolean isUsingPublic ()
{ return usingPublic; }
public void setUsingPublic (boolean value)
{ usingPublic = value; }
private static Catalog loadCatalog (
String parserClass,
ErrorHandler eh,
String uri,
boolean unified
) throws SAXException, IOException
{
XMLReader parser;
Loader loader;
boolean doesIntern = false;
if (parserClass == null)
parser = XMLReaderFactory.createXMLReader ();
else
parser = XMLReaderFactory.createXMLReader (parserClass);
if (eh != null)
parser.setErrorHandler (eh);
try {
doesIntern = parser.getFeature (
"http://xml.org/sax/features/string-interning");
} catch (SAXNotRecognizedException e) { }
loader = new Loader (doesIntern, eh, unified);
loader.cat.parserClass = parserClass;
loader.cat.catalogURI = uri;
parser.setContentHandler (loader);
parser.setProperty (
"http://xml.org/sax/properties/declaration-handler",
loader);
parser.setProperty (
"http://xml.org/sax/properties/lexical-handler",
loader);
parser.parse (uri);
return loader.cat;
}
private static String normalizePublicId (boolean full, String publicId)
{
if (publicId.startsWith ("urn:publicid:")) {
StringBuffer buf = new StringBuffer ();
char chars [] = publicId.toCharArray ();
boolean hasbug = false;
for (int i = 13; i < chars.length; i++) {
switch (chars [i]) {
case '+': buf.append (' '); continue;
case ':': buf.append ("//"); continue;
case ';': buf.append ("::"); continue;
case '%':
hasbug = true;
default: buf.append (chars [i]); continue;
}
}
publicId = buf.toString ();
if (hasbug)
System.err.println ("nyet unhexing public id: " + publicId);
full = true;
}
if (full) {
StringTokenizer tokens;
String token;
tokens = new StringTokenizer (publicId, " \r\n");
publicId = null;
while (tokens.hasMoreTokens ()) {
if (publicId == null)
publicId = tokens.nextToken ();
else
publicId += " " + tokens.nextToken ();
}
}
return publicId;
}
private static boolean isUriExcluded (int c)
{ return c <= 0x20 || c >= 0x7f || "\"<>^`{|}".indexOf (c) != -1; }
private static int hexNibble (int c)
{
if (c < 10)
return c + '0';
return ('a' - 10) + c;
}
private static String normalizeURI (String systemId)
{
int length = systemId.length ();
for (int i = 0; i < length; i++) {
char c = systemId.charAt (i);
if (isUriExcluded (c)) {
byte buf [];
ByteArrayOutputStream out;
int b;
try {
buf = systemId.getBytes ("UTF8");
out = new ByteArrayOutputStream (buf.length + 10);
for (i = 0; i < buf.length; i++) {
b = buf [i] & 0x0ff;
if (isUriExcluded (b)) {
out.write ((int) '%');
out.write (hexNibble (b >> 4));
out.write (hexNibble (b & 0x0f));
} else
out.write (b);
}
return out.toString ("8859_1");
} catch (IOException e) {
throw new RuntimeException (
"can't normalize URI: " + e.getMessage ());
}
}
}
return systemId;
}
private static class DoneDelegation extends SAXException
{
DoneDelegation () { }
}
private static class Catalog
{
String catalogURI;
ErrorHandler eh;
boolean unified;
String parserClass;
boolean hasPreference;
boolean usingPublic;
Hashtable publicIds;
Hashtable publicDelegations;
Hashtable systemIds;
Hashtable systemRewrites;
Hashtable systemDelegations;
Hashtable uris;
Hashtable uriRewrites;
Hashtable uriDelegations;
Hashtable doctypes;
Vector next;
Catalog () { }
private InputSource locatePublicId (String publicId)
throws SAXException, IOException
{
if (publicIds != null) {
String retval = (String) publicIds.get (publicId);
if (retval != null) {
return new InputSource (retval);
}
}
if (publicDelegations != null)
return checkDelegations (publicDelegations, publicId,
publicId, null);
return null;
}
private InputSource mapURI (
String uri,
Hashtable ids,
Hashtable rewrites,
Hashtable delegations
) throws SAXException, IOException
{
if (ids != null) {
String retval = (String) ids.get (uri);
if (retval != null) {
return new InputSource (retval);
}
}
if (rewrites != null) {
String prefix = null;
String replace = null;
int prefixLen = -1;
for (Enumeration e = rewrites.keys ();
e.hasMoreElements ();
) {
String temp = (String) e.nextElement ();
int len = -1;
if (!uri.startsWith (temp))
continue;
if (prefix != null
&& (len = temp.length ()) < prefixLen)
continue;
prefix = temp;
prefixLen = len;
replace = (String) rewrites.get (temp);
}
if (prefix != null) {
StringBuffer buf = new StringBuffer (replace);
buf.append (uri.substring (prefixLen));
return new InputSource (buf.toString ());
}
}
if (delegations != null)
return checkDelegations (delegations, uri, null, uri);
return null;
}
public InputSource resolve (
boolean usingPublic,
String publicId,
String systemId
) throws SAXException, IOException
{
boolean preferSystem;
InputSource retval;
if (hasPreference)
preferSystem = !this.usingPublic;
else
preferSystem = !usingPublic;
if (publicId != null)
publicId = normalizePublicId (false, publicId);
if (systemId != null) {
if (systemId.startsWith ("urn:publicid:")) {
String temp = normalizePublicId (true, systemId);
if (publicId == null) {
publicId = temp;
systemId = null;
} else if (!publicId.equals (temp)) {
systemId = null;
}
} else
systemId = normalizeURI (systemId);
}
if (systemId == null && publicId == null)
return null;
if (systemId != null) {
retval = mapURI (systemId, systemIds, systemRewrites,
systemDelegations);
if (retval != null) {
retval.setPublicId (publicId);
return retval;
}
}
if (publicId != null
&& !(systemId != null && preferSystem)) {
retval = locatePublicId (publicId);
if (retval != null) {
retval.setPublicId (publicId);
return retval;
}
}
if (next != null) {
int length = next.size ();
for (int i = 0; i < length; i++) {
Catalog n = getNext (i);
retval = n.resolve (usingPublic, publicId, systemId);
if (retval != null)
return retval;
}
}
return null;
}
public InputSource resolveURI (String uri)
throws SAXException, IOException
{
if (uri.startsWith ("urn:publicid:"))
return resolve (true, normalizePublicId (true, uri), null);
InputSource retval;
uri = normalizeURI (uri);
retval = mapURI (uri, uris, uriRewrites, uriDelegations);
if (retval != null)
return retval;
if (next != null) {
int length = next.size ();
for (int i = 0; i < length; i++) {
Catalog n = getNext (i);
retval = n.resolveURI (uri);
if (retval != null)
return retval;
}
}
return null;
}
public InputSource getExternalSubset (String name)
throws SAXException, IOException
{
if (doctypes != null) {
String value = (String) doctypes.get (name);
if (value != null) {
return new InputSource (value);
}
}
if (next != null) {
int length = next.size ();
for (int i = 0; i < length; i++) {
Catalog n = getNext (i);
if (n == null)
continue;
InputSource retval = n.getExternalSubset (name);
if (retval != null)
return retval;
}
}
return null;
}
private synchronized Catalog getNext (int i)
throws SAXException, IOException
{
Object obj;
if (next == null || i < 0 || i >= next.size ())
return null;
obj = next.elementAt (i);
if (obj instanceof Catalog)
return (Catalog) obj;
Catalog cat = null;
try {
cat = loadCatalog (parserClass, eh, (String) obj, unified);
next.setElementAt (cat, i);
} catch (SAXException e) {
} catch (IOException e) {
}
return cat;
}
private InputSource checkDelegations (
Hashtable delegations,
String id,
String publicId, String systemId ) throws SAXException, IOException
{
Vector matches = null;
int length = 0;
for (Enumeration e = delegations.keys ();
e.hasMoreElements ();
) {
String prefix = (String) e.nextElement ();
if (!id.startsWith (prefix))
continue;
if (matches == null)
matches = new Vector ();
int index;
for (index = 0; index < length; index++) {
String temp = (String) matches.elementAt (index);
if (prefix.length () > temp.length ()) {
matches.insertElementAt (prefix, index);
break;
}
}
if (index == length)
matches.addElement (prefix);
length++;
}
if (matches == null)
return null;
for (int i = 0; i < length; i++) {
Catalog catalog = null;
InputSource result;
synchronized (delegations) {
Object prefix = matches.elementAt (i);
Object cat = delegations.get (prefix);
if (cat instanceof Catalog)
catalog = (Catalog) cat;
else {
try {
catalog = loadCatalog (parserClass, eh,
(String) cat, unified);
delegations.put (prefix, catalog);
} catch (SAXException e) {
} catch (IOException e) {
}
}
}
if (catalog == null)
continue;
result = catalog.resolve (true, publicId, systemId);
if (result != null)
return result;
}
throw new DoneDelegation ();
}
}
private static final String catalogNamespace =
"urn:oasis:names:tc:entity:xmlns:xml:catalog";
private static class Loader extends DefaultHandler2
{
private boolean preInterned;
private ErrorHandler handler;
private boolean unified;
private int ignoreDepth;
private Locator locator;
private boolean started;
private Hashtable externals;
private Stack bases;
Catalog cat = new Catalog ();
Loader (boolean flag, ErrorHandler eh, boolean unified)
{
preInterned = flag;
handler = eh;
this.unified = unified;
cat.unified = unified;
cat.eh = eh;
}
private String nofrag (String uri)
throws SAXException
{
if (uri.indexOf ('#') != -1) {
warn ("URI with fragment: " + uri);
uri = uri.substring (0, uri.indexOf ('#'));
}
return uri;
}
private String absolutize (String uri)
throws SAXException
{
if (uri.startsWith ("file:/")
|| uri.startsWith ("http:/")
|| uri.startsWith ("https:/")
|| uri.startsWith ("ftp:/")
|| uri.startsWith ("urn:")
)
return uri;
try {
URL base = (URL) bases.peek ();
return new URL (base, uri).toString ();
} catch (Exception e) {
fatal ("can't absolutize URI: " + uri);
return null;
}
}
private void error (String message)
throws SAXException
{
if (handler == null)
return;
handler.error (new SAXParseException (message, locator));
}
private void fatal (String message)
throws SAXException
{
SAXParseException spe;
spe = new SAXParseException (message, locator);
if (handler != null)
handler.fatalError (spe);
throw spe;
}
private void warn (String message)
throws SAXException
{
if (handler == null)
return;
handler.warning (new SAXParseException (message, locator));
}
public void setDocumentLocator (Locator l)
{ locator = l; }
public void startDocument ()
throws SAXException
{
if (locator == null)
error ("no locator!");
bases = new Stack ();
String uri = locator.getSystemId ();
try {
bases.push (new URL (uri));
} catch (IOException e) {
fatal ("bad document base URI: " + uri);
}
}
public void endDocument ()
throws SAXException
{
try {
if (!started)
error ("not a catalog!");
} finally {
locator = null;
handler = null;
externals = null;
bases = null;
}
}
public void externalEntityDecl (String name, String pub, String sys)
throws SAXException
{
if (externals == null)
externals = new Hashtable ();
if (externals.get (name) == null)
externals.put (name, pub);
}
public void startEntity (String name)
throws SAXException
{
if (externals == null)
return;
String uri = (String) externals.get (name);
if (uri != null) {
try {
bases.push (new URL (uri));
} catch (IOException e) {
fatal ("entity '" + name + "', bad URI: " + uri);
}
}
}
public void endEntity (String name)
{
if (externals == null)
return;
String value = (String) externals.get (name);
if (value != null)
bases.pop ();
}
public void startElement (String namespace, String local,
String qName, Attributes atts)
throws SAXException
{
if (ignoreDepth != 0 || !catalogNamespace.equals (namespace)) {
ignoreDepth++;
return;
}
if (!preInterned)
local = local.intern ();
if (!started) {
started = true;
if ("catalog" != local)
fatal ("root element not 'catalog': " + local);
}
String xmlbase = atts.getValue ("xml:base");
if (xmlbase != null) {
URL base = (URL) bases.peek ();
try {
base = new URL (base, xmlbase);
} catch (IOException e) {
fatal ("can't resolve xml:base attribute: " + xmlbase);
}
bases.push (base);
} else
bases.push (bases.peek ());
String catalog = atts.getValue ("catalog");
if (catalog != null)
catalog = normalizeURI (absolutize (catalog));
String rewritePrefix = atts.getValue ("rewritePrefix");
if (rewritePrefix != null)
rewritePrefix = normalizeURI (absolutize (rewritePrefix));
String systemIdStartString;
systemIdStartString = atts.getValue ("systemIdStartString");
if (systemIdStartString != null) {
systemIdStartString = normalizeURI (systemIdStartString);
if (systemIdStartString.startsWith ("urn:publicid:")) {
error ("systemIdStartString is really a publicId!!");
return;
}
}
String uri = atts.getValue ("uri");
if (uri != null)
uri = normalizeURI (absolutize (uri));
String uriStartString;
uriStartString = atts.getValue ("uriStartString");
if (uriStartString != null) {
uriStartString = normalizeURI (uriStartString);
if (uriStartString.startsWith ("urn:publicid:")) {
error ("uriStartString is really a publicId!!");
return;
}
}
if ("catalog" == local || "group" == local) {
String prefer = atts.getValue ("prefer");
if (prefer != null && !"public".equals (prefer)) {
if (!"system".equals (prefer)) {
error ("in <" + local + " ... prefer='...'>, "
+ "assuming 'public'");
prefer = "public";
}
}
if (prefer != null) {
if ("catalog" == local) {
cat.hasPreference = true;
cat.usingPublic = "public".equals (prefer);
} else {
if (!cat.hasPreference || cat.usingPublic
!= "public".equals (prefer)) {
fatal ("<group prefer=...> case not handled");
}
}
} else if ("group" == local && cat.hasPreference) {
fatal ("<group prefer=...> case not handled");
}
} else if ("public" == local) {
String publicId = atts.getValue ("publicId");
String value = null;
if (publicId == null || uri == null) {
error ("expecting <public publicId=... uri=.../>");
return;
}
publicId = normalizePublicId (true, publicId);
uri = nofrag (uri);
if (cat.publicIds == null)
cat.publicIds = new Hashtable ();
else
value = (String) cat.publicIds.get (publicId);
if (value != null) {
if (!value.equals (uri))
warn ("ignoring <public...> entry for " + publicId);
} else
cat.publicIds.put (publicId, uri);
} else if ("delegatePublic" == local) {
String publicIdStartString;
Object value = null;
publicIdStartString = atts.getValue ("publicIdStartString");
if (publicIdStartString == null || catalog == null) {
error ("expecting <delegatePublic "
+ "publicIdStartString=... catalog=.../>");
return;
}
publicIdStartString = normalizePublicId (true,
publicIdStartString);
if (cat.publicDelegations == null)
cat.publicDelegations = new Hashtable ();
else
value = cat.publicDelegations.get (publicIdStartString);
if (value != null) {
if (!value.equals (catalog))
warn ("ignoring <delegatePublic...> entry for "
+ uriStartString);
} else
cat.publicDelegations.put (publicIdStartString, catalog);
} else if ("system" == local) {
String systemId = atts.getValue ("systemId");
String value = null;
if (systemId == null || uri == null) {
error ("expecting <system systemId=... uri=.../>");
return;
}
systemId = normalizeURI (systemId);
uri = nofrag (uri);
if (systemId.startsWith ("urn:publicid:")) {
error ("systemId is really a publicId!!");
return;
}
if (cat.systemIds == null) {
cat.systemIds = new Hashtable ();
if (unified)
cat.uris = cat.systemIds;
} else
value = (String) cat.systemIds.get (systemId);
if (value != null) {
if (!value.equals (uri))
warn ("ignoring <system...> entry for " + systemId);
} else
cat.systemIds.put (systemId, uri);
} else if ("rewriteSystem" == local) {
String value = null;
if (systemIdStartString == null || rewritePrefix == null
|| systemIdStartString.length () == 0
|| rewritePrefix.length () == 0
) {
error ("expecting <rewriteSystem "
+ "systemIdStartString=... rewritePrefix=.../>");
return;
}
if (cat.systemRewrites == null) {
cat.systemRewrites = new Hashtable ();
if (unified)
cat.uriRewrites = cat.systemRewrites;
} else
value = (String) cat.systemRewrites.get (
systemIdStartString);
if (value != null) {
if (!value.equals (rewritePrefix))
warn ("ignoring <rewriteSystem...> entry for "
+ systemIdStartString);
} else
cat.systemRewrites.put (systemIdStartString,
rewritePrefix);
} else if ("delegateSystem" == local) {
Object value = null;
if (systemIdStartString == null || catalog == null) {
error ("expecting <delegateSystem "
+ "systemIdStartString=... catalog=.../>");
return;
}
if (cat.systemDelegations == null) {
cat.systemDelegations = new Hashtable ();
if (unified)
cat.uriDelegations = cat.systemDelegations;
} else
value = cat.systemDelegations.get (systemIdStartString);
if (value != null) {
if (!value.equals (catalog))
warn ("ignoring <delegateSystem...> entry for "
+ uriStartString);
} else
cat.systemDelegations.put (systemIdStartString, catalog);
} else if ("uri" == local) {
String name = atts.getValue ("name");
String value = null;
if (name == null || uri == null) {
error ("expecting <uri name=... uri=.../>");
return;
}
if (name.startsWith ("urn:publicid:")) {
error ("name is really a publicId!!");
return;
}
name = normalizeURI (name);
if (cat.uris == null) {
cat.uris = new Hashtable ();
if (unified)
cat.systemIds = cat.uris;
} else
value = (String) cat.uris.get (name);
if (value != null) {
if (!value.equals (uri))
warn ("ignoring <uri...> entry for " + name);
} else
cat.uris.put (name, uri);
} else if ("rewriteURI" == local) {
String value = null;
if (uriStartString == null || rewritePrefix == null
|| uriStartString.length () == 0
|| rewritePrefix.length () == 0
) {
error ("expecting <rewriteURI "
+ "uriStartString=... rewritePrefix=.../>");
return;
}
if (cat.uriRewrites == null) {
cat.uriRewrites = new Hashtable ();
if (unified)
cat.systemRewrites = cat.uriRewrites;
} else
value = (String) cat.uriRewrites.get (uriStartString);
if (value != null) {
if (!value.equals (rewritePrefix))
warn ("ignoring <rewriteURI...> entry for "
+ uriStartString);
} else
cat.uriRewrites.put (uriStartString, rewritePrefix);
} else if ("delegateURI" == local) {
Object value = null;
if (uriStartString == null || catalog == null) {
error ("expecting <delegateURI "
+ "uriStartString=... catalog=.../>");
return;
}
if (cat.uriDelegations == null) {
cat.uriDelegations = new Hashtable ();
if (unified)
cat.systemDelegations = cat.uriDelegations;
} else
value = cat.uriDelegations.get (uriStartString);
if (value != null) {
if (!value.equals (catalog))
warn ("ignoring <delegateURI...> entry for "
+ uriStartString);
} else
cat.uriDelegations.put (uriStartString, catalog);
} else if ("nextCatalog" == local) {
if (catalog == null) {
error ("expecting <nextCatalog catalog=.../>");
return;
}
if (cat.next == null)
cat.next = new Vector ();
cat.next.addElement (catalog);
} else if ("doctype" == local) {
String name = atts.getValue ("name");
String value = null;
if (name == null || uri == null) {
error ("expecting <doctype name=... uri=.../>");
return;
}
name = normalizeURI (name);
if (cat.doctypes == null)
cat.doctypes = new Hashtable ();
else
value = (String) cat.doctypes.get (name);
if (value != null) {
if (!value.equals (uri))
warn ("ignoring <doctype...> entry for "
+ uriStartString);
} else
cat.doctypes.put (name, uri);
} else {
warn ("ignoring unknown catalog element: " + local);
ignoreDepth++;
}
}
public void endElement (String uri, String local, String qName)
throws SAXException
{
if (ignoreDepth != 0)
ignoreDepth--;
else
bases.pop ();
}
}
}