Project

General

Profile

« Previous | Next » 

Revision 71

Added by higgins over 24 years ago

Catalog files from arbortext added

View differences:

src/com/arbortext/catalog/InvalidCatalogEntryException.java
1
// InvalidCatalogEntryException.java - Bad Catalog entry type
2
// Written by Norman Walsh, nwalsh@arbortext.com
3
// NO WARRANTY! This class is in the public domain.
4
package com.arbortext.catalog;
5

  
6
/**
7
 * <p>Signal bad Catalog entry</p>
8
 *
9
 * <blockquote>
10
 * <em>This module, both source code and documentation, is in the
11
 * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
12
 * </blockquote>
13
 *
14
 * <p>This exception is thrown if an attempt is made to create
15
 * a CatalogEntry instance with the wrong number of arguments or
16
 * an obviously erroneous argument.</p>
17
 */
18
public class InvalidCatalogEntryException extends Exception {
19
    public InvalidCatalogEntryException() { super(); }
20
}
21

  
0 22

  
src/com/arbortext/catalog/UnknownCatalogFormatException.java
1
// UnknownCatalogFormatException.java - Unknown XML Catalog format
2
// Written by Norman Walsh, nwalsh@arbortext.com
3
// NO WARRANTY! This class is in the public domain.
4
package com.arbortext.catalog;
5

  
6
/**
7
 * <p>Signal unknown XML Catalog format.</p>
8
 *
9
 * <blockquote>
10
 * <em>This module, both source code and documentation, is in the
11
 * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
12
 * </blockquote>
13
 *
14
 * <p>This exception is thrown if an XML Catalog is loaded and the
15
 * root element of the catalog file is unrecognized.</p>
16
 */
17
public class UnknownCatalogFormatException extends Exception {
18
    public UnknownCatalogFormatException() { super(); }
19
}
0 20

  
src/com/arbortext/catalog/InvalidCatalogEntryTypeException.java
1
// InvalidCatalogEntryException.java - Bad Catalog entry type
2
// Written by Norman Walsh, nwalsh@arbortext.com
3
// NO WARRANTY! This class is in the public domain.
4
package com.arbortext.catalog;
5

  
6
/**
7
 * <p>Signal bad Catalog entry type</p>
8
 *
9
 * <blockquote>
10
 * <em>This module, both source code and documentation, is in the
11
 * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
12
 * </blockquote>
13
 *
14
 * <p>This exception is thrown if an attempt is made to create
15
 * a CatalogEntry instance using an invalid entry type.</p>
16
 */
17
public class InvalidCatalogEntryTypeException extends Exception {
18
    public InvalidCatalogEntryTypeException() { super(); }
19
}
0 20

  
src/com/arbortext/catalog/CatalogEntityResolver.java
1
// CatalogEntityResolver.java - SAX entityResolver using OASIS Catalogs
2
// Written by Norman Walsh, nwalsh@arbortext.com
3
// NO WARRANTY! This class is in the public domain.
4

  
5
package com.arbortext.catalog;
6

  
7
import java.io.IOException;
8
import java.io.InputStream;
9
import java.lang.Integer;
10
import java.net.URL;
11
import java.net.MalformedURLException;
12

  
13
import com.arbortext.catalog.Catalog;
14
import org.xml.sax.EntityResolver;
15
import org.xml.sax.InputSource;
16

  
17
/**
18
 * <p>Implements SAX entityResolver using OASIS Open Catalogs.</p>
19
 *
20
 * <blockquote>
21
 * <em>This module, both source code and documentation, is in the
22
 * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
23
 * </blockquote>
24
 *
25
 * <p>This class implements the SAX entityResolver interface. It uses
26
 * OASIS Open catalog files to provide a facility for mapping public
27
 * or system identifiers in source documents to local system identifiers.
28
 *
29
 * <p>This code interrogates the following non-standard system properties:</p>
30
 *
31
 * <dl>
32
 * <dt><b>xml.catalog.debug</b></dt>
33
 * <dd><p>Sets the debug level. A value of 0 is assumed if the
34
 * property is not set or is not a number.</p></dd>
35
 * </dl>
36
 *
37
 * @see Catalog
38
 *
39
 * @author Arbortext, Inc.
40
 * @author Norman Walsh
41
 *         <a href="mailto:nwalsh@arbortext.com">nwalsh@arbortext.com</a>
42
 * @version 1.0
43
 */
44
public class CatalogEntityResolver implements EntityResolver {
45
    /**
46
     * <p>The debug level</p>
47
     *
48
     * <p>In general, higher numbers produce more information:</p>
49
     * <ul>
50
     * <li>0, no messages
51
     * <li>1, minimal messages (high-level status)
52
     * <li>2, detailed messages
53
     * </ul>
54
     */
55
    public int debug = 0;
56

  
57
    /**
58
     * <p>Indicates that unusable system identifiers should be ignored.</p>
59
     */
60
    private boolean retryBadSystemIds = false;
61

  
62
    /**
63
     * <p>The OASIS Open Catalog used for entity resolution.</p>
64
     *
65
     * <p>This field is exposed so that the catalog can be updated
66
     * after creating the instance of CatalogEntityResolver that will
67
     * be used by the parser.</p>
68
     */
69
    public Catalog catalog = null;
70

  
71
    /**
72
     * <p>Constructs a CatalogEntityResolver with an empty catalog.</p>
73
     */
74
    public CatalogEntityResolver() {
75
	String property = System.getProperty("xml.catalog.debug");
76

  
77
	if (property != null) {
78
	    try {
79
		debug = Integer.parseInt(property);
80
	    } catch (NumberFormatException e) {
81
		debug = 0;
82
	    }
83
	}
84
    }
85

  
86
    /**
87
     * <p>Set the Catalog that will be used to resolve entities.</p>
88
     *
89
     * <p>This is a convenience method for setting the
90
     * <a href="#catalog"><code>catalog</code></a> field.</p>
91
     */
92
    public void setCatalog(Catalog cat) {
93
	catalog = cat;
94
    }
95

  
96
    /**
97
     * <p>Parse a Catalog file.</p>
98
     *
99
     * <p>This is really just a convenience method which calls
100
     * <a href="#catalog"><code>catalog.parseCatalog()</code></a>.</p>
101
     *
102
     * @param fileName The filename of the catalog file to process
103
     *
104
     * @throws MalformedURLException The fileName cannot be turned into
105
     * a valid URL.
106
     * @throws IOException Error reading catalog file.
107
     */
108
    public synchronized void parseCatalog(String fileName)
109
	throws MalformedURLException, IOException {
110
	catalog.parseCatalog(fileName);
111
    }
112

  
113
    /**
114
     * <p>Establish whether or not bad system identifiers should be
115
     * ignored.</p>
116
     *
117
     * <p>The semantics of catalog file lookup are such that if a system
118
     * identifier is supplied in the instance document, it is possible
119
     * that it will be used in preference to alternative system identifiers
120
     * in the catalog.</p>
121
     *
122
     * <p>If this variable is <code>true</code> and the system identifier
123
     * passed to the entity resolver would be returned, the entity resolver
124
     * attempts to open it. If it cannot be opened, the resolver
125
     * does another catalog search,
126
     * ignoring the fact that a system identifier was specified. If this
127
     * second search locates a system identifer, it will be returned.</p>
128
     *
129
     * <p>This setting is initially <code>false</code> meaning that
130
     * system identifiers
131
     * in the document will be used in preference to some entries in
132
     * the catalog.</p>
133
     *
134
     * @param retry If true, the resolver will retry Catalog lookups when
135
     * the supplied system identifer cannot be opened.
136
     */
137
    public void setRetry(boolean retry) {
138
	retryBadSystemIds = retry;
139
    }
140

  
141
    /**
142
     * <p>Implements the <code>resolveEntity</code> method
143
     * for the SAX interface.</p>
144
     *
145
     * <p>Presented with an optional public identifier and a system
146
     * identifier, this function attempts to locate a mapping in the
147
     * catalogs.</p>
148
     *
149
     * <p>If such a mapping is found, the resolver attempts to open
150
     * the mapped value as an InputSource and return it. Exceptions are
151
     * ignored and null is returned if the mapped value cannot be opened
152
     * as an input source.</p>
153
     *
154
     * If no mapping is found (or an error occurs attempting to open
155
     * the mapped value as an input source), null is returned and the system
156
     * will use the specified system identifier as if no entityResolver
157
     * was specified.</p>
158
     *
159
     * @param publicId  The public identifier for the entity in question.
160
     * This may be null.
161
     *
162
     * @param systemId  The system identifier for the entity in question.
163
     * XML requires a system identifier on all external entities, so this
164
     * value is always specified.
165
     *
166
     * @return An InputSource for the mapped identifier, or null.
167
     */
168
    public InputSource resolveEntity (String publicId, String systemId) {
169
	String resolved = null;
170

  
171
	if (systemId != null) {
172
	    try {
173
		resolved = catalog.resolveSystem(systemId);
174
	    } catch (MalformedURLException me) {
175
		debug(1, "Malformed URL exception trying to resolve",
176
		      publicId);
177
		resolved = null;
178
	    } catch (IOException ie) {
179
		debug(1, "I/O exception trying to resolve", publicId);
180
		resolved = null;
181
	    }
182
	}
183

  
184
	if (resolved == null) {
185
	    if (publicId != null) {
186
		try {
187
		    resolved = catalog.resolvePublic(publicId, systemId);
188
		} catch (MalformedURLException me) {
189
		    debug(1, "Malformed URL exception trying to resolve",
190
			  publicId);
191
		} catch (IOException ie) {
192
		    debug(1, "I/O exception trying to resolve", publicId);
193
		}
194
	    }
195

  
196
	    if (resolved != null) {
197
		debug(2, "Resolved", publicId, resolved);
198
	    }
199
	} else {
200
	    debug(2, "Resolved", systemId, resolved);
201
	}
202

  
203
	if (resolved == null && retryBadSystemIds
204
	    && publicId != null && systemId != null) {
205
	    URL systemURL = null;
206
	    try {
207
		systemURL = new URL(systemId);
208
	    } catch (MalformedURLException e) {
209
		try {
210
		    systemURL = new URL("file:///" + systemId);
211
		} catch (MalformedURLException e2) {
212
		    systemURL = null;
213
		}
214
	    }
215

  
216
	    if (systemURL != null) {
217
		try {
218
		    InputStream iStream = systemURL.openStream();
219

  
220
		    // The systemId can be opened, so that's the one that
221
		    // we'll use. There's no point making the caller open
222
		    // it again though...
223

  
224
		    InputSource iSource = new InputSource(systemId);
225
		    iSource.setPublicId(publicId);
226
		    iSource.setByteStream(iStream);
227
		    return iSource;
228
		} catch (Exception e) {
229
		    // nop
230
		}
231
	    }
232

  
233
	    // we got here, so it must be that the systemId cannot be
234
	    // opened and the caller wants us to retry...
235
	    debug(2, "Failed to open", systemId);
236
	    debug(2, "\tAttempting catalog lookup without system identifier.");
237
	    return resolveEntity(publicId, null);
238
	}
239

  
240
	if (resolved != null) {
241
	    try {
242
		InputSource iSource = new InputSource(resolved);
243
		iSource.setPublicId(publicId);
244

  
245
		// Ideally this method would not attempt to open the
246
		// InputStream, but there is a bug (in Xerces, at least)
247
		// that causes the parser to mistakenly open the wrong
248
		// system identifier if the returned InputSource does
249
		// not have a byteStream.
250
		//
251
		// It could be argued that we still shouldn't do this here,
252
		// but since the purpose of calling the entityResolver is
253
		// almost certainly to open the input stream, it seems to
254
		// do little harm.
255
		//
256
		URL url = new URL(resolved);
257
		InputStream iStream = url.openStream();
258
		iSource.setByteStream(iStream);
259

  
260
		return iSource;
261
	    } catch (Exception e) {
262
		debug(1, "Failed to create InputSource", resolved);
263
		return null;
264
	    }
265
	}
266
	return null;
267
    }
268

  
269
    /**
270
     * <p>Print debug message (if the debug level is high enough).</p>
271
     *
272
     * @param level The debug level of this message. This message
273
     * will only be
274
     * displayed if the current debug level is at least equal to this
275
     * value.
276
     * @param message The text of the message.
277
     */
278
    private void debug(int level, String message) {
279
	if (debug >= level) {
280
	    System.out.println(message);
281
	}
282
    }
283

  
284
    /**
285
     * <p>Print debug message (if the debug level is high enough).</p>
286
     *
287
     * @param level The debug level of this message. This message
288
     * will only be
289
     * displayed if the current debug level is at least equal to this
290
     * value.
291
     * @param message The text of the message.
292
     * @param spec An argument to the message.
293
     */
294
    private void debug(int level, String message, String spec) {
295
	if (debug >= level) {
296
	    System.out.println(message + ": " + spec);
297
	}
298
    }
299

  
300
    /**
301
     * <p>Print debug message (if the debug level is high enough).</p>
302
     *
303
     * @param level The debug level of this message. This message
304
     * will only be
305
     * displayed if the current debug level is at least equal to this
306
     * value.
307
     * @param message The text of the message.
308
     * @param spec1 An argument to the message.
309
     * @param spec1 Another argument to the message.
310
     */
311
    private void debug(int level, String message, String spec1, String spec2) {
312
	if (debug >= level) {
313
	    System.out.println(message + ": " + spec1);
314
	    System.out.println("\t" + spec2);
315
	}
316
    }
317
}
0 318

  
src/com/arbortext/catalog/NoXMLParserException.java
1
// NoXMLParserException.java - Attempt to parse XML Catalog with no Parser
2
// Written by Norman Walsh, nwalsh@arbortext.com
3
// NO WARRANTY! This class is in the public domain.
4
package com.arbortext.catalog;
5

  
6
/**
7
 * <p>Signal attempt to parse an XML Catalog without a Parser class.</p>
8
 *
9
 * <blockquote>
10
 * <em>This module, both source code and documentation, is in the
11
 * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
12
 * </blockquote>
13
 *
14
 * <p>This exception is thrown if an attempt is made to load an XML
15
 * Catalog, but no Parser class has been provided.</p>
16
 *
17
 * @see XMLCatalogReader
18
 * @see XMLCatalogReader#setParserClass
19
 */
20
public class NoXMLParserException extends Exception {
21
    public NoXMLParserException() { super(); }
22
}
0 23

  
src/com/arbortext/catalog/Catalog.java
1
// Catalog.java - Represents OASIS Open Catalog files.
2

  
3
// Written by Norman Walsh, nwalsh@arbortext.com
4
// NO WARRANTY! This class is in the public domain.
5

  
6
package com.arbortext.catalog;
7

  
8
import java.lang.Integer;
9
import java.io.IOException;
10
import java.io.FileNotFoundException;
11
import java.util.Enumeration;
12
import java.util.Hashtable;
13
import java.util.Vector;
14
import java.net.URL;
15
import java.net.MalformedURLException;
16
import com.arbortext.catalog.CatalogReader;
17
import com.arbortext.catalog.XMLCatalogReader;
18
import com.arbortext.catalog.NotXMLCatalogException;
19
import com.arbortext.catalog.NoXMLParserException;
20
import org.xml.sax.SAXException;
21

  
22
/**
23
 * <p>Represents OASIS Open Catalog files.</p>
24
 *
25
 * <blockquote>
26
 * <em>This module, both source code and documentation, is in the
27
 * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
28
 * </blockquote>
29
 *
30
 * <p>This class loads one or more OASIS Open Catalog files
31
 * (defined by
32
 * <a href="http://www.oasis-open.org/html/a401.htm">OASIS Technical
33
 * Resolution 9401:1997 (Amendment 2 to TR 9401)</a>)
34
 * and provides
35
 * methods for implementing the Catalog semantics.</p>
36
 *
37
 * <p>The primary purpose of the Catalog is to associate resources in the
38
 * document with local system identifiers. Some entities
39
 * (document types, XML entities, and notations) have names and all of them
40
 * can have either public or system identifiers or both. (In XML, only a
41
 * notation can have a public identifier without a system identifier, but
42
 * the methods implemented in this class obey the Catalog semantics
43
 * from the SGML
44
 * days when system identifiers were optional.)</p>
45
 *
46
 * <p>The system identifiers returned by the resolution methods in this
47
 * class are valid, i.e. usable by, and in fact constructed by, the
48
 * <tt>java.net.URL</tt> class. Unfortunately, this class seems to behave in
49
 * somewhat non-standard ways and the system identifiers returned may
50
 * not be directly usable in a browser or filesystem context.
51
 *
52
 * <p>This class processes the following Catalog entries:</p>
53
 *
54
 * <ul>
55
 * <li><b>BASE</b>
56
 * changes the base URI for resolving relative system identifiers. The
57
 * initial base URI is the URI of the location of the catalog (which is,
58
 * in turn, relative to the location of the current working directory
59
 * at startup, as returned by the <tt>user.dir</tt> system property).</li>
60
 * <li><b>CATALOG</b>
61
 * processes other catalog files. An included catalog occurs logically
62
 * at the end of the including catalog.</li>
63
 * <li><b>DELEGATE</b>
64
 * specifies alternate catalogs for some public identifiers. The delegated
65
 * catalogs are not loaded until they are needed, but they are cached
66
 * once loaded.</li>
67
 * <li><b>DOCTYPE</b>
68
 * associates the names of root elements with URIs. (In other words, an XML
69
 * processor might infer the doctype of an XML document that does not include
70
 * a doctype declaration by looking for the DOCTYPE entry in the
71
 * catalog which matches the name of the root element of the document.)</li>
72
 * <li><b>DOCUMENT</b>
73
 * provides a default document.</li>
74
 * <li><b>DTDDECL</b>
75
 * recognized and silently ignored. Not relevant for XML.</li>
76
 * <li><b>ENTITY</b>
77
 * associates entity names with URIs.</li>
78
 * <li><b>LINKTYPE</b>
79
 * recognized and silently ignored. Not relevant for XML.</li>
80
 * <li><b>NOTATION</b>
81
 * associates notation names with URIs.</li>
82
 * <li><b>OVERRIDE</b>
83
 * changes the override behavior. Initial behavior is set by the
84
 * system property <tt>xml.catalog.override</tt>. The default initial
85
 * behavior is 'YES', that is, entries in the catalog override
86
 * system identifiers specified in the document.</li>
87
 * <li><b>PUBLIC</b>
88
 * maps a public identifier to a system identifier.</li>
89
 * <li><b>SGMLDECL</b>
90
 * recognized and silently ignored. Not relevant for XML.</li>
91
 * <li><b>SYSTEM</b>
92
 * maps a system identifier to another system identifier.</li>
93
 * </ul>
94
 *
95
 * <p>Note that subordinate catalogs (all catalogs except the first,
96
 * including CATALOG and DELEGATE catalogs) are only loaded if and when
97
 * they are required.</p>
98
 *
99
 * <p>If provided with an SAX Parser class, this object can also load
100
 * XML Catalogs. For the details about which XML Catalog formats are
101
 * recognized, see {@link XMLCatalogReader}.
102
 *
103
 * <p>This code interrogates the following non-standard system properties:</p>
104
 *
105
 * <dl>
106
 * <dt><b>xml.catalog.debug</b></dt>
107
 * <dd><p>Sets the debug level. A value of 0 is assumed if the
108
 * property is not set or is not a number.</p></dd>
109
 *
110
 * <dt><b>xml.catalog.override</b></dt>
111
 * <dd><p>Specifies the default override behavior. If override is true ("true",
112
 * "yes", "1"), system identifiers in the catalog file are used in preference
113
 * to system identifiers in the document. In other words, a value of false
114
 * essentially disables catalog processing since almost all external
115
 * entities are required to have a system identifier in XML.
116
 * A value of true is assumed if the property is not set.</p></dd>
117
 *
118
 * <dt><b>xml.catalog.files</b></dt>
119
 * <dd><p>Identifies the list of catalog <i>files</i> to parse initially.
120
 * (Additional catalog files may be parsed if the CATALOG entry
121
 * is used.) Components of the list should be separated by the system
122
 * property "<code>path.separator</code>" character
123
 * (typically ";" on DOS/Windows systems, ":" on Unix systems).</p>
124
 *
125
 * <p>Additional catalogs may also be loaded with the
126
 * {@link #parseCatalog} method.</p>
127
 * </dd>
128
 * </dl>
129
 *
130
 * <p><b>Change Log:</b></p>
131
 * <dl>
132
 * <dt>1.0.1</dt>
133
 * <dd><p>Fixed a bug in the calculation of the list of subordinate catalogs.
134
 * This bug caused an infinite loop where parsing would alternately process
135
 * two catalogs indefinitely.</p>
136
 * </dd>
137
 * </dl>
138
 *
139
 * @see CatalogReader
140
 * @see XMLCatalogReader
141
 * @see CatalogEntry
142
 *
143
 * @author Abortext, Inc.
144
 * @author Norman Walsh
145
 *         <a href="mailto:nwalsh@arbortext.com">nwalsh@arbortext.com</a>
146
 * @version 1.0.1
147
 */
148
public class Catalog {
149
    /**
150
     * The base URI for relative system identifiers in the catalog.
151
     * This may be changed by BASE entries in the catalog.
152
     */
153
    private URL base;
154

  
155
    /**
156
     * The base URI of the Catalog file currently being parsed.
157
     */
158
    private URL catalogCwd;
159

  
160
    /** The catalog entries currently known to the system. */
161
    private Vector catalogEntries = new Vector();
162

  
163
    /** The default initial override setting. */
164
    private boolean default_override = true;
165

  
166
    /**
167
     * <p>The debug level.</p>
168
     *
169
     * <p>In general, higher numbers produce more information:</p>
170
     * <ul>
171
     * <li>0, no messages
172
     * <li>1, minimal messages (high-level status)
173
     * <li>2, more messages
174
     * <li>3, detailed messages
175
     * </ul>
176
     */
177
    public int debug = 0;
178

  
179
    /**
180
     * <p>A vector of catalog files to be loaded.</p>
181
     *
182
     * <p>This list is initially established by
183
     * <code>loadSystemCatalogs</code> when
184
     * it parses the system catalog list, but CATALOG entries may
185
     * contribute to it during the course of parsing.</p>
186
     *
187
     * @see #loadSystemCatalogs
188
     * @see localCatalogFiles
189
     */
190
    private Vector catalogFiles = new Vector();
191

  
192
    /**
193
     * <p>A vector of catalog files constructed during processing of
194
     * CATALOG entries in the current catalog.</p>
195
     *
196
     * <p>This two-level system is actually necessary to correctly implement
197
     * the semantics of the CATALOG entry. If one catalog file includes
198
     * another with a CATALOG entry, the included catalog logically
199
     * occurs <i>at the end</i> of the including catalog, and after any
200
     * preceding CATALOG entries. In other words, the CATALOG entry
201
     * cannot insert anything into the middle of a catalog file.</p>
202
     *
203
     * <p>When processing reaches the end of each catalog files, any
204
     * elements on this vector are added to the front of the
205
     * <code>catalogFiles</code> vector.</p>
206
     *
207
     * @see catalogFiles
208
     */
209
    private Vector localCatalogFiles = new Vector();
210

  
211
    /**
212
     * <p>A vector of Catalogs.</p>
213
     *
214
     * <p>The semantics of Catalog resolution are such that each
215
     * catalog is effectively a list of Catalogs (in other words,
216
     * a recursive list of Catalog instances).</p>
217
     *
218
     * <p>Catalogs that are processed as the result of CATALOG or
219
     * DELEGATE entries are subordinate to the catalog that contained
220
     * them, but they may in turn have subordinate catalogs.</p>
221
     *
222
     * <p>Catalogs are only loaded when they are needed, so this vector
223
     * initially contains a list of Catalog filenames (URLs). If, during
224
     * processing, one of these catalogs has to be loaded, the resulting
225
     * Catalog object is placed in the vector, effectively caching it
226
     * for the next query.</p>
227
     */
228
    private Vector catalogs = new Vector();
229

  
230
    /**
231
     * <p>A vector of DELEGATE Catalog entries constructed during
232
     * processing of the Catalog.</p>
233
     *
234
     * <p>This two-level system has two purposes; first, it allows
235
     * us to sort the DELEGATE entries by the length of the partial
236
     * public identifier so that a linear search encounters them in
237
     * the correct order and second, it puts them all at the end of
238
     * the Catalog.</p>
239
     *
240
     * <p>When processing reaches the end of each catalog file, any
241
     * elements on this vector are added to the end of the
242
     * <code>catalogEntries</code> vector. This assures that matching
243
     * PUBLIC keywords are encountered before DELEGATE entries.</p>
244
     */
245
    private Vector localDelegate = new Vector();
246

  
247
    /**
248
     * <p>The name of the parser class to load when parsing XML Catalogs.</p>
249
     *
250
     * <p>If a parser class is provided,
251
     * subsequent attempts to parse Catalog files will begin
252
     * by attemptiing an XML parse of the catalog file using a parser
253
     * of this class.
254
     * If the XML parse fails, the "default" text parse will be done
255
     * instead.</p>
256
     */
257
    private String parserClass = null;
258

  
259
    /**
260
     * <p>Constructs an empty Catalog.</p>
261
     *
262
     * <p>The constructor interrogates the relevant system properties
263
     * and initializes the catalog data structures.</p>
264
     */
265
    public Catalog() {
266
	String property = System.getProperty("xml.catalog.debug");
267

  
268
	if (property != null) {
269
	    try {
270
		debug = Integer.parseInt(property);
271
	    } catch (NumberFormatException e) {
272
		debug = 0;
273
	    }
274
	}
275

  
276
	property = System.getProperty("xml.catalog.override");
277

  
278
	if (property != null) {
279
	    default_override = (property.equalsIgnoreCase("true")
280
				|| property.equalsIgnoreCase("yes")
281
				|| property.equalsIgnoreCase("1"));
282
	}
283
    }
284

  
285
    /**
286
     * <p>Sets the parser class, enabling XML Catalog parsing.</p>
287
     *
288
     * <p>Sets the parser class that will be used for loading XML Catalogs.
289
     * If this method is not called, all catalogs will be parsed as
290
     * plain text (and assumed to conform to the
291
     * <a href="http://www.oasis-open.org/html/a401.htm">OASIS Catalog
292
     * format</a>).</p>
293
     *
294
     * @param parser The name of a class implementing the SAX Parser
295
     * interface to be used for subsequent XML Catalog parsing.
296
     */
297
    public void setParserClass(String parser) {
298
	parserClass = parser;
299
    }
300

  
301
    /**
302
     * <p>Load the system catalog files.</p>
303
     *
304
     * <p>The method adds all of the
305
     * catalogs specified in the <tt>xml.catalog.files</tt> property
306
     * to the Catalog list.</p>
307
     *
308
     * @throws MalformedURLException  One of the system catalogs is
309
     * identified with a filename that is not a valid URL.
310
     * @throws IOException One of the system catalogs cannot be read.
311
     */
312
    public void loadSystemCatalogs()
313
	throws MalformedURLException, IOException {
314
	String PCS = System.getProperty("path.separator");
315
	String catalog_files = System.getProperty("xml.catalog.files");
316

  
317
	while (catalog_files != null) {
318
	    int pos = catalog_files.indexOf(PCS);
319
	    String catfile = null;
320

  
321
	    if (pos > 0) {
322
		catfile = catalog_files.substring(0, pos);
323
		catalog_files = catalog_files.substring(pos+1);
324
	    } else {
325
		catfile = catalog_files;
326
		catalog_files = null;
327
	    }
328

  
329
	    catalogFiles.addElement(catfile);
330
	}
331

  
332
	if (catalogFiles.size() > 0) {
333
	    // This is a little odd. The parseCatalog() method expects
334
	    // a filename, but it adds that name to the end of the
335
	    // catalogFiles vector, and then processes that vector.
336
	    // This allows the system to handle CATALOG entries
337
	    // correctly.
338
	    //
339
	    // In this init case, we take the last element off the
340
	    // catalogFiles vector and pass it to parseCatalog. This
341
	    // will "do the right thing" in the init case, and allow
342
	    // parseCatalog() to do the right thing in the non-init
343
	    // case. Honest.
344
	    //
345
	    String catfile = (String) catalogFiles.lastElement();
346
	    catalogFiles.removeElement(catfile);
347
	    parseCatalog(catfile);
348
	}
349
    }
350

  
351
    /**
352
     * <p>Parse a catalog file, augmenting internal data structures</p>
353
     *
354
     * @param fileName The filename of the catalog file to process
355
     *
356
     * @throws MalformedURLException The fileName cannot be turned into
357
     * a valid URL.
358
     * @throws IOException Error reading catalog file.
359
     */
360
    public synchronized void parseCatalog(String fileName)
361
	throws MalformedURLException, IOException {
362

  
363
	// Put the file into the list of catalogs to process...
364
	// In all cases except the case when initCatalog() is the
365
	// caller, this will be the only catalog initially in the list...
366
	catalogFiles.addElement(fileName);
367

  
368
	// Now process all the files on the catalogFiles vector. This
369
	// vector can grow during processing if CATALOG entries are
370
	// encountered in the catalog
371
	int curCat = 0;
372
	while (curCat < catalogFiles.size()) {
373
	    String catfile = (String) catalogFiles.elementAt(curCat++);
374

  
375
	    if (catalogEntries.size() == 0 && catalogs.size() == 0) {
376
		// We haven't parsed any catalogs yet, let this
377
		// catalog be the first...
378
		parseCatalogFile(catfile);
379
	    } else {
380
		// This is a subordinate catalog. We save its name,
381
		// but don't bother to load it unless it's necessary.
382
		catalogs.addElement(catfile);
383
	    }
384

  
385
	    if (!localCatalogFiles.isEmpty()) {
386
		// Move all the localCatalogFiles into the front of
387
		// the catalogFiles queue
388
		Vector newQueue = new Vector();
389
		Enumeration q = localCatalogFiles.elements();
390
		while (q.hasMoreElements()) {
391
		    newQueue.addElement(q.nextElement());
392
		}
393

  
394
		// Put the rest of the catalogs on the end of the new list
395
		while (curCat < catalogFiles.size()) {
396
		    catfile = (String) catalogFiles.elementAt(curCat++);
397
		    newQueue.addElement(catfile);
398
		}
399

  
400
		localCatalogFiles = new Vector();
401
		catalogFiles = newQueue;
402
		curCat = 0;
403
	    }
404

  
405
	    if (!localDelegate.isEmpty()) {
406
		Enumeration e = localDelegate.elements();
407
		while (e.hasMoreElements()) {
408
		    catalogEntries.addElement(e.nextElement());
409
		}
410
		localDelegate = new Vector();
411
	    }
412
	}
413

  
414
	// We've parsed them all, reinit the vector...
415
	catalogFiles = new Vector();
416
    }
417

  
418
    /**
419
     * <p>Parse a single catalog file, augmenting internal data structures</p>
420
     *
421
     * @param fileName The filename of the catalog file to process
422
     *
423
     * @throws MalformedURLException The fileName cannot be turned into
424
     * a valid URL.
425
     * @throws IOException Error reading catalog file.
426
     */
427
    private synchronized void parseCatalogFile(String fileName)
428
	throws MalformedURLException, IOException {
429

  
430
	CatalogEntry entry;
431

  
432
	// The base-base is the cwd. If the catalog file is specified
433
	// with a relative path, this assures that it gets resolved
434
	// properly...
435
	try {
436
	    // tack on a basename because URLs point to files not dirs
437
	    String userdir = fixSlashes(System.getProperty("user.dir"));
438
	    catalogCwd = new URL("file:///" + userdir + "/basename");
439
	} catch (MalformedURLException e) {
440
	    String userdir = fixSlashes(System.getProperty("user.dir"));
441
	    debug(1, "Malformed URL on cwd", userdir);
442
	    catalogCwd = null;
443
	}
444

  
445
	// The initial base URI is the location of the catalog file
446
	try {
447
	    base = new URL(catalogCwd, fixSlashes(fileName));
448
	} catch (MalformedURLException e) {
449
	    try {
450
		base = new URL("file:///" + fixSlashes(fileName));
451
	    } catch (MalformedURLException e2) {
452
		debug(1, "Malformed URL on catalog filename", 
453
		      fixSlashes(fileName));
454
		base = null;
455
	    }
456
	}
457

  
458
	debug(1, "Loading catalog", fileName);
459
	debug(3, "Default BASE", base.toString());
460

  
461
	fileName = base.toString();
462

  
463
	if (parserClass != null) {
464
	    try {
465
		XMLCatalogReader catfile = new XMLCatalogReader();
466
		catfile.setParserClass(parserClass);
467
		catfile.parseCatalog(fileName);
468

  
469
		CatalogEntry ce = null;
470
		while ((ce = catfile.nextEntry()) != null) {
471
		    addEntry(ce);
472
		}
473
		return;
474
	    } catch (SAXException e1) {
475
		// not an XML catalog, continue with text parse
476
	    } catch (NoXMLParserException e2) {
477
		// not an XML catalog, continue with text parse
478
	    } catch (NotXMLCatalogException e2) {
479
		// not an XML catalog, continue with text parse
480
	    } catch (InstantiationException e3) {
481
		debug(1, "Cannot instantiate XML Parser class", parserClass);
482
	    } catch (IllegalAccessException e4) {
483
		debug(1, "Cannot access XML Parser class", parserClass);
484
	    } catch (ClassNotFoundException e5) {
485
		debug(1, "Cannot load XML Parser class", parserClass);
486
	    } catch (UnknownCatalogFormatException e6) {
487
		debug(1, "Unrecognized XML Catalog format.");
488
		return;
489
	    }
490
	}
491

  
492
	CatalogReader catfile = new CatalogReader();
493
	catfile.parseCatalog(fileName);
494

  
495
	// Process the contents of the catalog file as a whitespace
496
	// delimited set of tokens
497
	while ((entry = catfile.nextEntry()) != null) {
498
	    addEntry(entry);
499
	}
500
    }
501

  
502
    /**
503
     * <p>Cleanup and process a Catalog entry.</p>
504
     *
505
     * <p>This method processes each Catalog entry, changing mapped
506
     * relative system identifiers into absolute ones (based on the current
507
     * base URI), and maintaining other information about the current
508
     * catalog.</p>
509
     *
510
     * @param entry The CatalogEntry to process.
511
     */
512
    private void addEntry(CatalogEntry entry) {
513
	switch (entry.entryType()) {
514
	case CatalogEntry.BASE: {
515
	    String value = entry.formalSystemIdentifier();
516
	    URL newbase = null;
517

  
518
	    debug(3, "BASE", value);
519

  
520
	    try {
521
		value = fixSlashes(value);
522
		newbase = new URL(catalogCwd, value);
523
	    } catch (MalformedURLException e) {
524
		try {
525
		    newbase = new URL("file:///" + value);
526
		} catch (MalformedURLException e2) {
527
		    debug(1, "Malformed URL on base", value);
528
		    newbase = null;
529
		}
530
	    }
531

  
532
	    if (newbase != null) {
533
		base = newbase;
534
	    }
535

  
536
	    break;
537
	}
538

  
539
	case CatalogEntry.CATALOG: {
540
	    String fsi = makeAbsolute(entry.formalSystemIdentifier());
541

  
542
	    debug(3, "CATALOG", fsi);
543

  
544
	    localCatalogFiles.addElement(fsi);
545
	    break;
546
	}
547

  
548
	case CatalogEntry.DOCUMENT: {
549
	    String fsi = makeAbsolute(entry.formalSystemIdentifier());
550
	    entry.updateFormalSystemIdentifier(fsi);
551

  
552
	    debug(3, "DOCUMENT", fsi);
553

  
554
	    catalogEntries.addElement(entry);
555
	    break;
556
	}
557
	case CatalogEntry.OVERRIDE: {
558
	    debug(3, "OVERRIDE", entry.yes_or_no());
559

  
560
	    catalogEntries.addElement(entry);
561
	    break;
562
	}
563
	case CatalogEntry.SGMLDECL: {
564
	    // meaningless in XML
565
	    break;
566
	}
567
	case CatalogEntry.DELEGATE: {
568
	    String fsi = makeAbsolute(entry.formalSystemIdentifier());
569
	    entry.updateFormalSystemIdentifier(fsi);
570

  
571
	    debug(3, "DELEGATE", entry.partialPublicId(), fsi);
572

  
573
	    addDelegate(entry);
574
	    break;
575
	}
576
	case CatalogEntry.DOCTYPE: {
577
	    String fsi = makeAbsolute(entry.formalSystemIdentifier());
578
	    entry.updateFormalSystemIdentifier(fsi);
579

  
580
	    debug(3, "DOCTYPE", entry.publicId(), fsi);
581

  
582
	    catalogEntries.addElement(entry);
583
	    break;
584
	}
585
	case CatalogEntry.DTDDECL: {
586
	    // meaningless in XML
587
	    break;
588
	}
589
	case CatalogEntry.ENTITY: {
590
	    String fsi = makeAbsolute(entry.formalSystemIdentifier());
591
	    entry.updateFormalSystemIdentifier(fsi);
592

  
593
	    debug(3, "ENTITY", entry.entityName(), fsi);
594

  
595
	    catalogEntries.addElement(entry);
596
	    break;
597
	}
598
	case CatalogEntry.LINKTYPE: {
599
	    // meaningless in XML
600
	    break;
601
	}
602
	case CatalogEntry.NOTATION: {
603
	    String fsi = makeAbsolute(entry.formalSystemIdentifier());
604
	    entry.updateFormalSystemIdentifier(fsi);
605

  
606
	    debug(3, "NOTATION", entry.entityName(), fsi);
607

  
608
	    catalogEntries.addElement(entry);
609
	    break;
610
	}
611
	case CatalogEntry.PUBLIC: {
612
	    // This entry has to go in the vector because it would
613
	    // be relevant in subsequent searches for notations.
614
	    String publicid = entry.publicId();
615
	    String systemid = makeAbsolute(entry.formalSystemIdentifier());
616

  
617
	    debug(3, "PUBLIC", publicid, systemid);
618

  
619
	    entry.updateFormalSystemIdentifier(systemid);
620
	    catalogEntries.addElement(entry);
621
	    break;
622
	}
623
	case CatalogEntry.SYSTEM: {
624
	    String systemid = entry.systemId();
625
	    String fsi = makeAbsolute(entry.formalSystemIdentifier());
626

  
627
	    debug(3, "SYSTEM", systemid, fsi);
628

  
629
	    entry.updateFormalSystemIdentifier(fsi);
630
	    catalogEntries.addElement(entry);
631
	    break;
632
	}
633
	}
634
    }
635

  
636
    /**
637
     * <p>Parse all subordinate catalogs.</p>
638
     *
639
     * <p>This method recursively parses all of the subordinate catalogs.
640
     * If this method does not throw an exception, you can be confident that
641
     * no subsequent call to any resolve*() method will either, with two
642
     * possible exceptions:</p>
643
     *
644
     * <ol>
645
     * <li><p>Delegated catalogs are re-parsed each time they are needed
646
     * (because a variable list of them may be needed in each case,
647
     * depending on the length of the matching partial public identifier).</p>
648
     * <p>But they are parsed by this method, so as long as they don't
649
     * change or disappear while the program is running, they shouldn't
650
     * generate errors later if they don't generate errors now.</p>
651
     * <li><p>If you add new catalogs with <code>parseCatalog</code>, they
652
     * won't be loaded until they are needed or until you call
653
     * <code>parseAllCatalogs</code> again.</p>
654
     * </ol>
655
     *
656
     * <p>On the other hand, if you don't call this method, you may
657
     * successfully parse documents without having to load all possible
658
     * catalogs.</p>
659
     *
660
     * @throws MalformedURLException The filename (URL) for a
661
     * subordinate or delegated catalog is not a valid URL.
662
     * @throws IOException Error reading some subordinate or delegated
663
     * catalog file.
664
     */
665
    public void parseAllCatalogs()
666
	throws MalformedURLException, IOException {
667

  
668
	// Parse all the subordinate catalogs
669
	for (int catPos = 0; catPos < catalogs.size(); catPos++) {
670
	    Catalog c = null;
671

  
672
	    try {
673
		c = (Catalog) catalogs.elementAt(catPos);
674
	    } catch (ClassCastException e) {
675
		String catfile = (String) catalogs.elementAt(catPos);
676
		c = new Catalog();
677
		c.setParserClass(parserClass);
678
		c.debug = debug;
679

  
680
		c.parseCatalog(catfile);
681
		catalogs.setElementAt(c, catPos);
682
		c.parseAllCatalogs();
683
	    }
684
	}
685

  
686
	// Parse all the DELEGATE catalogs
687
	Enumeration enum = catalogEntries.elements();
688
	while (enum.hasMoreElements()) {
689
	    CatalogEntry e = (CatalogEntry) enum.nextElement();
690
	    if (e.entryType() == CatalogEntry.DELEGATE) {
691
		Catalog dcat = new Catalog();
692
		dcat.setParserClass(parserClass);
693
		dcat.debug = debug;
694
		dcat.parseCatalog(e.formalSystemIdentifier());
695
	    }
696
	}
697
    }
698

  
699

  
700
    /**
701
     * <p>Return the applicable DOCTYPE system identifier.</p>
702
     *
703
     * @param entityName The name of the entity (element) for which
704
     * a doctype is required.
705
     * @param publicId The nominal public identifier for the doctype
706
     * (as provided in the source document).
707
     * @param systemId The nominal system identifier for the doctype
708
     * (as provided in the source document).
709
     *
710
     * @return The system identifier to use for the doctype.
711
     *
712
     * @throws MalformedURLException The formal system identifier of a
713
     * subordinate catalog cannot be turned into a valid URL.
714
     * @throws IOException Error reading subordinate catalog file.
715
     */
716
    public String resolveDoctype(String entityName,
717
				 String publicId,
718
				 String systemId)
719
	throws MalformedURLException, IOException {
720
	String resolved = null;
721

  
722
	if (systemId != null) {
723
	    // If there's a SYSTEM entry in this catalog, use it
724
	    resolved = resolveLocalSystem(systemId);
725
	    if (resolved != null) {
726
		return resolved;
727
	    }
728
	}
729

  
730
	if (publicId != null) {
731
	    // If there's a PUBLIC entry in this catalog, use it
732
	    resolved = resolveLocalPublic(CatalogEntry.DOCTYPE,
733
					  entityName,
734
					  publicId,
735
					  systemId);
736
	    if (resolved != null) {
737
		return resolved;
738
	    }
739
	}
740

  
741
	// If there's a DOCTYPE entry in this catalog, use it
742
	boolean over = default_override;
743
	Enumeration enum = catalogEntries.elements();
744
	while (enum.hasMoreElements()) {
745
	    CatalogEntry e = (CatalogEntry) enum.nextElement();
746
	    if (e.entryType() == CatalogEntry.OVERRIDE) {
747
		over = e.yes_or_no().equalsIgnoreCase("YES");
748
		continue;
749
	    }
750

  
751
	    if (e.entryType() == CatalogEntry.DOCTYPE
752
		&& e.entityName().equals(entityName)) {
753
		if (over || systemId == null) {
754
		    return e.formalSystemIdentifier();
755
		}
756
	    }
757
	}
758

  
759
	// Otherwise, look in the subordinate catalogs
760
	return resolveSubordinateCatalogs(CatalogEntry.DOCTYPE,
761
					  entityName,
762
					  publicId,
763
					  systemId);
764
    }
765

  
766
    /**
767
     * <p>Return the applicable DOCUMENT entry.</p>
768
     *
769
     * @return The system identifier to use for the doctype.
770
     *
771
     * @throws MalformedURLException The formal system identifier of a
772
     * subordinate catalog cannot be turned into a valid URL.
773
     * @throws IOException Error reading subordinate catalog file.
774
     */
775
    public String resolveDocument()
776
	throws MalformedURLException, IOException {
777
	// If there's a DOCUMENT entry, return it
778
	Enumeration enum = catalogEntries.elements();
779
	while (enum.hasMoreElements()) {
780
	    CatalogEntry e = (CatalogEntry) enum.nextElement();
781
	    if (e.entryType() == CatalogEntry.DOCUMENT) {
782
		return e.formalSystemIdentifier();
783
	    }
784
	}
785

  
786
	return resolveSubordinateCatalogs(CatalogEntry.DOCUMENT,
787
					  null, null, null);
788
    }
789

  
790
    /**
791
     * <p>Return the applicable ENTITY system identifier.</p>
792
     *
793
     * @param entityName The name of the entity for which
794
     * a system identifier is required.
795
     * @param publicId The nominal public identifier for the entity
796
     * (as provided in the source document).
797
     * @param systemId The nominal system identifier for the entity
798
     * (as provided in the source document).
799
     *
800
     * @return The system identifier to use for the entity.
801
     *
802
     * @throws MalformedURLException The formal system identifier of a
803
     * subordinate catalog cannot be turned into a valid URL.
804
     * @throws IOException Error reading subordinate catalog file.
805
     */
806
    public String resolveEntity(String entityName,
807
				String publicId,
808
				String systemId)
809
	throws MalformedURLException, IOException {
810
	String resolved = null;
811

  
812
	if (systemId != null) {
813
	    // If there's a SYSTEM entry in this catalog, use it
814
	    resolved = resolveLocalSystem(systemId);
815
	    if (resolved != null) {
816
		return resolved;
817
	    }
818
	}
819

  
820
	if (publicId != null) {
821
	    // If there's a PUBLIC entry in this catalog, use it
822
	    resolved = resolveLocalPublic(CatalogEntry.ENTITY,
823
					  entityName,
824
					  publicId,
825
					  systemId);
826
	    if (resolved != null) {
827
		return resolved;
828
	    }
829
	}
830

  
831
	// If there's a ENTITY entry in this catalog, use it
832
	boolean over = default_override;
833
	Enumeration enum = catalogEntries.elements();
834
	while (enum.hasMoreElements()) {
835
	    CatalogEntry e = (CatalogEntry) enum.nextElement();
836
	    if (e.entryType() == CatalogEntry.OVERRIDE) {
837
		over = e.yes_or_no().equalsIgnoreCase("YES");
838
		continue;
839
	    }
840

  
841
	    if (e.entryType() == CatalogEntry.ENTITY
842
		&& e.entityName().equals(entityName)) {
843
		if (over || systemId == null) {
844
		    return e.formalSystemIdentifier();
845
		}
846
	    }
847
	}
848

  
849
	// Otherwise, look in the subordinate catalogs
850
	return resolveSubordinateCatalogs(CatalogEntry.ENTITY,
851
					  entityName,
852
					  publicId,
853
					  systemId);
854
    }
855

  
856
    /**
857
     * <p>Return the applicable NOTATION system identifier.</p>
858
     *
859
     * @param notationName The name of the notation for which
860
     * a doctype is required.
861
     * @param publicId The nominal public identifier for the notation
862
     * (as provided in the source document).
863
     * @param systemId The nominal system identifier for the notation
864
     * (as provided in the source document).
865
     *
866
     * @return The system identifier to use for the notation.
867
     *
868
     * @throws MalformedURLException The formal system identifier of a
869
     * subordinate catalog cannot be turned into a valid URL.
870
     * @throws IOException Error reading subordinate catalog file.
871
     */
872
    public String resolveNotation(String notationName,
873
				  String publicId,
874
				  String systemId)
875
	throws MalformedURLException, IOException {
876
	String resolved = null;
877

  
878
	if (systemId != null) {
879
	    // If there's a SYSTEM entry in this catalog, use it
880
	    resolved = resolveLocalSystem(systemId);
881
	    if (resolved != null) {
882
		return resolved;
883
	    }
884
	}
885

  
886
	if (publicId != null) {
887
	    // If there's a PUBLIC entry in this catalog, use it
888
	    resolved = resolveLocalPublic(CatalogEntry.NOTATION,
889
					  notationName,
890
					  publicId,
891
					  systemId);
892
	    if (resolved != null) {
893
		return resolved;
894
	    }
895
	}
896

  
897
	// If there's a NOTATION entry in this catalog, use it
898
	boolean over = default_override;
899
	Enumeration enum = catalogEntries.elements();
900
	while (enum.hasMoreElements()) {
901
	    CatalogEntry e = (CatalogEntry) enum.nextElement();
902
	    if (e.entryType() == CatalogEntry.OVERRIDE) {
903
		over = e.yes_or_no().equalsIgnoreCase("YES");
904
		continue;
905
	    }
906

  
907
	    if (e.entryType() == CatalogEntry.NOTATION
908
		&& e.entityName().equals(notationName)) {
909
		if (over || systemId == null) {
910
		    return e.formalSystemIdentifier();
911
		}
912
	    }
913
	}
914

  
915
	// Otherwise, look in the subordinate catalogs
916
	return resolveSubordinateCatalogs(CatalogEntry.NOTATION,
917
					  notationName,
918
					  publicId,
919
					  systemId);
920
    }
921

  
922
    /**
923
     * <p>Return the applicable PUBLIC or SYSTEM identifier.</p>
924
     *
925
     * <p>This method searches the Catalog and returns the system
926
     * identifier specified for the given system or
927
     * public identifiers. If
928
     * no appropriate PUBLIC or SYSTEM entry is found in the Catalog,
929
     * null is returned.</p>
930
     *
931
     * @param publicId The public identifier to locate in the catalog.
932
     * Public identifiers are normalized before comparison.
933
     * @param systemId The nominal system identifier for the entity
934
     * in question (as provided in the source document).
935
     *
936
     * @throws MalformedURLException The formal system identifier of a
937
     * subordinate catalog cannot be turned into a valid URL.
938
     * @throws IOException Error reading subordinate catalog file.
939
     *
940
     * @return The system identifier to use.
941
     * Note that the nominal system identifier is not returned if a
942
     * match is not found in the catalog, instead null is returned
943
     * to indicate that no match was found.
944
     */
945
    public String resolvePublic(String publicId, String systemId) 
946
	throws MalformedURLException, IOException {
947

  
948
	// If there's a SYSTEM entry in this catalog, use it
949
	if (systemId != null) {
950
	    String resolved = resolveLocalSystem(systemId);
951
	    if (resolved != null) {
952
		return resolved;
953
	    }
954
	}
955

  
956
	// If there's a PUBLIC entry in this catalog, use it
957
	String resolved = resolveLocalPublic(CatalogEntry.PUBLIC,
958
					     null,
959
					     publicId,
960
					     systemId);
961
	if (resolved != null) {
962
	    return resolved;
963
	}
964

  
965
	// Otherwise, look in the subordinate catalogs
966
	return resolveSubordinateCatalogs(CatalogEntry.PUBLIC,
967
					  null,
968
					  publicId,
969
					  systemId);
970
    }
971

  
972
    /**
973
     * <p>Return the applicable PUBLIC or SYSTEM identifier</p>
974
     *
975
     * <p>This method searches the Catalog and returns the system
976
     * identifier specified for the given system or public identifiers.
977
     * If no appropriate PUBLIC or SYSTEM entry is found in the Catalog,
978
     * delegated Catalogs are interrogated.</p>
979
     *
980
     * <p>There are four possible cases:</p>
981
     *
982
     * <ul>
983
     * <li>If the system identifier provided matches a SYSTEM entry
984
     * in the current catalog, the SYSTEM entry is returned.
985
     * <li>If the system identifier is not null, the PUBLIC entries
986
     * that were encountered when OVERRIDE YES was in effect are
987
     * interrogated and the first matching entry is returned.</li>
988
     * <li>If the system identifier is null, then all of the PUBLIC
989
     * entries are interrogated and the first matching entry
990
     * is returned. This may not be the same as the preceding case, if
991
     * some PUBLIC entries are encountered when OVERRIDE NO is in effect. In
992
     * XML, the only place where a public identifier may occur without
993
     * a system identifier is in a notation declaration.</li>
994
     * <li>Finally, if the public identifier matches one of the partial
995
     * public identifiers specified in a DELEGATE entry in
996
     * the Catalog, the delegated catalog is interrogated. The first
997
     * time that the delegated catalog is required, it will be
998
     * retrieved and parsed. It is subsequently cached.
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff