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.
|
999
|
* </li>
|
1000
|
* </ul>
|
1001
|
*
|
1002
|
* @param entityType The CatalogEntry type for which this query is
|
1003
|
* being conducted. This is necessary in order to do the approprate
|
1004
|
* query on a delegated catalog.
|
1005
|
* @param entityName The name of the entity being searched for, if
|
1006
|
* appropriate.
|
1007
|
* @param publicId The public identifier of the entity in question.
|
1008
|
* @param systemId The nominal system identifier for the entity
|
1009
|
* in question (as provided in the source document).
|
1010
|
*
|
1011
|
* @throws MalformedURLException The formal system identifier of a
|
1012
|
* delegated catalog cannot be turned into a valid URL.
|
1013
|
* @throws IOException Error reading delegated catalog file.
|
1014
|
*
|
1015
|
* @return The system identifier to use.
|
1016
|
* Note that the nominal system identifier is not returned if a
|
1017
|
* match is not found in the catalog, instead null is returned
|
1018
|
* to indicate that no match was found.
|
1019
|
*/
|
1020
|
private synchronized String resolveLocalPublic(int entityType,
|
1021
|
String entityName,
|
1022
|
String publicId,
|
1023
|
String systemId)
|
1024
|
throws MalformedURLException, IOException {
|
1025
|
|
1026
|
// Always normalize the public identifier before attempting a match
|
1027
|
publicId = CatalogReader.normalize(publicId);
|
1028
|
|
1029
|
// If there's a SYSTEM entry in this catalog, use it
|
1030
|
if (systemId != null) {
|
1031
|
String resolved = resolveLocalSystem(systemId);
|
1032
|
if (resolved != null) {
|
1033
|
return resolved;
|
1034
|
}
|
1035
|
}
|
1036
|
|
1037
|
// If there's a PUBLIC entry in this catalog, use it
|
1038
|
boolean over = default_override;
|
1039
|
Enumeration enum = catalogEntries.elements();
|
1040
|
while (enum.hasMoreElements()) {
|
1041
|
CatalogEntry e = (CatalogEntry) enum.nextElement();
|
1042
|
if (e.entryType() == CatalogEntry.OVERRIDE) {
|
1043
|
over = e.yes_or_no().equalsIgnoreCase("YES");
|
1044
|
continue;
|
1045
|
}
|
1046
|
|
1047
|
if (e.entryType() == CatalogEntry.PUBLIC
|
1048
|
&& e.publicId().equals(publicId)) {
|
1049
|
if (over || systemId == null) {
|
1050
|
return e.formalSystemIdentifier();
|
1051
|
}
|
1052
|
}
|
1053
|
}
|
1054
|
|
1055
|
// If there's a DELEGATE entry in this catalog, use it
|
1056
|
over = default_override;
|
1057
|
enum = catalogEntries.elements();
|
1058
|
Vector delCats = new Vector();
|
1059
|
while (enum.hasMoreElements()) {
|
1060
|
CatalogEntry e = (CatalogEntry) enum.nextElement();
|
1061
|
if (e.entryType() == CatalogEntry.OVERRIDE) {
|
1062
|
over = e.yes_or_no().equalsIgnoreCase("YES");
|
1063
|
continue;
|
1064
|
}
|
1065
|
|
1066
|
if (e.entryType() == CatalogEntry.DELEGATE
|
1067
|
&& (over || systemId == null)) {
|
1068
|
String p = (String) e.partialPublicId();
|
1069
|
if (p.length() <= publicId.length()
|
1070
|
&& p.equals(publicId.substring(0, p.length()))) {
|
1071
|
// delegate this match to the other catalog
|
1072
|
|
1073
|
delCats.addElement(e.formalSystemIdentifier());
|
1074
|
}
|
1075
|
}
|
1076
|
}
|
1077
|
|
1078
|
if (delCats.size() > 0) {
|
1079
|
Enumeration enumCats = delCats.elements();
|
1080
|
|
1081
|
if (debug > 0) {
|
1082
|
debug(1, "Switching to delegated catalog(s):");
|
1083
|
while (enumCats.hasMoreElements()) {
|
1084
|
String delegatedCatalog = (String) enumCats.nextElement();
|
1085
|
debug(1, "\t" + delegatedCatalog);
|
1086
|
}
|
1087
|
}
|
1088
|
|
1089
|
Catalog dcat = new Catalog();
|
1090
|
dcat.setParserClass(parserClass);
|
1091
|
dcat.debug = debug;
|
1092
|
|
1093
|
enumCats = delCats.elements();
|
1094
|
while (enumCats.hasMoreElements()) {
|
1095
|
String delegatedCatalog = (String) enumCats.nextElement();
|
1096
|
dcat.parseCatalog(delegatedCatalog);
|
1097
|
}
|
1098
|
|
1099
|
return dcat.resolvePublic(publicId, null);
|
1100
|
}
|
1101
|
|
1102
|
// Nada!
|
1103
|
return null;
|
1104
|
}
|
1105
|
|
1106
|
/**
|
1107
|
* <p>Return the applicable SYSTEM system identifier</p>
|
1108
|
*
|
1109
|
* <p>If a SYSTEM entry exists in the Catalog
|
1110
|
* for the system ID specified, return the mapped value.</p>
|
1111
|
*
|
1112
|
* <p>The caller is responsible for doing any necessary
|
1113
|
* normalization of the system identifier before calling
|
1114
|
* this method. For example, a relative system identifier in
|
1115
|
* a document might be converted to an absolute system identifier
|
1116
|
* before attempting to resolve it.</p>
|
1117
|
*
|
1118
|
* <p>On Windows-based operating systems, the comparison between
|
1119
|
* the system identifier provided and the SYSTEM entries in the
|
1120
|
* Catalog is case-insensitive.</p>
|
1121
|
*
|
1122
|
* @param systemId The system ID to locate in the catalog.
|
1123
|
*
|
1124
|
* @return The system identifier to use for the notation.
|
1125
|
*
|
1126
|
* @throws MalformedURLException The formal system identifier of a
|
1127
|
* subordinate catalog cannot be turned into a valid URL.
|
1128
|
* @throws IOException Error reading subordinate catalog file.
|
1129
|
*/
|
1130
|
public String resolveSystem(String systemId)
|
1131
|
throws MalformedURLException, IOException {
|
1132
|
|
1133
|
// If there's a SYSTEM entry in this catalog, use it
|
1134
|
if (systemId != null) {
|
1135
|
String resolved = resolveLocalSystem(systemId);
|
1136
|
if (resolved != null) {
|
1137
|
return resolved;
|
1138
|
}
|
1139
|
}
|
1140
|
|
1141
|
// Otherwise, look in the subordinate catalogs
|
1142
|
return resolveSubordinateCatalogs(CatalogEntry.SYSTEM,
|
1143
|
null,
|
1144
|
null,
|
1145
|
systemId);
|
1146
|
}
|
1147
|
|
1148
|
/**
|
1149
|
* <p>Return the applicable SYSTEM system identifier in this
|
1150
|
* catalog.</p>
|
1151
|
*
|
1152
|
* <p>If a SYSTEM entry exists in the catalog file
|
1153
|
* for the system ID specified, return the mapped value.</p>
|
1154
|
*
|
1155
|
* @param systemId The system ID to locate in the catalog
|
1156
|
*
|
1157
|
* @return The mapped system identifier or null
|
1158
|
*/
|
1159
|
private String resolveLocalSystem(String systemId) {
|
1160
|
String osname = System.getProperty("os.name");
|
1161
|
boolean windows = (osname.indexOf("Windows") >= 0);
|
1162
|
Enumeration enum = catalogEntries.elements();
|
1163
|
while (enum.hasMoreElements()) {
|
1164
|
CatalogEntry e = (CatalogEntry) enum.nextElement();
|
1165
|
if (e.entryType() == CatalogEntry.SYSTEM
|
1166
|
&& (e.systemId().equals(systemId)
|
1167
|
|| (windows
|
1168
|
&& e.systemId().equalsIgnoreCase(systemId)))) {
|
1169
|
return e.formalSystemIdentifier();
|
1170
|
}
|
1171
|
}
|
1172
|
return null;
|
1173
|
}
|
1174
|
|
1175
|
|
1176
|
/**
|
1177
|
* <p>Search the subordinate catalogs, in order, looking for a
|
1178
|
* match.</p>
|
1179
|
*
|
1180
|
* <p>This method searches the Catalog and returns the system
|
1181
|
* identifier specified for the given entity type with the given
|
1182
|
* name, public, and system identifiers. In some contexts, these
|
1183
|
* may be null.</p>
|
1184
|
*
|
1185
|
* @param entityType The CatalogEntry type for which this query is
|
1186
|
* being conducted. This is necessary in order to do the approprate
|
1187
|
* query on a subordinate catalog.
|
1188
|
* @param entityName The name of the entity being searched for, if
|
1189
|
* appropriate.
|
1190
|
* @param publicId The public identifier of the entity in question
|
1191
|
* (as provided in the source document).
|
1192
|
* @param systemId The nominal system identifier for the entity
|
1193
|
* in question (as provided in the source document).
|
1194
|
*
|
1195
|
* @throws MalformedURLException The formal system identifier of a
|
1196
|
* delegated catalog cannot be turned into a valid URL.
|
1197
|
* @throws IOException Error reading delegated catalog file.
|
1198
|
*
|
1199
|
* @return The system identifier to use.
|
1200
|
* Note that the nominal system identifier is not returned if a
|
1201
|
* match is not found in the catalog, instead null is returned
|
1202
|
* to indicate that no match was found.
|
1203
|
*/
|
1204
|
private synchronized String resolveSubordinateCatalogs(int entityType,
|
1205
|
String entityName,
|
1206
|
String publicId,
|
1207
|
String systemId)
|
1208
|
throws MalformedURLException, IOException {
|
1209
|
|
1210
|
for (int catPos = 0; catPos < catalogs.size(); catPos++) {
|
1211
|
Catalog c = null;
|
1212
|
|
1213
|
try {
|
1214
|
c = (Catalog) catalogs.elementAt(catPos);
|
1215
|
} catch (ClassCastException e) {
|
1216
|
String catfile = (String) catalogs.elementAt(catPos);
|
1217
|
c = new Catalog();
|
1218
|
c.setParserClass(parserClass);
|
1219
|
c.debug = debug;
|
1220
|
|
1221
|
try {
|
1222
|
c.parseCatalog(catfile);
|
1223
|
} catch (MalformedURLException mue) {
|
1224
|
debug(1, "Malformed Catalog URL", catfile);
|
1225
|
} catch (FileNotFoundException fnfe) {
|
1226
|
debug(1, "Failed to load catalog, file not found",
|
1227
|
catfile);
|
1228
|
} catch (IOException ioe) {
|
1229
|
debug(1, "Failed to load catalog, I/O error", catfile);
|
1230
|
}
|
1231
|
|
1232
|
catalogs.setElementAt(c, catPos);
|
1233
|
}
|
1234
|
|
1235
|
String resolved = null;
|
1236
|
|
1237
|
// Ok, now what are we supposed to call here?
|
1238
|
switch (entityType) {
|
1239
|
case CatalogEntry.DOCTYPE: {
|
1240
|
resolved = c.resolveDoctype(entityName,
|
1241
|
publicId,
|
1242
|
systemId);
|
1243
|
break;
|
1244
|
}
|
1245
|
case CatalogEntry.DOCUMENT: {
|
1246
|
resolved = c.resolveDocument();
|
1247
|
break;
|
1248
|
}
|
1249
|
case CatalogEntry.ENTITY: {
|
1250
|
resolved = c.resolveEntity(entityName,
|
1251
|
publicId,
|
1252
|
systemId);
|
1253
|
break;
|
1254
|
}
|
1255
|
case CatalogEntry.NOTATION: {
|
1256
|
resolved = c.resolveNotation(entityName,
|
1257
|
publicId,
|
1258
|
systemId);
|
1259
|
break;
|
1260
|
}
|
1261
|
case CatalogEntry.PUBLIC: {
|
1262
|
resolved = c.resolvePublic(publicId, systemId);
|
1263
|
break;
|
1264
|
}
|
1265
|
case CatalogEntry.SYSTEM: {
|
1266
|
resolved = c.resolveSystem(systemId);
|
1267
|
break;
|
1268
|
}
|
1269
|
}
|
1270
|
|
1271
|
if (resolved != null) {
|
1272
|
return resolved;
|
1273
|
}
|
1274
|
}
|
1275
|
|
1276
|
return null;
|
1277
|
}
|
1278
|
|
1279
|
// -----------------------------------------------------------------
|
1280
|
|
1281
|
/**
|
1282
|
* <p>Replace backslashes with forward slashes. (URLs always use
|
1283
|
* forward slashes.)</p>
|
1284
|
*
|
1285
|
* @param sysid The input system identifier.
|
1286
|
* @return The same system identifier with backslashes turned into
|
1287
|
* forward slashes.
|
1288
|
*/
|
1289
|
private String fixSlashes (String sysid) {
|
1290
|
return sysid.replace('\\', '/');
|
1291
|
}
|
1292
|
|
1293
|
/**
|
1294
|
* <p>Construct an absolute URI from a relative one, using the current
|
1295
|
* base URI.</p>
|
1296
|
*
|
1297
|
* @param sysid The (possibly relative) system identifier
|
1298
|
* @return The system identifier made absolute with respect to the
|
1299
|
* current {@link #base}.
|
1300
|
*/
|
1301
|
private String makeAbsolute(String sysid) {
|
1302
|
URL local = null;
|
1303
|
|
1304
|
sysid = fixSlashes(sysid);
|
1305
|
|
1306
|
try {
|
1307
|
local = new URL(base, sysid);
|
1308
|
} catch (MalformedURLException e) {
|
1309
|
debug(1, "Malformed URL on system identifier", sysid);
|
1310
|
}
|
1311
|
|
1312
|
if (local != null) {
|
1313
|
return local.toString();
|
1314
|
} else {
|
1315
|
return sysid;
|
1316
|
}
|
1317
|
}
|
1318
|
|
1319
|
/**
|
1320
|
* <p>Print debug message (if the debug level is high enough).</p>
|
1321
|
*
|
1322
|
* @param level The debug level of this message. This message
|
1323
|
* will only be
|
1324
|
* displayed if the current debug level is at least equal to this
|
1325
|
* value.
|
1326
|
* @param message The text of the message.
|
1327
|
*/
|
1328
|
private void debug(int level, String message) {
|
1329
|
if (debug >= level) {
|
1330
|
System.out.println(message);
|
1331
|
}
|
1332
|
}
|
1333
|
|
1334
|
/**
|
1335
|
* <p>Print debug message (if the debug level is high enough).</p>
|
1336
|
*
|
1337
|
* @param level The debug level of this message. This message
|
1338
|
* will only be
|
1339
|
* displayed if the current debug level is at least equal to this
|
1340
|
* value.
|
1341
|
* @param message The text of the message.
|
1342
|
* @param spec An argument to the message.
|
1343
|
*/
|
1344
|
private void debug(int level, String message, String spec) {
|
1345
|
if (debug >= level) {
|
1346
|
System.out.println(message + ": " + spec);
|
1347
|
}
|
1348
|
}
|
1349
|
|
1350
|
/**
|
1351
|
* <p>Print debug message (if the debug level is high enough).</p>
|
1352
|
*
|
1353
|
* @param level The debug level of this message. This message
|
1354
|
* will only be
|
1355
|
* displayed if the current debug level is at least equal to this
|
1356
|
* value.
|
1357
|
* @param message The text of the message.
|
1358
|
* @param spec1 An argument to the message.
|
1359
|
* @param spec1 Another argument to the message.
|
1360
|
*/
|
1361
|
private void debug(int level, String message, String spec1, String spec2) {
|
1362
|
if (debug >= level) {
|
1363
|
System.out.println(message + ": " + spec1);
|
1364
|
System.out.println("\t" + spec2);
|
1365
|
}
|
1366
|
}
|
1367
|
|
1368
|
// -----------------------------------------------------------------
|
1369
|
|
1370
|
/**
|
1371
|
* <p>Add to the current list of delegated catalogs.</p>
|
1372
|
*
|
1373
|
* <p>This method always constructs the {@link #localDelegate}
|
1374
|
* vector so that it is ordered by length of partial
|
1375
|
* public identifier.</p>
|
1376
|
*
|
1377
|
* @param entry The DELEGATE catalog entry
|
1378
|
*/
|
1379
|
private void addDelegate(CatalogEntry entry) {
|
1380
|
int pos = 0;
|
1381
|
String partial = entry.partialPublicId();
|
1382
|
|
1383
|
Enumeration local = localDelegate.elements();
|
1384
|
while (local.hasMoreElements()) {
|
1385
|
CatalogEntry dpe = (CatalogEntry) local.nextElement();
|
1386
|
String dp = dpe.partialPublicId();
|
1387
|
if (dp.equals(partial)) {
|
1388
|
// we already have this prefix
|
1389
|
return;
|
1390
|
}
|
1391
|
if (dp.length() > partial.length()) {
|
1392
|
pos++;
|
1393
|
}
|
1394
|
if (dp.length() < partial.length()) {
|
1395
|
break;
|
1396
|
}
|
1397
|
}
|
1398
|
|
1399
|
// now insert partial into the vector at [pos]
|
1400
|
if (localDelegate.size() == 0) {
|
1401
|
localDelegate.addElement(entry);
|
1402
|
} else {
|
1403
|
localDelegate.insertElementAt(entry, pos);
|
1404
|
}
|
1405
|
}
|
1406
|
}
|