mirror of git://gcc.gnu.org/git/gcc.git
				
				
				
			
		
			
				
	
	
		
			1612 lines
		
	
	
		
			59 KiB
		
	
	
	
		
			Java
		
	
	
	
			
		
		
	
	
			1612 lines
		
	
	
		
			59 KiB
		
	
	
	
		
			Java
		
	
	
	
| /* XCat.java --
 | |
|    Copyright (C) 2001 Free Software Foundation, Inc.
 | |
| 
 | |
| This file is part of GNU Classpath.
 | |
| 
 | |
| GNU Classpath is free software; you can redistribute it and/or modify
 | |
| it under the terms of the GNU General Public License as published by
 | |
| the Free Software Foundation; either version 2, or (at your option)
 | |
| any later version.
 | |
| 
 | |
| GNU Classpath is distributed in the hope that it will be useful, but
 | |
| WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
| General Public License for more details.
 | |
| 
 | |
| You should have received a copy of the GNU General Public License
 | |
| along with GNU Classpath; see the file COPYING.  If not, write to the
 | |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 | |
| 02110-1301 USA.
 | |
| 
 | |
| Linking this library statically or dynamically with other modules is
 | |
| making a combined work based on this library.  Thus, the terms and
 | |
| conditions of the GNU General Public License cover the whole
 | |
| combination.
 | |
| 
 | |
| As a special exception, the copyright holders of this library give you
 | |
| permission to link this library with independent modules to produce an
 | |
| executable, regardless of the license terms of these independent
 | |
| modules, and to copy and distribute the resulting executable under
 | |
| terms of your choice, provided that you also meet, for each linked
 | |
| independent module, the terms and conditions of the license of that
 | |
| module.  An independent module is a module which is not derived from
 | |
| or based on this library.  If you modify this library, you may extend
 | |
| this exception to your version of the library, but you are not
 | |
| obligated to do so.  If you do not wish to do so, delete this
 | |
| exception statement from your version. */
 | |
| 
 | |
| 
 | |
| package gnu.xml.util;
 | |
| 
 | |
| import gnu.java.lang.CPStringBuilder;
 | |
| 
 | |
| 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;
 | |
| 
 | |
| /**
 | |
|  * Packages <a href=
 | |
|     "http://www.oasis-open.org/committees/entity/spec-2001-08-06.html"
 | |
|     >OASIS XML Catalogs</a>,
 | |
|  * primarily for entity resolution by parsers.
 | |
|  * That specification defines an XML syntax for mappings between
 | |
|  * identifiers declared in DTDs (particularly PUBLIC identifiers) and
 | |
|  * locations.  SAX has always supported such mappings, but conventions for
 | |
|  * an XML file syntax to maintain them have previously been lacking.
 | |
|  *
 | |
|  * <p> This has three main operational modes.  The primary intended mode is
 | |
|  * to create a resolver, then preloading it with one or more site-standard
 | |
|  * catalogs before using it with one or more SAX parsers: <pre>
 | |
|  *      XCat    catalog = new XCat ();
 | |
|  *      catalog.setErrorHandler (diagnosticErrorHandler);
 | |
|  *      catalog.loadCatalog ("file:/local/catalogs/catalog.cat");
 | |
|  *      catalog.loadCatalog ("http://shared/catalog.cat");
 | |
|  *      ...
 | |
|  *      catalog.disableLoading ();
 | |
|  *      parser1.setEntityResolver (catalog);
 | |
|  *      parser2.setEntityResolver (catalog);
 | |
|  *      ...</pre>
 | |
|  *
 | |
|  * <p>A second mode is to arrange that your application uses instances of
 | |
|  * this class as its entity resolver, and automatically loads catalogs
 | |
|  * referenced by <em><?oasis-xml-catalog...?></em> processing
 | |
|  * instructions found before the DTD in documents it parses.
 | |
|  * It would then discard the resolver after each parse.
 | |
|  *
 | |
|  * <p> A third mode applies catalogs in contexts other than entity
 | |
|  * resolution for parsers.
 | |
|  * The {@link #resolveURI resolveURI()} method supports resolving URIs
 | |
|  * stored in XML application data, rather than inside DTDs.
 | |
|  * Catalogs would be loaded as shown above, and the catalog could
 | |
|  * be used concurrently for parser entity resolution and for
 | |
|  * application URI resolution.
 | |
|  * </p>
 | |
|  *
 | |
|  * <center><hr width='70%'></center>
 | |
|  *
 | |
|  * <p>Errors in catalogs implicitly loaded (during resolution) are ignored
 | |
|  * beyond being reported through any <em>ErrorHandler</em> assigned using
 | |
|  * {@link #setErrorHandler setErrorHandler()}.  SAX exceptions
 | |
|  * thrown from such a handler won't abort resolution, although throwing a
 | |
|  * <em>RuntimeException</em> or <em>Error</em> will normally abort both
 | |
|  * resolution and parsing.  Useful diagnostic information is available to
 | |
|  * any <em>ErrorHandler</em> used to report problems, or from any exception
 | |
|  * thrown from an explicit {@link #loadCatalog loadCatalog()} invocation.
 | |
|  * Applications can use that information as troubleshooting aids.
 | |
|  *
 | |
|  * <p>While this class requires <em>SAX2 Extensions 1.1</em> classes in
 | |
|  * its class path, basic functionality does not require using a SAX2
 | |
|  * parser that supports the extended entity resolution functionality.
 | |
|  * See the original SAX1
 | |
|  * {@link #resolveEntity(java.lang.String,java.lang.String) resolveEntity()}
 | |
|  * method for a list of restrictions which apply when it is used with
 | |
|  * older SAX parsers.
 | |
|  *
 | |
|  * @see EntityResolver2
 | |
|  *
 | |
|  * @author David Brownell
 | |
|  */
 | |
| 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;
 | |
| 
 | |
|     // private EntityResolver   next;   // chain to next if we fail...
 | |
| 
 | |
|     //
 | |
|     // NOTE:  This is a straightforward implementation, and if
 | |
|     // there are lots of "nextCatalog" or "delegate*" entries
 | |
|     // in use, two tweaks would be worth considering:
 | |
|     //
 | |
|     //  - Centralize some sort of cache (key by URI) for individual
 | |
|     //    resolvers.  That'd avoid multiple copies of a given catalog.
 | |
|     //
 | |
|     //  - Have resolution track what catalogs (+modes) have been
 | |
|     //    searched.  This would support loop detection.
 | |
|     //
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * Initializes without preloading a catalog.
 | |
|      * This API is convenient when you may want to arrange that catalogs
 | |
|      * are automatically loaded when explicitly referenced in documents,
 | |
|      * using the <em>oasis-xml-catalog</em> processing instruction.
 | |
|      * In such cases you won't usually be able to preload catalogs.
 | |
|      */
 | |
|     public XCat () { }
 | |
| 
 | |
|     /**
 | |
|      * Initializes, and preloads a catalog using the default SAX parser.
 | |
|      * This API is convenient when you operate with one or more standard
 | |
|      * catalogs.
 | |
|      *
 | |
|      * <p> This just delegates to {@link #loadCatalog loadCatalog()};
 | |
|      * see it for exception information.
 | |
|      *
 | |
|      * @param uri absolute URI for the catalog file.
 | |
|      */
 | |
|     public XCat (String uri)
 | |
|     throws SAXException, IOException
 | |
|         { loadCatalog (uri); }
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * Loads an OASIS XML Catalog.
 | |
|      * It is appended to the list of currently active catalogs, or
 | |
|      * reloaded if a catalog with the same URI was already loaded.
 | |
|      * Callers have control over what parser is used, how catalog parsing
 | |
|      * errors are reported, and whether URIs will be resolved consistently.
 | |
|      *
 | |
|      * <p> The OASIS specification says that errors detected when loading
 | |
|      * catalogs "must recover by ignoring the catalog entry file that
 | |
|      * failed, and proceeding."  In this API, that action can be the
 | |
|      * responsibility of applications, when they explicitly load any
 | |
|      * catalog using this method.
 | |
|      *
 | |
|      * <p>Note that catalogs referenced by this one will not be loaded
 | |
|      * at this time.  Catalogs referenced through <em>nextCatalog</em>
 | |
|      * or <em>delegate*</em> elements are normally loaded only if needed.
 | |
|      *
 | |
|      * @see #setErrorHandler
 | |
|      * @see #setParserClass
 | |
|      * @see #setUnified
 | |
|      *
 | |
|      * @param uri absolute URI for the catalog file.
 | |
|      *
 | |
|      * @exception IOException As thrown by the parser, typically to
 | |
|      *  indicate problems reading data from that URI.
 | |
|      * @exception SAXException As thrown by the parser, typically to
 | |
|      *  indicate problems parsing data from that URI.  It may also
 | |
|      *  be thrown if the parser doesn't support necessary handlers.
 | |
|      * @exception IllegalStateException When attempting to load a
 | |
|      *  catalog after loading has been {@link #disableLoading disabled},
 | |
|      *  such as after any entity or URI lookup has been performed.
 | |
|      */
 | |
|     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) {
 | |
|             // maybe just reload
 | |
|             for (index = 0; index < catalogs.length; index++)
 | |
|                 if (uri.equals (catalogs [index].catalogURI))
 | |
|                     break;
 | |
|         }
 | |
|         catalog = loadCatalog (parserClass, errorHandler, uri, unified);
 | |
| 
 | |
|         // add to list of catalogs
 | |
|         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;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * "New Style" external entity resolution for parsers.
 | |
|      * Calls to this method prevent explicit loading of additional catalogs
 | |
|      * using {@link #loadCatalog loadCatalog()}.
 | |
|      *
 | |
|      * <p>This supports the full core catalog functionality for locating
 | |
|      * (and relocating) parsed entities that have been declared in a
 | |
|      * document's DTD.
 | |
|      *
 | |
|      * @param name Entity name, such as "dudley", "%nell", or "[dtd]".
 | |
|      * @param publicId Either a normalized public ID, or null.
 | |
|      * @param baseURI Absolute base URI associated with systemId.
 | |
|      * @param systemId URI found in entity declaration (may be
 | |
|      *  relative to baseURI).
 | |
|      *
 | |
|      * @return Input source for accessing the external entity, or null
 | |
|      *  if no mapping was found.  The input source may have opened
 | |
|      *  the stream, and will have a fully resolved URI.
 | |
|      *
 | |
|      * @see #getExternalSubset
 | |
|      */
 | |
|     public InputSource resolveEntity (
 | |
|         String name,            // UNUSED ... systemId is always non-null
 | |
|         String publicId,
 | |
|         String baseURI,         // UNUSED ... it just lets sysId be relative
 | |
|         String systemId
 | |
|     ) throws SAXException, IOException
 | |
|     {
 | |
|         if (loadingPermitted)
 | |
|             disableLoading ();
 | |
| 
 | |
|         try {
 | |
|             // steps as found in OASIS XML catalog spec 7.1.2
 | |
|             // steps 1, 8 involve looping over the list of catalogs
 | |
|             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) {
 | |
|             // done!
 | |
|         }
 | |
|         // step 9 involves returning "no match"
 | |
|         return null;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * "New Style" parser callback to add an external subset.
 | |
|      * For documents that don't include an external subset, this may
 | |
|      * return one according to <em>doctype</em> catalog entries.
 | |
|      * (This functionality is not a core part of the OASIS XML Catalog
 | |
|      * specification, though it's presented in an appendix.)
 | |
|      * If no such entry is defined, this returns null to indicate that
 | |
|      * this document will not be modified to include such a subset.
 | |
|      * Calls to this method prevent explicit loading of additional catalogs
 | |
|      * using {@link #loadCatalog loadCatalog()}.
 | |
|      *
 | |
|      * <p><em>Warning:</em> That catalog functionality can be dangerous.
 | |
|      * It can provide definitions of general entities, and thereby mask
 | |
|      * certain well formedess errors.
 | |
|      *
 | |
|      * @param name Name of the document element, either as declared in
 | |
|      *  a DOCTYPE declaration or as observed in the text.
 | |
|      * @param baseURI Document's base URI (absolute).
 | |
|      *
 | |
|      * @return Input source for accessing the external subset, or null
 | |
|      *  if no mapping was found.  The input source may have opened
 | |
|      *  the stream, and will have a fully resolved URI.
 | |
|      */
 | |
|     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) {
 | |
|             // done!
 | |
|         }
 | |
|         return null;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * "Old Style" external entity resolution for parsers.
 | |
|      * This API provides only core functionality.
 | |
|      * Calls to this method prevent explicit loading of additional catalogs
 | |
|      * using {@link #loadCatalog loadCatalog()}.
 | |
|      *
 | |
|      * <p>The functional limitations of this interface include:</p><ul>
 | |
|      *
 | |
|      *  <li>Since system IDs will be absolutized before the resolver
 | |
|      *  sees them, matching against relative URIs won't work.
 | |
|      *  This may affect <em>system</em>, <em>rewriteSystem</em>,
 | |
|      *  and <em>delegateSystem</em> catalog entries.
 | |
|      *
 | |
|      *  <li>Because of that absolutization, documents declaring entities
 | |
|      *  with system IDs using URI schemes that the JVM does not recognize
 | |
|      *  may be unparsable.  URI schemes such as <em>file:/</em>,
 | |
|      *  <em>http://</em>, <em>https://</em>, and <em>ftp://</em>
 | |
|      *  will usually work reliably.
 | |
|      *
 | |
|      *  <li>Because missing external subsets can't be provided, the
 | |
|      *  <em>doctype</em> catalog entries will be ignored.
 | |
|      *  (The {@link #getExternalSubset getExternalSubset()} method is
 | |
|      *  a "New Style" resolution option.)
 | |
|      *
 | |
|      *  </ul>
 | |
|      *
 | |
|      * <p>Applications can tell whether this limited functionality will be
 | |
|      * used: if the feature flag associated with the {@link EntityResolver2}
 | |
|      * interface is not <em>true</em>, the limitations apply.  Applications
 | |
|      * can't usually know whether a given document and catalog will trigger
 | |
|      * those limitations.  The issue can only be bypassed by operational
 | |
|      * procedures such as not using catalogs or documents which involve
 | |
|      * those features.
 | |
|      *
 | |
|      * @param publicId Either a normalized public ID, or null
 | |
|      * @param systemId Always an absolute URI.
 | |
|      *
 | |
|      * @return Input source for accessing the external entity, or null
 | |
|      *  if no mapping was found.  The input source may have opened
 | |
|      *  the stream, and will have a fully resolved URI.
 | |
|      */
 | |
|     final public InputSource resolveEntity (String publicId, String systemId)
 | |
|     throws SAXException, IOException
 | |
|     {
 | |
|         return resolveEntity (null, publicId, null, systemId);
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * Resolves a URI reference that's not defined to the DTD.
 | |
|      * This is intended for use with URIs found in document text, such as
 | |
|      * <em>xml-stylesheet</em> processing instructions and in attribute
 | |
|      * values, where they are not recognized as URIs by XML parsers.
 | |
|      * Calls to this method prevent explicit loading of additional catalogs
 | |
|      * using {@link #loadCatalog loadCatalog()}.
 | |
|      *
 | |
|      * <p>This functionality is supported by the OASIS XML Catalog
 | |
|      * specification, but will never be invoked by an XML parser.
 | |
|      * It corresponds closely to functionality for mapping system
 | |
|      * identifiers for entities declared in DTDs; closely enough that
 | |
|      * this implementation's default behavior is that they be
 | |
|      * identical, to minimize potential confusion.
 | |
|      *
 | |
|      * <p>This method could be useful when implementing the
 | |
|      * {@link javax.xml.transform.URIResolver} interface, wrapping the
 | |
|      * input source in a {@link javax.xml.transform.sax.SAXSource}.
 | |
|      *
 | |
|      * @see #isUnified
 | |
|      * @see #setUnified
 | |
|      *
 | |
|      * @param baseURI The relevant base URI as specified by the XML Base
 | |
|      *  specification.  This recognizes <em>xml:base</em> attributes
 | |
|      *  as overriding the actual (physical) base URI.
 | |
|      * @param uri Either an absolute URI, or one relative to baseURI
 | |
|      *
 | |
|      * @return Input source for accessing the mapped URI, or null
 | |
|      *  if no mapping was found.  The input source may have opened
 | |
|      *  the stream, and will have a fully resolved URI.
 | |
|      */
 | |
|     public InputSource resolveURI (String baseURI, String uri)
 | |
|     throws SAXException, IOException
 | |
|     {
 | |
|         if (loadingPermitted)
 | |
|             disableLoading ();
 | |
| 
 | |
|         // NOTE:  baseURI isn't used here, but caller MUST have it,
 | |
|         // and heuristics _might_ use it in the future ... plus,
 | |
|         // it's symmetric with resolveEntity ().
 | |
| 
 | |
|         // steps 1, 6 involve looping
 | |
|         try {
 | |
|             for (int i = 0; i < catalogs.length; i++) {
 | |
|                 InputSource     tmp = catalogs [i].resolveURI (uri);
 | |
|                 if (tmp != null)
 | |
|                     return tmp;
 | |
|             }
 | |
|         } catch (DoneDelegation x) {
 | |
|             // done
 | |
|         }
 | |
|         // step 7 reports no match
 | |
|         return null;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * Records that catalog loading is no longer permitted.
 | |
|      * Loading is automatically disabled when lookups are performed,
 | |
|      * and should be manually disabled when <em>startDTD()</em> (or
 | |
|      * any other DTD declaration callback) is invoked, or at the latest
 | |
|      * when the document root element is seen.
 | |
|      */
 | |
|     public synchronized void disableLoading ()
 | |
|     {
 | |
|         // NOTE:  this method and loadCatalog() are synchronized
 | |
|         // so that it's impossible to load (top level) catalogs
 | |
|         // after lookups start.  Likewise, deferred loading is also
 | |
|         // synchronized (for "next" and delegated catalogs) to
 | |
|         // ensure that parsers can share resolvers.
 | |
|         loadingPermitted = false;
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * Returns the error handler used to report catalog errors.
 | |
|      * Null is returned if the parser's default error handling
 | |
|      * will be used.
 | |
|      *
 | |
|      * @see #setErrorHandler
 | |
|      */
 | |
|     public ErrorHandler getErrorHandler ()
 | |
|         { return errorHandler; }
 | |
| 
 | |
|     /**
 | |
|      * Assigns the error handler used to report catalog errors.
 | |
|      * These errors may come either from the SAX2 parser or
 | |
|      * from the catalog parsing code driven by the parser.
 | |
|      *
 | |
|      * <p> If you're sharing the resolver between parsers, don't
 | |
|      * change this once lookups have begun.
 | |
|      *
 | |
|      * @see #getErrorHandler
 | |
|      *
 | |
|      * @param parser The error handler, or null saying to use the default
 | |
|      *  (no diagnostics, and only fatal errors terminate loading).
 | |
|      */
 | |
|     public void setErrorHandler (ErrorHandler handler)
 | |
|         { errorHandler = handler; }
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * Returns the name of the SAX2 parser class used to parse catalogs.
 | |
|      * Null is returned if the system default is used.
 | |
|      * @see #setParserClass
 | |
|      */
 | |
|     public String getParserClass ()
 | |
|         { return parserClass; }
 | |
| 
 | |
|     /**
 | |
|      * Names the SAX2 parser class used to parse catalogs.
 | |
|      *
 | |
|      * <p> If you're sharing the resolver between parsers, don't change
 | |
|      * this once lookups have begun.
 | |
|      *
 | |
|      * <p> Note that in order to properly support the <em>xml:base</em>
 | |
|      * attribute and relative URI resolution, the SAX parser used to parse
 | |
|      * the catalog must provide a {@link Locator} and support the optional
 | |
|      * declaration and lexical handlers.
 | |
|      *
 | |
|      * @see #getParserClass
 | |
|      *
 | |
|      * @param parser The parser class name, or null saying to use the
 | |
|      *  system default SAX2 parser.
 | |
|      */
 | |
|     public void setParserClass (String parser)
 | |
|         { parserClass = parser; }
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * Returns true (the default) if all methods resolve
 | |
|      * a given URI in the same way.
 | |
|      * Returns false if calls resolving URIs as entities (such as
 | |
|      * {@link #resolveEntity resolveEntity()}) use different catalog entries
 | |
|      * than those resolving them as URIs ({@link #resolveURI resolveURI()}),
 | |
|      * which will generally produce different results.
 | |
|      *
 | |
|      * <p>The OASIS XML Catalog specification defines two related schemes
 | |
|      * to map URIs "as URIs" or "as system IDs".
 | |
|      * URIs use <em>uri</em>, <em>rewriteURI</em>, and <em>delegateURI</em>
 | |
|      * elements.  System IDs do the same things with <em>systemId</em>,
 | |
|      * <em>rewriteSystemId</em>, and <em>delegateSystemId</em>.
 | |
|      * It's confusing and error prone to maintain two parallel copies of
 | |
|      * such data.  Accordingly, this class makes that behavior optional.
 | |
|      * The <em>unified</em> interpretation of URI mappings is preferred,
 | |
|      * since it prevents surprises where one URI gets mapped to different
 | |
|      * contents depending on whether the reference happens to have come
 | |
|      * from a DTD (or not).
 | |
|      *
 | |
|      * @see #setUnified
 | |
|      */
 | |
|     public boolean isUnified ()
 | |
|         { return unified; }
 | |
| 
 | |
|     /**
 | |
|      * Assigns the value of the flag returned by {@link #isUnified}.
 | |
|      * Set it to false to be strictly conformant with the OASIS XML Catalog
 | |
|      * specification.  Set it to true to make all mappings for a given URI
 | |
|      * give the same result, regardless of the reason for the mapping.
 | |
|      *
 | |
|      * <p>Don't change this once you've loaded the first catalog.
 | |
|      *
 | |
|      * @param value new flag setting
 | |
|      */
 | |
|     public void setUnified (boolean value)
 | |
|         { unified = value; }
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * Returns true (the default) if a catalog's public identifier
 | |
|      * mappings will be used.
 | |
|      * When false is returned, such mappings are ignored except when
 | |
|      * system IDs are discarded, such as for
 | |
|      * entities using the <em>urn:publicid:</em> URI scheme in their
 | |
|      * system identifiers.  (See RFC 3151 for information about that
 | |
|      * URI scheme.  Using it in system identifiers may not work well
 | |
|      * with many SAX parsers unless the <em>resolve-dtd-uris</em>
 | |
|      * feature flag is set to false.)
 | |
|      * @see #setUsingPublic
 | |
|      */
 | |
|     public boolean isUsingPublic ()
 | |
|         { return usingPublic; }
 | |
| 
 | |
|     /**
 | |
|      * Specifies which catalog search mode is used.
 | |
|      * By default, public identifier mappings are able to override system
 | |
|      * identifiers when both are available.
 | |
|      * Applications may choose to ignore public
 | |
|      * identifier mappings in such cases, so that system identifiers
 | |
|      * declared in DTDs will only be overridden by an explicit catalog
 | |
|      * match for that system ID.
 | |
|      *
 | |
|      * <p> If you're sharing the resolver between parsers, don't
 | |
|      * change this once lookups have begun.
 | |
|      * @see #isUsingPublic
 | |
|      *
 | |
|      * @param value true to always use public identifier mappings,
 | |
|      *  false to only use them for system ids using the <em>urn:publicid:</em>
 | |
|      *  URI scheme.
 | |
|      */
 | |
|     public void setUsingPublic (boolean value)
 | |
|         { usingPublic = value; }
 | |
| 
 | |
| 
 | |
| 
 | |
|     // hmm, what's this do? :)
 | |
|     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);
 | |
|         // resolve-dtd-entities is at default value (unrecognized == true)
 | |
| 
 | |
|         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;
 | |
|     }
 | |
| 
 | |
|     // perform one or both the normalizations for public ids
 | |
|     private static String normalizePublicId (boolean full, String publicId)
 | |
|     {
 | |
|         if (publicId.startsWith ("urn:publicid:")) {
 | |
|             CPStringBuilder     buf = new CPStringBuilder ();
 | |
|             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 '%':
 | |
| // FIXME unhex that char!  meanwhile, warn and fallthrough ...
 | |
|                     hasbug = true;
 | |
|                 default:        buf.append (chars [i]); continue;
 | |
|                 }
 | |
|             }
 | |
|             publicId = buf.toString ();
 | |
| if (hasbug)
 | |
| System.err.println ("nyet unhexing public id: " + publicId);
 | |
|             full = true;
 | |
|         }
 | |
| 
 | |
|         // SAX parsers do everything except that URN mapping, but
 | |
|         // we can't trust other sources to normalize correctly
 | |
|         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;
 | |
|     }
 | |
| 
 | |
|     // handles URIs with "excluded" characters
 | |
|     private static String normalizeURI (String systemId)
 | |
|     {
 | |
|         int                     length = systemId.length ();
 | |
| 
 | |
|         for (int i = 0; i < length; i++) {
 | |
|             char        c = systemId.charAt (i);
 | |
| 
 | |
|             // escape non-ASCII plus "excluded" characters
 | |
|             if (isUriExcluded (c)) {
 | |
|                 byte                    buf [];
 | |
|                 ByteArrayOutputStream   out;
 | |
|                 int                             b;
 | |
| 
 | |
|                 // a JVM that doesn't know UTF8 and 8859_1 is unusable!
 | |
|                 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;
 | |
|     }
 | |
| 
 | |
|     // thrown to mark authoritative end of a search
 | |
|     private static class DoneDelegation extends SAXException
 | |
|     {
 | |
|         DoneDelegation () { }
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * Represents a OASIS XML Catalog, and encapsulates much of
 | |
|      * the catalog functionality.
 | |
|      */
 | |
|     private static class Catalog
 | |
|     {
 | |
|         // loading infrastructure
 | |
|         String          catalogURI;
 | |
|         ErrorHandler    eh;
 | |
|         boolean         unified;
 | |
|         String          parserClass;
 | |
| 
 | |
|         // catalog data
 | |
|         boolean         hasPreference;
 | |
|         boolean         usingPublic;
 | |
| 
 | |
|         Hashtable       publicIds;
 | |
|         Hashtable       publicDelegations;
 | |
| 
 | |
|         Hashtable       systemIds;
 | |
|         Hashtable       systemRewrites;
 | |
|         Hashtable       systemDelegations;
 | |
| 
 | |
|         Hashtable       uris;
 | |
|         Hashtable       uriRewrites;
 | |
|         Hashtable       uriDelegations;
 | |
| 
 | |
|         Hashtable       doctypes;
 | |
| 
 | |
|         Vector          next;
 | |
| 
 | |
|         // nonpublic!
 | |
|         Catalog () { }
 | |
| 
 | |
| 
 | |
|         // steps as found in OASIS XML catalog spec 7.1.2
 | |
|         private InputSource locatePublicId (String publicId)
 | |
|         throws SAXException, IOException
 | |
|         {
 | |
|             // 5. return (first) 'public' entry
 | |
|             if (publicIds != null) {
 | |
|                 String  retval = (String) publicIds.get (publicId);
 | |
|                 if (retval != null) {
 | |
|                     // IF the URI is accessible ...
 | |
|                     return new InputSource (retval);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // 6. return delegatePublic catalog match [complex]
 | |
|             if (publicDelegations != null)
 | |
|                 return checkDelegations (publicDelegations, publicId,
 | |
|                                 publicId, null);
 | |
| 
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         // steps as found in OASIS XML catalog spec 7.1.2 or 7.2.2
 | |
|         private InputSource mapURI (
 | |
|             String      uri,
 | |
|             Hashtable   ids,
 | |
|             Hashtable   rewrites,
 | |
|             Hashtable   delegations
 | |
|         ) throws SAXException, IOException
 | |
|         {
 | |
|             // 7.1.2: 2. return (first) 'system' entry
 | |
|             // 7.2.2: 2. return (first) 'uri' entry
 | |
|             if (ids != null) {
 | |
|                 String  retval = (String) ids.get (uri);
 | |
|                 if (retval != null) {
 | |
|                     // IF the URI is accessible ...
 | |
|                     return new InputSource (retval);
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // 7.1.2: 3. return 'rewriteSystem' entries
 | |
|             // 7.2.2: 3. return 'rewriteURI' entries
 | |
|             if (rewrites != null) {
 | |
|                 String  prefix = null;
 | |
|                 String  replace = null;
 | |
|                 int     prefixLen = -1;
 | |
| 
 | |
|                 for (Enumeration e = rewrites.keys ();
 | |
|                         e.hasMoreElements ();
 | |
|                         /* NOP */) {
 | |
|                     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) {
 | |
|                     CPStringBuilder     buf = new CPStringBuilder (replace);
 | |
|                     buf.append (uri.substring (prefixLen));
 | |
|                     // IF the URI is accessible ...
 | |
|                     return new InputSource (buf.toString ());
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // 7.1.2: 4. return 'delegateSystem' catalog match [complex]
 | |
|             // 7.2.2: 4. return 'delegateURI' catalog match [complex]
 | |
|             if (delegations != null)
 | |
|                 return checkDelegations (delegations, uri, null, uri);
 | |
| 
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /**
 | |
|          * Returns a URI for an external entity.
 | |
|          */
 | |
|         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);
 | |
| 
 | |
|             // behavior here matches section 7.1.1 of the oasis spec
 | |
|             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)) {
 | |
|                         // error; ok to recover by:
 | |
|                         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;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // 7. apply nextCatalog entries
 | |
|             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;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Maps one URI into another, for resources that are not defined
 | |
|          * using XML external entity or notation syntax.
 | |
|          */
 | |
|         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);
 | |
| 
 | |
|             // 7.2.2 steps 2-4
 | |
|             retval = mapURI (uri, uris, uriRewrites, uriDelegations);
 | |
|             if (retval != null)
 | |
|                 return retval;
 | |
| 
 | |
|             // 7.2.2 step 5. apply nextCatalog entries
 | |
|             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;
 | |
|         }
 | |
| 
 | |
| 
 | |
|         /**
 | |
|          * Finds the external subset associated with a given root element.
 | |
|          */
 | |
|         public InputSource getExternalSubset (String name)
 | |
|         throws SAXException, IOException
 | |
|         {
 | |
|             if (doctypes != null) {
 | |
|                 String  value = (String) doctypes.get (name);
 | |
|                 if (value != null) {
 | |
|                     // IF the URI is accessible ...
 | |
|                     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;
 | |
| 
 | |
|             // ok, we deferred reading that catalog till now.
 | |
|             // load and cache it.
 | |
|             Catalog     cat = null;
 | |
| 
 | |
|             try {
 | |
|                 cat = loadCatalog (parserClass, eh, (String) obj, unified);
 | |
|                 next.setElementAt (cat, i);
 | |
|             } catch (SAXException e) {
 | |
|                 // must fail quietly, says the OASIS spec
 | |
|             } catch (IOException e) {
 | |
|                 // same applies here
 | |
|             }
 | |
|             return cat;
 | |
|         }
 | |
| 
 | |
|         private InputSource checkDelegations (
 | |
|             Hashtable   delegations,
 | |
|             String      id,
 | |
|             String      publicId,       // only one of public/system
 | |
|             String      systemId        // will be non-null...
 | |
|         ) throws SAXException, IOException
 | |
|         {
 | |
|             Vector      matches = null;
 | |
|             int         length = 0;
 | |
| 
 | |
|             // first, see if any prefixes match.
 | |
|             for (Enumeration e = delegations.keys ();
 | |
|                     e.hasMoreElements ();
 | |
|                     /* NOP */) {
 | |
|                 String  prefix = (String) e.nextElement ();
 | |
| 
 | |
|                 if (!id.startsWith (prefix))
 | |
|                     continue;
 | |
|                 if (matches == null)
 | |
|                     matches = new Vector ();
 | |
| 
 | |
|                 // maintain in longer->shorter sorted order
 | |
|                 // NOTE:  assumes not many matches will fire!
 | |
|                 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;
 | |
| 
 | |
|             // now we know the list of catalogs to replace our "top level"
 | |
|             // list ... we use it here, rather than somehow going back and
 | |
|             // restarting, since this helps avoid reading most catalogs.
 | |
|             // this assumes stackspace won't be a problem.
 | |
|             for (int i = 0; i < length; i++) {
 | |
|                 Catalog         catalog = null;
 | |
|                 InputSource     result;
 | |
| 
 | |
|                 // get this catalog.  we may not have read it yet.
 | |
|                 synchronized (delegations) {
 | |
|                     Object      prefix = matches.elementAt (i);
 | |
|                     Object      cat = delegations.get (prefix);
 | |
| 
 | |
|                     if (cat instanceof Catalog)
 | |
|                         catalog = (Catalog) cat;
 | |
|                     else {
 | |
|                         try {
 | |
|                             // load and cache that catalog
 | |
|                             catalog = loadCatalog (parserClass, eh,
 | |
|                                     (String) cat, unified);
 | |
|                             delegations.put (prefix, catalog);
 | |
|                         } catch (SAXException e) {
 | |
|                             // must ignore, says the OASIS spec
 | |
|                         } catch (IOException e) {
 | |
|                             // same applies here
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 // ignore failed loads, and proceed
 | |
|                 if (catalog == null)
 | |
|                     continue;
 | |
| 
 | |
|                 // we have a catalog ... resolve!
 | |
|                 // usingPublic value can't matter, there's no choice
 | |
|                 result = catalog.resolve (true, publicId, systemId);
 | |
|                 if (result != null)
 | |
|                     return result;
 | |
|             }
 | |
| 
 | |
|             // if there were no successes, the entire
 | |
|             // lookup failed (all the way to top level)
 | |
|             throw new DoneDelegation ();
 | |
|         }
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /** This is the namespace URI used for OASIS XML Catalogs.  */
 | |
|     private static final String catalogNamespace =
 | |
|         "urn:oasis:names:tc:entity:xmlns:xml:catalog";
 | |
| 
 | |
| 
 | |
|     /**
 | |
|      * Loads/unmarshals one 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 ();
 | |
| 
 | |
| 
 | |
|         /**
 | |
|          * Constructor.
 | |
|          * @param flag true iff the parser already interns strings.
 | |
|          * @param eh Errors and warnings are delegated to this.
 | |
|          * @param unified true keeps one table for URI mappings;
 | |
|          *      false matches OASIS spec, storing mappings
 | |
|          *      for URIs and SYSTEM ids in parallel tables.
 | |
|          */
 | |
|         Loader (boolean flag, ErrorHandler eh, boolean unified)
 | |
|         {
 | |
|             preInterned = flag;
 | |
|             handler = eh;
 | |
|             this.unified = unified;
 | |
|             cat.unified = unified;
 | |
|             cat.eh = eh;
 | |
|         }
 | |
| 
 | |
| 
 | |
|         // strips out fragments
 | |
|         private String nofrag (String uri)
 | |
|         throws SAXException
 | |
|         {
 | |
|             if (uri.indexOf ('#') != -1) {
 | |
|                 warn ("URI with fragment: " + uri);
 | |
|                 uri = uri.substring (0, uri.indexOf ('#'));
 | |
|             }
 | |
|             return uri;
 | |
|         }
 | |
| 
 | |
|         // absolutizes relative URIs
 | |
|         private String absolutize (String uri)
 | |
|         throws SAXException
 | |
|         {
 | |
|             // avoid creating URLs if they're already absolutized,
 | |
|             // or if the URI is already using a known scheme
 | |
|             if (uri.startsWith ("file:/")
 | |
|                     || uri.startsWith ("http:/")
 | |
|                     || uri.startsWith ("https:/")
 | |
|                     || uri.startsWith ("ftp:/")
 | |
|                     || uri.startsWith ("urn:")
 | |
|                     )
 | |
|                 return uri;
 | |
| 
 | |
|             // otherwise, let's hope the JDK handles this URI scheme.
 | |
|             try {
 | |
|                 URL     base = (URL) bases.peek ();
 | |
|                 return new URL (base, uri).toString ();
 | |
|             } catch (Exception e) {
 | |
|                 fatal ("can't absolutize URI: " + uri);
 | |
|                 return null;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // recoverable error
 | |
|         private void error (String message)
 | |
|         throws SAXException
 | |
|         {
 | |
|             if (handler == null)
 | |
|                 return;
 | |
|             handler.error (new SAXParseException (message, locator));
 | |
|         }
 | |
| 
 | |
|         // nonrecoverable error
 | |
|         private void fatal (String message)
 | |
|         throws SAXException
 | |
|         {
 | |
|             SAXParseException   spe;
 | |
| 
 | |
|             spe = new SAXParseException (message, locator);
 | |
|             if (handler != null)
 | |
|                 handler.fatalError (spe);
 | |
|             throw spe;
 | |
|         }
 | |
| 
 | |
|         // low severity problem
 | |
|         private void warn (String message)
 | |
|         throws SAXException
 | |
|         {
 | |
|             if (handler == null)
 | |
|                 return;
 | |
|             handler.warning (new SAXParseException (message, locator));
 | |
|         }
 | |
| 
 | |
|         // callbacks:
 | |
| 
 | |
|         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;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // XML Base support for external entities.
 | |
| 
 | |
|         // NOTE: expects parser is in default "resolve-dtd-uris" mode.
 | |
|         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);
 | |
| 
 | |
|             // NOTE: breaks if an EntityResolver substitutes these URIs.
 | |
|             // If toplevel loader supports one, must intercept calls...
 | |
|             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 ();
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Processes catalog elements, saving their data.
 | |
|          */
 | |
|         public void startElement (String namespace, String local,
 | |
|             String qName, Attributes atts)
 | |
|         throws SAXException
 | |
|         {
 | |
|             // must ignore non-catalog elements, and their contents
 | |
|             if (ignoreDepth != 0 || !catalogNamespace.equals (namespace)) {
 | |
|                 ignoreDepth++;
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             // basic sanity checks
 | |
|             if (!preInterned)
 | |
|                 local = local.intern ();
 | |
|             if (!started) {
 | |
|                 started = true;
 | |
|                 if ("catalog" != local)
 | |
|                     fatal ("root element not 'catalog': " + local);
 | |
|             }
 | |
| 
 | |
|             // Handle any xml:base attribute
 | |
|             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 ());
 | |
| 
 | |
|             // fetch multi-element attributes, apply standard tweaks
 | |
|             // values (uri, catalog, rewritePrefix) get normalized too,
 | |
|             // as a precaution and since we may compare the values
 | |
|             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);
 | |
|                 // unmatchable <rewriteSystemId>, <delegateSystemId> elements
 | |
|                 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);
 | |
|                 // unmatchable <rewriteURI>, <delegateURI> elements
 | |
|                 if (uriStartString.startsWith ("urn:publicid:")) {
 | |
|                     error ("uriStartString is really a publicId!!");
 | |
|                     return;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // strictly speaking "group" and "catalog" shouldn't nest
 | |
|             // ... arbitrary restriction, no evident motivation
 | |
| 
 | |
| // FIXME stack "prefer" settings (two elements only!) and use
 | |
| // them to populate different public mapping/delegation tables
 | |
| 
 | |
|             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");
 | |
|                 }
 | |
| 
 | |
|             //
 | |
|             // PUBLIC ids:  cleanly set up for id substitution
 | |
|             //
 | |
|             } 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);
 | |
| 
 | |
| 
 | |
|             //
 | |
|             // SYSTEM ids:  need substitution due to operational issues
 | |
|             //
 | |
|             } 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);
 | |
| 
 | |
| 
 | |
|             //
 | |
|             // URI:  just like "system" ID support, except that
 | |
|             // fragment IDs are disallowed in "system" elements.
 | |
|             //
 | |
|             } 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);
 | |
| 
 | |
|             //
 | |
|             // NON-DELEGATING approach to modularity
 | |
|             //
 | |
|             } else if ("nextCatalog" == local) {
 | |
|                 if (catalog == null) {
 | |
|                     error ("expecting <nextCatalog catalog=.../>");
 | |
|                     return;
 | |
|                 }
 | |
|                 if (cat.next == null)
 | |
|                     cat.next = new Vector ();
 | |
|                 cat.next.addElement (catalog);
 | |
| 
 | |
|             //
 | |
|             // EXTENSIONS from appendix E
 | |
|             //
 | |
|             } 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);
 | |
| 
 | |
| 
 | |
|             //
 | |
|             // RESERVED ... ignore (like reserved attributes) but warn
 | |
|             //
 | |
|             } 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 ();
 | |
|         }
 | |
|     }
 | |
| }
 |