// XMLCatalogReader.java - Read XML Catalog files // Written by Norman Walsh, nwalsh@arbortext.com // NO WARRANTY! This class is in the public domain. package com.arbortext.catalog; import java.lang.Integer; import java.util.Vector; import java.util.Enumeration; import java.io.IOException; import java.net.URL; import java.net.MalformedURLException; import com.arbortext.catalog.CatalogEntry; import com.arbortext.catalog.CatalogReader; import com.arbortext.catalog.InvalidCatalogEntryTypeException; import com.arbortext.catalog.InvalidCatalogEntryException; import com.arbortext.catalog.UnknownCatalogFormatException; import com.arbortext.catalog.NotXMLCatalogException; import com.arbortext.catalog.NoXMLParserException; import org.xml.sax.*; /** *

Parses XML Catalog files.

* *
* This module, both source code and documentation, is in the * Public Domain, and comes with NO WARRANTY. *
* *

This class reads XML Catalog files, returning a stream * of tokens. At present, it recognizes John Cowan's * XML Catalogs * (formerly XCatalogs). In the future, additional XML Catalog formats * may be supported.

* *

This code interrogates the following non-standard system properties:

* *
*
xml.catalog.debug
*

Sets the debug level. A value of 0 is assumed if the * property is not set or is not a number.

*
* * @see Catalog * * @author Arbortext, Inc. * @author Norman Walsh * nwalsh@arbortext.com * @version 1.0 */ public class XMLCatalogReader implements DocumentHandler { // These are class variables so that several methods can access them /** The filename (URL) of the catalog being read */ private String catfilename = null; /** *

The debug level

* *

In general, higher numbers produce more information:

* */ public int debug = 0; /** * Indicates that the catalog type is not XML */ private static final int NOTXMLCATALOG = -1; /** * Indicates that the catalog type is unknown. */ private static final int UNKNOWNCATALOG = 0; /** * Indicates that the catalog type is an XML Catalog (John Cowan's * XCatalog) */ private static final int XCATALOG = 1; /** * Indicates the catalog type. */ private int catalogType = NOTXMLCATALOG; /** *

The list of entries scanned from the catalog.

* *

The SAX Parser is event-driven, but the Catalog class expects * to iterate through the entries with * nextToken(). So this class builds a * vector of entries during the parse and returns them sequentially * when nextToken() is called.

* * @see Catalog */ private Vector catalogEntries = new Vector(); /** * An enumerator for walking through the list of catalogEntries. */ private Enumeration catalogEnum = null; /** *

The name of the parser class to load when parsing XML Catalogs.

* *

If a parser class is provided, * subsequent attempts to parse Catalog files will begin * by attemptiing an XML parse of the catalog file using a parser * of this class. * If the XML parse fails, the "default" text parse will be done * instead.

*/ private String parserClass = null; /** *

Construct an XMLCatalogReader object.

*/ public XMLCatalogReader() { String property = System.getProperty("xml.catalog.debug"); if (property != null) { try { debug = Integer.parseInt(property); } catch (NumberFormatException e) { debug = 0; } } } /** *

Sets the parser class, enabling XML Catalog parsing.

* *

Sets the parser class that will be used for loading XML Catalogs. * If this method is not called, all attempts to use the * XMLCatalogParser will fail, throwing a * NoXMLParserException.

* * @param parser The name of a class implementing the SAX Parser * interface to be used for subsequent XML Catalog parsing. * * @see com.arbortext.catalog.NoXMLParserException */ public void setParserClass(String parser) { parserClass = parser; } /** *

Attempt to parse an XML Catalog file.

* * @param fileUrl The URL or filename of the catalog file to process * @param catParser A SAX-compliant parser to use for reading the files * * @throws SAXException Error parsing catalog file. * @throws IOException Error reading catalog file. * @throws NoXMLParserException No Parser class provided. * @throws NotXMLCatalogException The Catalog appears not to be XML. * @throws UnknownCatalogFormatException Unexpected XML catalog type. * @throws ClassNotFoundException Parser class can't be found. * @throws InstantiationException Parser class can't be instantiated. * @throws IllegalAccessException Error instantiating parser class. * @throws ClassCastException Parser class isn't a SAX Parser. */ public void parseCatalog(String fileUrl) throws SAXException, IOException, NotXMLCatalogException, NoXMLParserException, UnknownCatalogFormatException, ClassNotFoundException, InstantiationException, IllegalAccessException, ClassCastException { // Create an instance of the parser if (parserClass == null) { throw new NoXMLParserException(); } Parser parser = (Parser) Class.forName(parserClass).newInstance(); catfilename = fileUrl; parser.setDocumentHandler(this); parser.parse(fileUrl); if (catalogType == NOTXMLCATALOG) { // Why doesn't the attempt to parse this file throw a // SAX Exception??? throw new NotXMLCatalogException(); } if (catalogType == UNKNOWNCATALOG) { throw new UnknownCatalogFormatException(); } } /** *

Get the next entry from the file

* * @throws IOException Error reading catalog file * @return A CatalogEntry object for the next entry in the catalog */ public CatalogEntry nextEntry() throws IOException { if (catalogEnum == null) { catalogEnum = catalogEntries.elements(); } if (catalogEnum.hasMoreElements()) { return (CatalogEntry) catalogEnum.nextElement(); } else { return null; } } // ---------------------------------------------------------------------- /* *

Parse elements from John Cowan's XML Catalog doctype.

* *

Each recognized element is turned into an appropriate * CatalogEntry and put onto the entries vector for later * retrieval.

* * @param name The name of the element. * @param atts The list of attributes on the element. * * @see CatalogEntry */ private void xCatalogEntry (String name, AttributeList atts) { CatalogEntry ce = null; try { if (name.equals("Base")) { ce = new CatalogEntry(CatalogEntry.BASE, atts.getValue("HRef")); debug(3, "Base", atts.getValue("HRef")); } if (name.equals("Delegate")) { ce = new CatalogEntry(CatalogEntry.DELEGATE, CatalogReader.normalize(atts.getValue("PublicId")), atts.getValue("HRef")); debug(3, "Delegate", CatalogReader.normalize(atts.getValue("PublicId")), atts.getValue("HRef")); } if (name.equals("Extend")) { ce = new CatalogEntry(CatalogEntry.CATALOG, atts.getValue("HRef")); debug(3, "Extend", atts.getValue("HRef")); } if (name.equals("Map")) { ce = new CatalogEntry(CatalogEntry.PUBLIC, CatalogReader.normalize(atts.getValue("PublicId")), atts.getValue("HRef")); debug(3, "Map", CatalogReader.normalize(atts.getValue("PublicId")), atts.getValue("HRef")); } if (name.equals("Remap")) { ce = new CatalogEntry(CatalogEntry.SYSTEM, atts.getValue("SystemId"), atts.getValue("HRef")); debug(3, "Remap", CatalogReader.normalize(atts.getValue("SystemId")), atts.getValue("HRef")); } if (ce == null) { // This is equivalent to an invalid catalog entry type debug(1, "Invalid catalog entry type", name); } } catch (InvalidCatalogEntryTypeException icete) { debug(1, "Invalid catalog entry type", name); } catch (InvalidCatalogEntryException icete) { debug(1, "Invalid catalog entry", name); } if (ce != null) { catalogEntries.addElement(ce); } } // ---------------------------------------------------------------------- // Implement the SAX DocumentHandler interface /**

The SAX setDocumentLocator method. Does nothing.

*/ public void setDocumentLocator (Locator locator) { return; } /**

The SAX startDocument method. Does nothing.

*/ public void startDocument () throws SAXException { return; } /**

The SAX endDocument method. Does nothing.

*/ public void endDocument () throws SAXException { return; } /** *

The SAX startElement method.

* *

This element attempts to identify the type of catalog * by looking at the name of the first element encountered. * If it recognizes the element, it sets the catalogType * appropriately.

* *

After the catalog type has been identified, the appropriate * entry parser is called for each subsequent element in the * catalog.

* * @param name The name of the element. * @param atts The list of attributes on the element. * */ public void startElement (String name, AttributeList atts) throws SAXException { if (catalogType == UNKNOWNCATALOG || catalogType == NOTXMLCATALOG) { if (name.equals("XMLCatalog")) { catalogType = XCATALOG; return; } } if (catalogType == XCATALOG) { xCatalogEntry(name, atts); } } /**

The SAX endElement method. Does nothing.

*/ public void endElement (String name) throws SAXException { return; } /**

The SAX characters method. Does nothing.

*/ public void characters (char ch[], int start, int length) throws SAXException { return; } /**

The SAX ignorableWhitespace method. Does nothing.

*/ public void ignorableWhitespace (char ch[], int start, int length) throws SAXException { return; } /**

The SAX processingInstruction method. Does nothing.

*/ public void processingInstruction (String target, String data) throws SAXException { return; } // ----------------------------------------------------------------- /** *

Print debug message (if the debug level is high enough).

* * @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 token The catalog file token being processed. */ private void debug(int level, String message, String token) { if (debug >= level) { System.out.println(message + ": " + token); } } /** *

Print debug message (if the debug level is high enough).

* * @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 token The catalog file token being processed. * @param spec The argument to the token. */ private void debug(int level, String message, String token, String spec) { if (debug >= level) { System.out.println(message + ": " + token + " " + spec); } } /** *

Print debug message (if the debug level is high enough).

* * @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 token The catalog file token being processed. * @param spec1 The first argument to the token. * @param spec2 The second argument to the token. */ private void debug(int level, String message, String token, String spec1, String spec2) { if (debug >= level) { System.out.println(message + ": " + token + " " + spec1); System.out.println("\t" + spec2); } } }