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 ();
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |