1 /* 2 * reserved comment block 3 * DO NOT REMOVE OR ALTER! 4 */ 5 // Catalog.java - Represents OASIS Open Catalog files. 6 7 /* 8 * Copyright 2001-2004 The Apache Software Foundation or its licensors, 9 * as applicable. 10 * 11 * Licensed under the Apache License, Version 2.0 (the "License"); 12 * you may not use this file except in compliance with the License. 13 * You may obtain a copy of the License at 14 * 15 * http://www.apache.org/licenses/LICENSE-2.0 16 * 17 * Unless required by applicable law or agreed to in writing, software 18 * distributed under the License is distributed on an "AS IS" BASIS, 19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20 * See the License for the specific language governing permissions and 21 * limitations under the License. 22 */ 23 24 package com.sun.org.apache.xml.internal.resolver; 25 26 import com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl; 27 import com.sun.org.apache.xerces.internal.utils.SecuritySupport; 28 import java.io.IOException; 29 import java.io.FileNotFoundException; 30 import java.io.InputStream; 31 import java.io.UnsupportedEncodingException; 32 import java.io.DataInputStream; 33 34 import java.util.Enumeration; 35 import java.util.Hashtable; 36 import java.util.Vector; 37 38 import java.net.URL; 39 import java.net.MalformedURLException; 40 41 import javax.xml.parsers.SAXParserFactory; 42 43 import com.sun.org.apache.xml.internal.resolver.CatalogManager; 44 import com.sun.org.apache.xml.internal.resolver.helpers.PublicId; 45 import com.sun.org.apache.xml.internal.resolver.readers.CatalogReader; 46 import com.sun.org.apache.xml.internal.resolver.readers.SAXCatalogReader; 47 import com.sun.org.apache.xml.internal.resolver.readers.TR9401CatalogReader; 48 import com.sun.org.apache.xml.internal.resolver.readers.OASISXMLCatalogReader; 49 import com.sun.org.apache.xml.internal.resolver.helpers.FileURL; 50 51 /** 52 * Represents OASIS Open Catalog files. 53 * 54 * <p>This class implements the semantics of OASIS Open Catalog files 55 * (defined by 56 * <a href="http://www.oasis-open.org/html/a401.htm">OASIS Technical 57 * Resolution 9401:1997 (Amendment 2 to TR 9401)</a>).</p> 58 * 59 * <p>The primary purpose of the Catalog is to associate resources in the 60 * document with local system identifiers. Some entities 61 * (document types, XML entities, and notations) have names and all of them 62 * can have either public or system identifiers or both. (In XML, only a 63 * notation can have a public identifier without a system identifier, but 64 * the methods implemented in this class obey the Catalog semantics 65 * from the SGML 66 * days when system identifiers were optional.)</p> 67 * 68 * <p>The system identifiers returned by the resolution methods in this 69 * class are valid, i.e. usable by, and in fact constructed by, the 70 * <tt>java.net.URL</tt> class. Unfortunately, this class seems to behave in 71 * somewhat non-standard ways and the system identifiers returned may 72 * not be directly usable in a browser or filesystem context. 73 * 74 * <p>This class recognizes all of the Catalog entries defined in 75 * TR9401:1997:</p> 76 * 77 * <ul> 78 * <li><b>BASE</b> 79 * changes the base URI for resolving relative system identifiers. The 80 * initial base URI is the URI of the location of the catalog (which is, 81 * in turn, relative to the location of the current working directory 82 * at startup, as returned by the <tt>user.dir</tt> system property).</li> 83 * <li><b>CATALOG</b> 84 * processes other catalog files. An included catalog occurs logically 85 * at the end of the including catalog.</li> 86 * <li><b>DELEGATE_PUBLIC</b> 87 * specifies alternate catalogs for some public identifiers. The delegated 88 * catalogs are not loaded until they are needed, but they are cached 89 * once loaded.</li> 90 * <li><b>DELEGATE_SYSTEM</b> 91 * specifies alternate catalogs for some system identifiers. The delegated 92 * catalogs are not loaded until they are needed, but they are cached 93 * once loaded.</li> 94 * <li><b>DELEGATE_URI</b> 95 * specifies alternate catalogs for some URIs. The delegated 96 * catalogs are not loaded until they are needed, but they are cached 97 * once loaded.</li> 98 * <li><b>REWRITE_SYSTEM</b> 99 * specifies alternate prefix for a system identifier.</li> 100 * <li><b>REWRITE_URI</b> 101 * specifies alternate prefix for a URI.</li> 102 * <li><b>SYSTEM_SUFFIX</b> 103 * maps any system identifier that ends with a particular suffix to another 104 * system identifier.</li> 105 * <li><b>URI_SUFFIX</b> 106 * maps any URI that ends with a particular suffix to another URI.</li> 107 * <li><b>DOCTYPE</b> 108 * associates the names of root elements with URIs. (In other words, an XML 109 * processor might infer the doctype of an XML document that does not include 110 * a doctype declaration by looking for the DOCTYPE entry in the 111 * catalog which matches the name of the root element of the document.)</li> 112 * <li><b>DOCUMENT</b> 113 * provides a default document.</li> 114 * <li><b>DTDDECL</b> 115 * recognized and silently ignored. Not relevant for XML.</li> 116 * <li><b>ENTITY</b> 117 * associates entity names with URIs.</li> 118 * <li><b>LINKTYPE</b> 119 * recognized and silently ignored. Not relevant for XML.</li> 120 * <li><b>NOTATION</b> 121 * associates notation names with URIs.</li> 122 * <li><b>OVERRIDE</b> 123 * changes the override behavior. Initial behavior is set by the 124 * system property <tt>xml.catalog.override</tt>. The default initial 125 * behavior is 'YES', that is, entries in the catalog override 126 * system identifiers specified in the document.</li> 127 * <li><b>PUBLIC</b> 128 * maps a public identifier to a system identifier.</li> 129 * <li><b>SGMLDECL</b> 130 * recognized and silently ignored. Not relevant for XML.</li> 131 * <li><b>SYSTEM</b> 132 * maps a system identifier to another system identifier.</li> 133 * <li><b>URI</b> 134 * maps a URI to another URI.</li> 135 * </ul> 136 * 137 * <p>Note that BASE entries are treated as described by RFC2396. In 138 * particular, this has the counter-intuitive property that after a BASE 139 * entry identifing "http://example.com/a/b/c" as the base URI, 140 * the relative URI "foo" is resolved to the absolute URI 141 * "http://example.com/a/b/foo". You must provide the trailing slash if 142 * you do not want the final component of the path to be discarded as a 143 * filename would in a URI for a resource: "http://example.com/a/b/c/". 144 * </p> 145 * 146 * <p>Note that subordinate catalogs (all catalogs except the first, 147 * including CATALOG and DELEGATE* catalogs) are only loaded if and when 148 * they are required.</p> 149 * 150 * <p>This class relies on classes which implement the CatalogReader 151 * interface to actually load catalog files. This allows the catalog 152 * semantics to be implemented for TR9401 text-based catalogs, XML 153 * catalogs, or any number of other storage formats.</p> 154 * 155 * <p>Additional catalogs may also be loaded with the 156 * {@link #parseCatalog} method.</p> 157 * </dd> 158 * </dl> 159 * 160 * <p><b>Change Log:</b></p> 161 * <dl> 162 * <dt>2.0</dt> 163 * <dd><p>Rewrite to use CatalogReaders.</p></dd> 164 * <dt>1.1</dt> 165 * <dd><p>Allow quoted components in <tt>xml.catalog.files</tt> 166 * so that URLs containing colons can be used on Unix. 167 * The string passed to <tt>xml.catalog.files</tt> can now have the form:</p> 168 * <pre> 169 * unquoted-path-with-no-sep-chars:"double-quoted path with or without sep chars":'single-quoted path with or without sep chars' 170 * </pre> 171 * <p>(Where ":" is the separater character in this example.)</p> 172 * <p>If an unquoted path contains an embedded double or single quote 173 * character, no special processig is performed on that character. No 174 * path can contain separater characters, double, and single quotes 175 * simultaneously.</p> 176 * <p>Fix bug in calculation of BASE entries: if 177 * a catalog contains multiple BASE entries, each is relative to the preceding 178 * base, not the default base URI of the catalog.</p> 179 * </dd> 180 * <dt>1.0.1</dt> 181 * <dd><p>Fixed a bug in the calculation of the list of subordinate catalogs. 182 * This bug caused an infinite loop where parsing would alternately process 183 * two catalogs indefinitely.</p> 184 * </dd> 185 * </dl> 186 * 187 * @see CatalogReader 188 * @see CatalogEntry 189 * 190 * @author Norman Walsh 191 * <a href="mailto:Norman.Walsh@Sun.COM">Norman.Walsh@Sun.COM</a> 192 * 193 * @version 1.0 194 * 195 * <p>Derived from public domain code originally published by Arbortext, 196 * Inc.</p> 197 */ 198 public class Catalog { 199 /** The BASE Catalog Entry type. */ 200 public static final int BASE = CatalogEntry.addEntryType("BASE", 1); 201 202 /** The CATALOG Catalog Entry type. */ 203 public static final int CATALOG = CatalogEntry.addEntryType("CATALOG", 1); 204 205 /** The DOCUMENT Catalog Entry type. */ 206 public static final int DOCUMENT = CatalogEntry.addEntryType("DOCUMENT", 1); 207 208 /** The OVERRIDE Catalog Entry type. */ 209 public static final int OVERRIDE = CatalogEntry.addEntryType("OVERRIDE", 1); 210 211 /** The SGMLDECL Catalog Entry type. */ 212 public static final int SGMLDECL = CatalogEntry.addEntryType("SGMLDECL", 1); 213 214 /** The DELEGATE_PUBLIC Catalog Entry type. */ 215 public static final int DELEGATE_PUBLIC = CatalogEntry.addEntryType("DELEGATE_PUBLIC", 2); 216 217 /** The DELEGATE_SYSTEM Catalog Entry type. */ 218 public static final int DELEGATE_SYSTEM = CatalogEntry.addEntryType("DELEGATE_SYSTEM", 2); 219 220 /** The DELEGATE_URI Catalog Entry type. */ 221 public static final int DELEGATE_URI = CatalogEntry.addEntryType("DELEGATE_URI", 2); 222 223 /** The DOCTYPE Catalog Entry type. */ 224 public static final int DOCTYPE = CatalogEntry.addEntryType("DOCTYPE", 2); 225 226 /** The DTDDECL Catalog Entry type. */ 227 public static final int DTDDECL = CatalogEntry.addEntryType("DTDDECL", 2); 228 229 /** The ENTITY Catalog Entry type. */ 230 public static final int ENTITY = CatalogEntry.addEntryType("ENTITY", 2); 231 232 /** The LINKTYPE Catalog Entry type. */ 233 public static final int LINKTYPE = CatalogEntry.addEntryType("LINKTYPE", 2); 234 235 /** The NOTATION Catalog Entry type. */ 236 public static final int NOTATION = CatalogEntry.addEntryType("NOTATION", 2); 237 238 /** The PUBLIC Catalog Entry type. */ 239 public static final int PUBLIC = CatalogEntry.addEntryType("PUBLIC", 2); 240 241 /** The SYSTEM Catalog Entry type. */ 242 public static final int SYSTEM = CatalogEntry.addEntryType("SYSTEM", 2); 243 244 /** The URI Catalog Entry type. */ 245 public static final int URI = CatalogEntry.addEntryType("URI", 2); 246 247 /** The REWRITE_SYSTEM Catalog Entry type. */ 248 public static final int REWRITE_SYSTEM = CatalogEntry.addEntryType("REWRITE_SYSTEM", 2); 249 250 /** The REWRITE_URI Catalog Entry type. */ 251 public static final int REWRITE_URI = CatalogEntry.addEntryType("REWRITE_URI", 2); 252 /** The SYSTEM_SUFFIX Catalog Entry type. */ 253 public static final int SYSTEM_SUFFIX = CatalogEntry.addEntryType("SYSTEM_SUFFIX", 2); 254 /** The URI_SUFFIX Catalog Entry type. */ 255 public static final int URI_SUFFIX = CatalogEntry.addEntryType("URI_SUFFIX", 2); 256 257 /** 258 * The base URI for relative system identifiers in the catalog. 259 * This may be changed by BASE entries in the catalog. 260 */ 261 protected URL base; 262 263 /** The base URI of the Catalog file currently being parsed. */ 264 protected URL catalogCwd; 265 266 /** The catalog entries currently known to the system. */ 267 protected Vector catalogEntries = new Vector(); 268 269 /** The default initial override setting. */ 270 protected boolean default_override = true; 271 272 /** The catalog manager in use for this instance. */ 273 protected CatalogManager catalogManager = CatalogManager.getStaticManager(); 274 275 /** 276 * A vector of catalog files to be loaded. 277 * 278 * <p>This list is initially established by 279 * <code>loadSystemCatalogs</code> when 280 * it parses the system catalog list, but CATALOG entries may 281 * contribute to it during the course of parsing.</p> 282 * 283 * @see #loadSystemCatalogs 284 * @see #localCatalogFiles 285 */ 286 protected Vector catalogFiles = new Vector(); 287 288 /** 289 * A vector of catalog files constructed during processing of 290 * CATALOG entries in the current catalog. 291 * 292 * <p>This two-level system is actually necessary to correctly implement 293 * the semantics of the CATALOG entry. If one catalog file includes 294 * another with a CATALOG entry, the included catalog logically 295 * occurs <i>at the end</i> of the including catalog, and after any 296 * preceding CATALOG entries. In other words, the CATALOG entry 297 * cannot insert anything into the middle of a catalog file.</p> 298 * 299 * <p>When processing reaches the end of each catalog files, any 300 * elements on this vector are added to the front of the 301 * <code>catalogFiles</code> vector.</p> 302 * 303 * @see #catalogFiles 304 */ 305 protected Vector localCatalogFiles = new Vector(); 306 307 /** 308 * A vector of Catalogs. 309 * 310 * <p>The semantics of Catalog resolution are such that each 311 * catalog is effectively a list of Catalogs (in other words, 312 * a recursive list of Catalog instances).</p> 313 * 314 * <p>Catalogs that are processed as the result of CATALOG or 315 * DELEGATE* entries are subordinate to the catalog that contained 316 * them, but they may in turn have subordinate catalogs.</p> 317 * 318 * <p>Catalogs are only loaded when they are needed, so this vector 319 * initially contains a list of Catalog filenames (URLs). If, during 320 * processing, one of these catalogs has to be loaded, the resulting 321 * Catalog object is placed in the vector, effectively caching it 322 * for the next query.</p> 323 */ 324 protected Vector catalogs = new Vector(); 325 326 /** 327 * A vector of DELEGATE* Catalog entries constructed during 328 * processing of the Catalog. 329 * 330 * <p>This two-level system has two purposes; first, it allows 331 * us to sort the DELEGATE* entries by the length of the partial 332 * public identifier so that a linear search encounters them in 333 * the correct order and second, it puts them all at the end of 334 * the Catalog.</p> 335 * 336 * <p>When processing reaches the end of each catalog file, any 337 * elements on this vector are added to the end of the 338 * <code>catalogEntries</code> vector. This assures that matching 339 * PUBLIC keywords are encountered before DELEGATE* entries.</p> 340 */ 341 protected Vector localDelegate = new Vector(); 342 343 /** 344 * A hash of CatalogReaders. 345 * 346 * <p>This hash maps MIME types to elements in the readerArr 347 * vector. This allows the Catalog to quickly locate the reader 348 * for a particular MIME type.</p> 349 */ 350 protected Hashtable readerMap = new Hashtable(); 351 352 /** 353 * A vector of CatalogReaders. 354 * 355 * <p>This vector contains all of the readers in the order that they 356 * were added. In the event that a catalog is read from a file, where 357 * the MIME type is unknown, each reader is attempted in turn until 358 * one succeeds.</p> 359 */ 360 protected Vector readerArr = new Vector(); 361 362 /** 363 * Constructs an empty Catalog. 364 * 365 * <p>The constructor interrogates the relevant system properties 366 * using the default (static) CatalogManager 367 * and initializes the catalog data structures.</p> 368 */ 369 public Catalog() { 370 // nop; 371 } 372 373 /** 374 * Constructs an empty Catalog with a specific CatalogManager. 375 * 376 * <p>The constructor interrogates the relevant system properties 377 * using the specified Catalog Manager 378 * and initializes the catalog data structures.</p> 379 */ 380 public Catalog(CatalogManager manager) { 381 catalogManager = manager; 382 } 383 384 /** 385 * Return the CatalogManager used by this catalog. 386 * 387 */ 388 public CatalogManager getCatalogManager() { 389 return catalogManager; 390 } 391 392 /** 393 * Establish the CatalogManager used by this catalog. 394 * 395 */ 396 public void setCatalogManager(CatalogManager manager) { 397 catalogManager = manager; 398 } 399 400 /** 401 * Setup readers. 402 */ 403 public void setupReaders() { 404 SAXParserFactory spf = catalogManager.useServicesMechanism() ? 405 SAXParserFactory.newInstance() : new SAXParserFactoryImpl(); 406 spf.setNamespaceAware(true); 407 spf.setValidating(false); 408 409 SAXCatalogReader saxReader = new SAXCatalogReader(spf); 410 411 saxReader.setCatalogParser(null, "XMLCatalog", 412 "com.sun.org.apache.xml.internal.resolver.readers.XCatalogReader"); 413 414 saxReader.setCatalogParser(OASISXMLCatalogReader.namespaceName, 415 "catalog", 416 "com.sun.org.apache.xml.internal.resolver.readers.OASISXMLCatalogReader"); 417 418 addReader("application/xml", saxReader); 419 420 TR9401CatalogReader textReader = new TR9401CatalogReader(); 421 addReader("text/plain", textReader); 422 } 423 424 /** 425 * Add a new CatalogReader to the Catalog. 426 * 427 * <p>This method allows you to add a new CatalogReader to the 428 * catalog. The reader will be associated with the specified mimeType. 429 * You can only have one reader per mimeType.</p> 430 * 431 * <p>In the absence of a mimeType (e.g., when reading a catalog 432 * directly from a file on the local system), the readers are attempted 433 * in the order that you add them to the Catalog.</p> 434 * 435 * <p>Note that subordinate catalogs (created by CATALOG or 436 * DELEGATE* entries) get a copy of the set of readers present in 437 * the primary catalog when they are created. Readers added subsequently 438 * will not be available. For this reason, it is best to add all 439 * of the readers before the first call to parse a catalog.</p> 440 * 441 * @param mimeType The MIME type associated with this reader. 442 * @param reader The CatalogReader to use. 443 */ 444 public void addReader(String mimeType, CatalogReader reader) { 445 if (readerMap.containsKey(mimeType)) { 446 Integer pos = (Integer) readerMap.get(mimeType); 447 readerArr.set(pos.intValue(), reader); 448 } else { 449 readerArr.add(reader); 450 Integer pos = new Integer(readerArr.size()-1); 451 readerMap.put(mimeType, pos); 452 } 453 } 454 455 /** 456 * Copies the reader list from the current Catalog to a new Catalog. 457 * 458 * <p>This method is used internally when constructing a new catalog. 459 * It copies the current reader associations over to the new catalog. 460 * </p> 461 * 462 * @param newCatalog The new Catalog. 463 */ 464 protected void copyReaders(Catalog newCatalog) { 465 // Have to copy the readers in the right order...convert hash to arr 466 Vector mapArr = new Vector(readerMap.size()); 467 468 // Pad the mapArr out to the right length 469 for (int count = 0; count < readerMap.size(); count++) { 470 mapArr.add(null); 471 } 472 473 Enumeration en = readerMap.keys(); 474 while (en.hasMoreElements()) { 475 String mimeType = (String) en.nextElement(); 476 Integer pos = (Integer) readerMap.get(mimeType); 477 mapArr.set(pos.intValue(), mimeType); 478 } 479 480 for (int count = 0; count < mapArr.size(); count++) { 481 String mimeType = (String) mapArr.get(count); 482 Integer pos = (Integer) readerMap.get(mimeType); 483 newCatalog.addReader(mimeType, 484 (CatalogReader) 485 readerArr.get(pos.intValue())); 486 } 487 } 488 489 /** 490 * Create a new Catalog object. 491 * 492 * <p>This method constructs a new instance of the running Catalog 493 * class (which might be a subtype of com.sun.org.apache.xml.internal.resolver.Catalog). 494 * All new catalogs are managed by the same CatalogManager. 495 * </p> 496 * 497 * <p>N.B. All Catalog subtypes should call newCatalog() to construct 498 * a new Catalog. Do not simply use "new Subclass()" since that will 499 * confuse future subclasses.</p> 500 */ 501 protected Catalog newCatalog() { 502 String catalogClass = this.getClass().getName(); 503 504 try { 505 Catalog c = (Catalog) (Class.forName(catalogClass).newInstance()); 506 c.setCatalogManager(catalogManager); 507 copyReaders(c); 508 return c; 509 } catch (ClassNotFoundException cnfe) { 510 catalogManager.debug.message(1, "Class Not Found Exception: " + catalogClass); 511 } catch (IllegalAccessException iae) { 512 catalogManager.debug.message(1, "Illegal Access Exception: " + catalogClass); 513 } catch (InstantiationException ie) { 514 catalogManager.debug.message(1, "Instantiation Exception: " + catalogClass); 515 } catch (ClassCastException cce) { 516 catalogManager.debug.message(1, "Class Cast Exception: " + catalogClass); 517 } catch (Exception e) { 518 catalogManager.debug.message(1, "Other Exception: " + catalogClass); 519 } 520 521 Catalog c = new Catalog(); 522 c.setCatalogManager(catalogManager); 523 copyReaders(c); 524 return c; 525 } 526 527 /** 528 * Returns the current base URI. 529 */ 530 public String getCurrentBase() { 531 return base.toString(); 532 } 533 534 /** 535 * Returns the default override setting associated with this 536 * catalog. 537 * 538 * <p>All catalog files loaded by this catalog will have the 539 * initial override setting specified by this default.</p> 540 */ 541 public String getDefaultOverride() { 542 if (default_override) { 543 return "yes"; 544 } else { 545 return "no"; 546 } 547 } 548 549 /** 550 * Load the system catalog files. 551 * 552 * <p>The method adds all of the 553 * catalogs specified in the <tt>xml.catalog.files</tt> property 554 * to the Catalog list.</p> 555 * 556 * @throws MalformedURLException One of the system catalogs is 557 * identified with a filename that is not a valid URL. 558 * @throws IOException One of the system catalogs cannot be read. 559 */ 560 public void loadSystemCatalogs() 561 throws MalformedURLException, IOException { 562 563 Vector catalogs = catalogManager.getCatalogFiles(); 564 if (catalogs != null) { 565 for (int count = 0; count < catalogs.size(); count++) { 566 catalogFiles.addElement(catalogs.elementAt(count)); 567 } 568 } 569 570 if (catalogFiles.size() > 0) { 571 // This is a little odd. The parseCatalog() method expects 572 // a filename, but it adds that name to the end of the 573 // catalogFiles vector, and then processes that vector. 574 // This allows the system to handle CATALOG entries 575 // correctly. 576 // 577 // In this init case, we take the last element off the 578 // catalogFiles vector and pass it to parseCatalog. This 579 // will "do the right thing" in the init case, and allow 580 // parseCatalog() to do the right thing in the non-init 581 // case. Honest. 582 // 583 String catfile = (String) catalogFiles.lastElement(); 584 catalogFiles.removeElement(catfile); 585 parseCatalog(catfile); 586 } 587 } 588 589 /** 590 * Parse a catalog file, augmenting internal data structures. 591 * 592 * @param fileName The filename of the catalog file to process 593 * 594 * @throws MalformedURLException The fileName cannot be turned into 595 * a valid URL. 596 * @throws IOException Error reading catalog file. 597 */ 598 public synchronized void parseCatalog(String fileName) 599 throws MalformedURLException, IOException { 600 601 default_override = catalogManager.getPreferPublic(); 602 catalogManager.debug.message(4, "Parse catalog: " + fileName); 603 604 // Put the file into the list of catalogs to process... 605 // In all cases except the case when initCatalog() is the 606 // caller, this will be the only catalog initially in the list... 607 catalogFiles.addElement(fileName); 608 609 // Now process all the pending catalogs... 610 parsePendingCatalogs(); 611 } 612 613 /** 614 * Parse a catalog file, augmenting internal data structures. 615 * 616 * <p>Catalogs retrieved over the net may have an associated MIME type. 617 * The MIME type can be used to select an appropriate reader.</p> 618 * 619 * @param mimeType The MIME type of the catalog file. 620 * @param is The InputStream from which the catalog should be read 621 * 622 * @throws CatalogException Failed to load catalog 623 * mimeType. 624 * @throws IOException Error reading catalog file. 625 */ 626 public synchronized void parseCatalog(String mimeType, InputStream is) 627 throws IOException, CatalogException { 628 629 default_override = catalogManager.getPreferPublic(); 630 catalogManager.debug.message(4, "Parse " + mimeType + " catalog on input stream"); 631 632 CatalogReader reader = null; 633 634 if (readerMap.containsKey(mimeType)) { 635 int arrayPos = ((Integer) readerMap.get(mimeType)).intValue(); 636 reader = (CatalogReader) readerArr.get(arrayPos); 637 } 638 639 if (reader == null) { 640 String msg = "No CatalogReader for MIME type: " + mimeType; 641 catalogManager.debug.message(2, msg); 642 throw new CatalogException(CatalogException.UNPARSEABLE, msg); 643 } 644 645 reader.readCatalog(this, is); 646 647 // Now process all the pending catalogs... 648 parsePendingCatalogs(); 649 } 650 651 /** 652 * Parse a catalog document, augmenting internal data structures. 653 * 654 * <p>This method supports catalog files stored in jar files: e.g., 655 * jar:file:///path/to/filename.jar!/path/to/catalog.xml". That URI 656 * doesn't survive transmogrification through the URI processing that 657 * the parseCatalog(String) performs and passing it as an input stream 658 * doesn't set the base URI appropriately.</p> 659 * 660 * <p>Written by Stefan Wachter (2002-09-26)</p> 661 * 662 * @param aUrl The URL of the catalog document to process 663 * 664 * @throws IOException Error reading catalog file. 665 */ 666 public synchronized void parseCatalog(URL aUrl) throws IOException { 667 catalogCwd = aUrl; 668 base = aUrl; 669 670 default_override = catalogManager.getPreferPublic(); 671 catalogManager.debug.message(4, "Parse catalog: " + aUrl.toString()); 672 673 DataInputStream inStream = null; 674 boolean parsed = false; 675 676 for (int count = 0; !parsed && count < readerArr.size(); count++) { 677 CatalogReader reader = (CatalogReader) readerArr.get(count); 678 679 try { 680 inStream = new DataInputStream(aUrl.openStream()); 681 } catch (FileNotFoundException fnfe) { 682 // No catalog; give up! 683 break; 684 } 685 686 try { 687 reader.readCatalog(this, inStream); 688 parsed=true; 689 } catch (CatalogException ce) { 690 if (ce.getExceptionType() == CatalogException.PARSE_FAILED) { 691 // give up! 692 break; 693 } else { 694 // try again! 695 } 696 } 697 698 try { 699 inStream.close(); 700 } catch (IOException e) { 701 //nop 702 } 703 } 704 705 if (parsed) parsePendingCatalogs(); 706 } 707 708 /** 709 * Parse all of the pending catalogs. 710 * 711 * <p>Catalogs may refer to other catalogs, this method parses 712 * all of the currently pending catalog files.</p> 713 */ 714 protected synchronized void parsePendingCatalogs() 715 throws MalformedURLException, IOException { 716 717 if (!localCatalogFiles.isEmpty()) { 718 // Move all the localCatalogFiles into the front of 719 // the catalogFiles queue 720 Vector newQueue = new Vector(); 721 Enumeration q = localCatalogFiles.elements(); 722 while (q.hasMoreElements()) { 723 newQueue.addElement(q.nextElement()); 724 } 725 726 // Put the rest of the catalogs on the end of the new list 727 for (int curCat = 0; curCat < catalogFiles.size(); curCat++) { 728 String catfile = (String) catalogFiles.elementAt(curCat); 729 newQueue.addElement(catfile); 730 } 731 732 catalogFiles = newQueue; 733 localCatalogFiles.clear(); 734 } 735 736 // Suppose there are no catalog files to process, but the 737 // single catalog already parsed included some delegate 738 // entries? Make sure they don't get lost. 739 if (catalogFiles.isEmpty() && !localDelegate.isEmpty()) { 740 Enumeration e = localDelegate.elements(); 741 while (e.hasMoreElements()) { 742 catalogEntries.addElement(e.nextElement()); 743 } 744 localDelegate.clear(); 745 } 746 747 // Now process all the files on the catalogFiles vector. This 748 // vector can grow during processing if CATALOG entries are 749 // encountered in the catalog 750 while (!catalogFiles.isEmpty()) { 751 String catfile = (String) catalogFiles.elementAt(0); 752 try { 753 catalogFiles.remove(0); 754 } catch (ArrayIndexOutOfBoundsException e) { 755 // can't happen 756 } 757 758 if (catalogEntries.size() == 0 && catalogs.size() == 0) { 759 // We haven't parsed any catalogs yet, let this 760 // catalog be the first... 761 try { 762 parseCatalogFile(catfile); 763 } catch (CatalogException ce) { 764 System.out.println("FIXME: " + ce.toString()); 765 } 766 } else { 767 // This is a subordinate catalog. We save its name, 768 // but don't bother to load it unless it's necessary. 769 catalogs.addElement(catfile); 770 } 771 772 if (!localCatalogFiles.isEmpty()) { 773 // Move all the localCatalogFiles into the front of 774 // the catalogFiles queue 775 Vector newQueue = new Vector(); 776 Enumeration q = localCatalogFiles.elements(); 777 while (q.hasMoreElements()) { 778 newQueue.addElement(q.nextElement()); 779 } 780 781 // Put the rest of the catalogs on the end of the new list 782 for (int curCat = 0; curCat < catalogFiles.size(); curCat++) { 783 catfile = (String) catalogFiles.elementAt(curCat); 784 newQueue.addElement(catfile); 785 } 786 787 catalogFiles = newQueue; 788 localCatalogFiles.clear(); 789 } 790 791 if (!localDelegate.isEmpty()) { 792 Enumeration e = localDelegate.elements(); 793 while (e.hasMoreElements()) { 794 catalogEntries.addElement(e.nextElement()); 795 } 796 localDelegate.clear(); 797 } 798 } 799 800 // We've parsed them all, reinit the vector... 801 catalogFiles.clear(); 802 } 803 804 /** 805 * Parse a single catalog file, augmenting internal data structures. 806 * 807 * @param fileName The filename of the catalog file to process 808 * 809 * @throws MalformedURLException The fileName cannot be turned into 810 * a valid URL. 811 * @throws IOException Error reading catalog file. 812 */ 813 protected synchronized void parseCatalogFile(String fileName) 814 throws MalformedURLException, IOException, CatalogException { 815 816 CatalogEntry entry; 817 818 // The base-base is the cwd. If the catalog file is specified 819 // with a relative path, this assures that it gets resolved 820 // properly... 821 try { 822 // tack on a basename because URLs point to files not dirs 823 catalogCwd = FileURL.makeURL("basename"); 824 } catch (MalformedURLException e) { 825 String userdir = SecuritySupport.getSystemProperty("user.dir"); 826 userdir.replace('\\', '/'); 827 catalogManager.debug.message(1, "Malformed URL on cwd", userdir); 828 catalogCwd = null; 829 } 830 831 // The initial base URI is the location of the catalog file 832 try { 833 base = new URL(catalogCwd, fixSlashes(fileName)); 834 } catch (MalformedURLException e) { 835 try { 836 base = new URL("file:" + fixSlashes(fileName)); 837 } catch (MalformedURLException e2) { 838 catalogManager.debug.message(1, "Malformed URL on catalog filename", 839 fixSlashes(fileName)); 840 base = null; 841 } 842 } 843 844 catalogManager.debug.message(2, "Loading catalog", fileName); 845 catalogManager.debug.message(4, "Default BASE", base.toString()); 846 847 fileName = base.toString(); 848 849 DataInputStream inStream = null; 850 boolean parsed = false; 851 boolean notFound = false; 852 853 for (int count = 0; !parsed && count < readerArr.size(); count++) { 854 CatalogReader reader = (CatalogReader) readerArr.get(count); 855 856 try { 857 notFound = false; 858 inStream = new DataInputStream(base.openStream()); 859 } catch (FileNotFoundException fnfe) { 860 // No catalog; give up! 861 notFound = true; 862 break; 863 } 864 865 try { 866 reader.readCatalog(this, inStream); 867 parsed = true; 868 } catch (CatalogException ce) { 869 if (ce.getExceptionType() == CatalogException.PARSE_FAILED) { 870 // give up! 871 break; 872 } else { 873 // try again! 874 } 875 } 876 877 try { 878 inStream.close(); 879 } catch (IOException e) { 880 //nop 881 } 882 } 883 884 if (!parsed) { 885 if (notFound) { 886 catalogManager.debug.message(3, "Catalog does not exist", fileName); 887 } else { 888 catalogManager.debug.message(1, "Failed to parse catalog", fileName); 889 } 890 } 891 } 892 893 /** 894 * Cleanup and process a Catalog entry. 895 * 896 * <p>This method processes each Catalog entry, changing mapped 897 * relative system identifiers into absolute ones (based on the current 898 * base URI), and maintaining other information about the current 899 * catalog.</p> 900 * 901 * @param entry The CatalogEntry to process. 902 */ 903 public void addEntry(CatalogEntry entry) { 904 int type = entry.getEntryType(); 905 906 if (type == BASE) { 907 String value = entry.getEntryArg(0); 908 URL newbase = null; 909 910 if (base == null) { 911 catalogManager.debug.message(5, "BASE CUR", "null"); 912 } else { 913 catalogManager.debug.message(5, "BASE CUR", base.toString()); 914 } 915 catalogManager.debug.message(4, "BASE STR", value); 916 917 try { 918 value = fixSlashes(value); 919 newbase = new URL(base, value); 920 } catch (MalformedURLException e) { 921 try { 922 newbase = new URL("file:" + value); 923 } catch (MalformedURLException e2) { 924 catalogManager.debug.message(1, "Malformed URL on base", value); 925 newbase = null; 926 } 927 } 928 929 if (newbase != null) { 930 base = newbase; 931 } 932 933 catalogManager.debug.message(5, "BASE NEW", base.toString()); 934 } else if (type == CATALOG) { 935 String fsi = makeAbsolute(entry.getEntryArg(0)); 936 937 catalogManager.debug.message(4, "CATALOG", fsi); 938 939 localCatalogFiles.addElement(fsi); 940 } else if (type == PUBLIC) { 941 String publicid = PublicId.normalize(entry.getEntryArg(0)); 942 String systemid = makeAbsolute(normalizeURI(entry.getEntryArg(1))); 943 944 entry.setEntryArg(0, publicid); 945 entry.setEntryArg(1, systemid); 946 947 catalogManager.debug.message(4, "PUBLIC", publicid, systemid); 948 949 catalogEntries.addElement(entry); 950 } else if (type == SYSTEM) { 951 String systemid = normalizeURI(entry.getEntryArg(0)); 952 String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1))); 953 954 entry.setEntryArg(1, fsi); 955 956 catalogManager.debug.message(4, "SYSTEM", systemid, fsi); 957 958 catalogEntries.addElement(entry); 959 } else if (type == URI) { 960 String uri = normalizeURI(entry.getEntryArg(0)); 961 String altURI = makeAbsolute(normalizeURI(entry.getEntryArg(1))); 962 963 entry.setEntryArg(1, altURI); 964 965 catalogManager.debug.message(4, "URI", uri, altURI); 966 967 catalogEntries.addElement(entry); 968 } else if (type == DOCUMENT) { 969 String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(0))); 970 entry.setEntryArg(0, fsi); 971 972 catalogManager.debug.message(4, "DOCUMENT", fsi); 973 974 catalogEntries.addElement(entry); 975 } else if (type == OVERRIDE) { 976 catalogManager.debug.message(4, "OVERRIDE", entry.getEntryArg(0)); 977 978 catalogEntries.addElement(entry); 979 } else if (type == SGMLDECL) { 980 // meaningless in XML 981 String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(0))); 982 entry.setEntryArg(0, fsi); 983 984 catalogManager.debug.message(4, "SGMLDECL", fsi); 985 986 catalogEntries.addElement(entry); 987 } else if (type == DELEGATE_PUBLIC) { 988 String ppi = PublicId.normalize(entry.getEntryArg(0)); 989 String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1))); 990 991 entry.setEntryArg(0, ppi); 992 entry.setEntryArg(1, fsi); 993 994 catalogManager.debug.message(4, "DELEGATE_PUBLIC", ppi, fsi); 995 996 addDelegate(entry); 997 } else if (type == DELEGATE_SYSTEM) { 998 String psi = normalizeURI(entry.getEntryArg(0)); 999 String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1))); 1000 1001 entry.setEntryArg(0, psi); 1002 entry.setEntryArg(1, fsi); 1003 1004 catalogManager.debug.message(4, "DELEGATE_SYSTEM", psi, fsi); 1005 1006 addDelegate(entry); 1007 } else if (type == DELEGATE_URI) { 1008 String pui = normalizeURI(entry.getEntryArg(0)); 1009 String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1))); 1010 1011 entry.setEntryArg(0, pui); 1012 entry.setEntryArg(1, fsi); 1013 1014 catalogManager.debug.message(4, "DELEGATE_URI", pui, fsi); 1015 1016 addDelegate(entry); 1017 } else if (type == REWRITE_SYSTEM) { 1018 String psi = normalizeURI(entry.getEntryArg(0)); 1019 String rpx = makeAbsolute(normalizeURI(entry.getEntryArg(1))); 1020 1021 entry.setEntryArg(0, psi); 1022 entry.setEntryArg(1, rpx); 1023 1024 catalogManager.debug.message(4, "REWRITE_SYSTEM", psi, rpx); 1025 1026 catalogEntries.addElement(entry); 1027 } else if (type == REWRITE_URI) { 1028 String pui = normalizeURI(entry.getEntryArg(0)); 1029 String upx = makeAbsolute(normalizeURI(entry.getEntryArg(1))); 1030 1031 entry.setEntryArg(0, pui); 1032 entry.setEntryArg(1, upx); 1033 1034 catalogManager.debug.message(4, "REWRITE_URI", pui, upx); 1035 1036 catalogEntries.addElement(entry); 1037 } else if (type == SYSTEM_SUFFIX) { 1038 String pui = normalizeURI(entry.getEntryArg(0)); 1039 String upx = makeAbsolute(normalizeURI(entry.getEntryArg(1))); 1040 1041 entry.setEntryArg(0, pui); 1042 entry.setEntryArg(1, upx); 1043 1044 catalogManager.debug.message(4, "SYSTEM_SUFFIX", pui, upx); 1045 1046 catalogEntries.addElement(entry); 1047 } else if (type == URI_SUFFIX) { 1048 String pui = normalizeURI(entry.getEntryArg(0)); 1049 String upx = makeAbsolute(normalizeURI(entry.getEntryArg(1))); 1050 1051 entry.setEntryArg(0, pui); 1052 entry.setEntryArg(1, upx); 1053 1054 catalogManager.debug.message(4, "URI_SUFFIX", pui, upx); 1055 1056 catalogEntries.addElement(entry); 1057 } else if (type == DOCTYPE) { 1058 String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1))); 1059 entry.setEntryArg(1, fsi); 1060 1061 catalogManager.debug.message(4, "DOCTYPE", entry.getEntryArg(0), fsi); 1062 1063 catalogEntries.addElement(entry); 1064 } else if (type == DTDDECL) { 1065 // meaningless in XML 1066 String fpi = PublicId.normalize(entry.getEntryArg(0)); 1067 entry.setEntryArg(0, fpi); 1068 String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1))); 1069 entry.setEntryArg(1, fsi); 1070 1071 catalogManager.debug.message(4, "DTDDECL", fpi, fsi); 1072 1073 catalogEntries.addElement(entry); 1074 } else if (type == ENTITY) { 1075 String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1))); 1076 entry.setEntryArg(1, fsi); 1077 1078 catalogManager.debug.message(4, "ENTITY", entry.getEntryArg(0), fsi); 1079 1080 catalogEntries.addElement(entry); 1081 } else if (type == LINKTYPE) { 1082 // meaningless in XML 1083 String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1))); 1084 entry.setEntryArg(1, fsi); 1085 1086 catalogManager.debug.message(4, "LINKTYPE", entry.getEntryArg(0), fsi); 1087 1088 catalogEntries.addElement(entry); 1089 } else if (type == NOTATION) { 1090 String fsi = makeAbsolute(normalizeURI(entry.getEntryArg(1))); 1091 entry.setEntryArg(1, fsi); 1092 1093 catalogManager.debug.message(4, "NOTATION", entry.getEntryArg(0), fsi); 1094 1095 catalogEntries.addElement(entry); 1096 } else { 1097 catalogEntries.addElement(entry); 1098 } 1099 } 1100 1101 /** 1102 * Handle unknown CatalogEntry types. 1103 * 1104 * <p>This method exists to allow subclasses to deal with unknown 1105 * entry types.</p> 1106 */ 1107 public void unknownEntry(Vector strings) { 1108 if (strings != null && strings.size() > 0) { 1109 String keyword = (String) strings.elementAt(0); 1110 catalogManager.debug.message(2, "Unrecognized token parsing catalog", keyword); 1111 } 1112 } 1113 1114 /** 1115 * Parse all subordinate catalogs. 1116 * 1117 * <p>This method recursively parses all of the subordinate catalogs. 1118 * If this method does not throw an exception, you can be confident that 1119 * no subsequent call to any resolve*() method will either, with two 1120 * possible exceptions:</p> 1121 * 1122 * <ol> 1123 * <li><p>Delegated catalogs are re-parsed each time they are needed 1124 * (because a variable list of them may be needed in each case, 1125 * depending on the length of the matching partial public identifier).</p> 1126 * <p>But they are parsed by this method, so as long as they don't 1127 * change or disappear while the program is running, they shouldn't 1128 * generate errors later if they don't generate errors now.</p> 1129 * <li><p>If you add new catalogs with <code>parseCatalog</code>, they 1130 * won't be loaded until they are needed or until you call 1131 * <code>parseAllCatalogs</code> again.</p> 1132 * </ol> 1133 * 1134 * <p>On the other hand, if you don't call this method, you may 1135 * successfully parse documents without having to load all possible 1136 * catalogs.</p> 1137 * 1138 * @throws MalformedURLException The filename (URL) for a 1139 * subordinate or delegated catalog is not a valid URL. 1140 * @throws IOException Error reading some subordinate or delegated 1141 * catalog file. 1142 */ 1143 public void parseAllCatalogs() 1144 throws MalformedURLException, IOException { 1145 1146 // Parse all the subordinate catalogs 1147 for (int catPos = 0; catPos < catalogs.size(); catPos++) { 1148 Catalog c = null; 1149 1150 try { 1151 c = (Catalog) catalogs.elementAt(catPos); 1152 } catch (ClassCastException e) { 1153 String catfile = (String) catalogs.elementAt(catPos); 1154 c = newCatalog(); 1155 1156 c.parseCatalog(catfile); 1157 catalogs.setElementAt(c, catPos); 1158 c.parseAllCatalogs(); 1159 } 1160 } 1161 1162 // Parse all the DELEGATE catalogs 1163 Enumeration en = catalogEntries.elements(); 1164 while (en.hasMoreElements()) { 1165 CatalogEntry e = (CatalogEntry) en.nextElement(); 1166 if (e.getEntryType() == DELEGATE_PUBLIC 1167 || e.getEntryType() == DELEGATE_SYSTEM 1168 || e.getEntryType() == DELEGATE_URI) { 1169 Catalog dcat = newCatalog(); 1170 dcat.parseCatalog(e.getEntryArg(1)); 1171 } 1172 } 1173 } 1174 1175 1176 /** 1177 * Return the applicable DOCTYPE system identifier. 1178 * 1179 * @param entityName The name of the entity (element) for which 1180 * a doctype is required. 1181 * @param publicId The nominal public identifier for the doctype 1182 * (as provided in the source document). 1183 * @param systemId The nominal system identifier for the doctype 1184 * (as provided in the source document). 1185 * 1186 * @return The system identifier to use for the doctype. 1187 * 1188 * @throws MalformedURLException The formal system identifier of a 1189 * subordinate catalog cannot be turned into a valid URL. 1190 * @throws IOException Error reading subordinate catalog file. 1191 */ 1192 public String resolveDoctype(String entityName, 1193 String publicId, 1194 String systemId) 1195 throws MalformedURLException, IOException { 1196 String resolved = null; 1197 1198 catalogManager.debug.message(3, "resolveDoctype(" 1199 +entityName+","+publicId+","+systemId+")"); 1200 1201 systemId = normalizeURI(systemId); 1202 1203 if (publicId != null && publicId.startsWith("urn:publicid:")) { 1204 publicId = PublicId.decodeURN(publicId); 1205 } 1206 1207 if (systemId != null && systemId.startsWith("urn:publicid:")) { 1208 systemId = PublicId.decodeURN(systemId); 1209 if (publicId != null && !publicId.equals(systemId)) { 1210 catalogManager.debug.message(1, "urn:publicid: system identifier differs from public identifier; using public identifier"); 1211 systemId = null; 1212 } else { 1213 publicId = systemId; 1214 systemId = null; 1215 } 1216 } 1217 1218 if (systemId != null) { 1219 // If there's a SYSTEM entry in this catalog, use it 1220 resolved = resolveLocalSystem(systemId); 1221 if (resolved != null) { 1222 return resolved; 1223 } 1224 } 1225 1226 if (publicId != null) { 1227 // If there's a PUBLIC entry in this catalog, use it 1228 resolved = resolveLocalPublic(DOCTYPE, 1229 entityName, 1230 publicId, 1231 systemId); 1232 if (resolved != null) { 1233 return resolved; 1234 } 1235 } 1236 1237 // If there's a DOCTYPE entry in this catalog, use it 1238 boolean over = default_override; 1239 Enumeration en = catalogEntries.elements(); 1240 while (en.hasMoreElements()) { 1241 CatalogEntry e = (CatalogEntry) en.nextElement(); 1242 if (e.getEntryType() == OVERRIDE) { 1243 over = e.getEntryArg(0).equalsIgnoreCase("YES"); 1244 continue; 1245 } 1246 1247 if (e.getEntryType() == DOCTYPE 1248 && e.getEntryArg(0).equals(entityName)) { 1249 if (over || systemId == null) { 1250 return e.getEntryArg(1); 1251 } 1252 } 1253 } 1254 1255 // Otherwise, look in the subordinate catalogs 1256 return resolveSubordinateCatalogs(DOCTYPE, 1257 entityName, 1258 publicId, 1259 systemId); 1260 } 1261 1262 /** 1263 * Return the applicable DOCUMENT entry. 1264 * 1265 * @return The system identifier to use for the doctype. 1266 * 1267 * @throws MalformedURLException The formal system identifier of a 1268 * subordinate catalog cannot be turned into a valid URL. 1269 * @throws IOException Error reading subordinate catalog file. 1270 */ 1271 public String resolveDocument() 1272 throws MalformedURLException, IOException { 1273 // If there's a DOCUMENT entry, return it 1274 1275 catalogManager.debug.message(3, "resolveDocument"); 1276 1277 Enumeration en = catalogEntries.elements(); 1278 while (en.hasMoreElements()) { 1279 CatalogEntry e = (CatalogEntry) en.nextElement(); 1280 if (e.getEntryType() == DOCUMENT) { 1281 return e.getEntryArg(0); 1282 } 1283 } 1284 1285 return resolveSubordinateCatalogs(DOCUMENT, 1286 null, null, null); 1287 } 1288 1289 /** 1290 * Return the applicable ENTITY system identifier. 1291 * 1292 * @param entityName The name of the entity for which 1293 * a system identifier is required. 1294 * @param publicId The nominal public identifier for the entity 1295 * (as provided in the source document). 1296 * @param systemId The nominal system identifier for the entity 1297 * (as provided in the source document). 1298 * 1299 * @return The system identifier to use for the entity. 1300 * 1301 * @throws MalformedURLException The formal system identifier of a 1302 * subordinate catalog cannot be turned into a valid URL. 1303 * @throws IOException Error reading subordinate catalog file. 1304 */ 1305 public String resolveEntity(String entityName, 1306 String publicId, 1307 String systemId) 1308 throws MalformedURLException, IOException { 1309 String resolved = null; 1310 1311 catalogManager.debug.message(3, "resolveEntity(" 1312 +entityName+","+publicId+","+systemId+")"); 1313 1314 systemId = normalizeURI(systemId); 1315 1316 if (publicId != null && publicId.startsWith("urn:publicid:")) { 1317 publicId = PublicId.decodeURN(publicId); 1318 } 1319 1320 if (systemId != null && systemId.startsWith("urn:publicid:")) { 1321 systemId = PublicId.decodeURN(systemId); 1322 if (publicId != null && !publicId.equals(systemId)) { 1323 catalogManager.debug.message(1, "urn:publicid: system identifier differs from public identifier; using public identifier"); 1324 systemId = null; 1325 } else { 1326 publicId = systemId; 1327 systemId = null; 1328 } 1329 } 1330 1331 if (systemId != null) { 1332 // If there's a SYSTEM entry in this catalog, use it 1333 resolved = resolveLocalSystem(systemId); 1334 if (resolved != null) { 1335 return resolved; 1336 } 1337 } 1338 1339 if (publicId != null) { 1340 // If there's a PUBLIC entry in this catalog, use it 1341 resolved = resolveLocalPublic(ENTITY, 1342 entityName, 1343 publicId, 1344 systemId); 1345 if (resolved != null) { 1346 return resolved; 1347 } 1348 } 1349 1350 // If there's a ENTITY entry in this catalog, use it 1351 boolean over = default_override; 1352 Enumeration en = catalogEntries.elements(); 1353 while (en.hasMoreElements()) { 1354 CatalogEntry e = (CatalogEntry) en.nextElement(); 1355 if (e.getEntryType() == OVERRIDE) { 1356 over = e.getEntryArg(0).equalsIgnoreCase("YES"); 1357 continue; 1358 } 1359 1360 if (e.getEntryType() == ENTITY 1361 && e.getEntryArg(0).equals(entityName)) { 1362 if (over || systemId == null) { 1363 return e.getEntryArg(1); 1364 } 1365 } 1366 } 1367 1368 // Otherwise, look in the subordinate catalogs 1369 return resolveSubordinateCatalogs(ENTITY, 1370 entityName, 1371 publicId, 1372 systemId); 1373 } 1374 1375 /** 1376 * Return the applicable NOTATION system identifier. 1377 * 1378 * @param notationName The name of the notation for which 1379 * a doctype is required. 1380 * @param publicId The nominal public identifier for the notation 1381 * (as provided in the source document). 1382 * @param systemId The nominal system identifier for the notation 1383 * (as provided in the source document). 1384 * 1385 * @return The system identifier to use for the notation. 1386 * 1387 * @throws MalformedURLException The formal system identifier of a 1388 * subordinate catalog cannot be turned into a valid URL. 1389 * @throws IOException Error reading subordinate catalog file. 1390 */ 1391 public String resolveNotation(String notationName, 1392 String publicId, 1393 String systemId) 1394 throws MalformedURLException, IOException { 1395 String resolved = null; 1396 1397 catalogManager.debug.message(3, "resolveNotation(" 1398 +notationName+","+publicId+","+systemId+")"); 1399 1400 systemId = normalizeURI(systemId); 1401 1402 if (publicId != null && publicId.startsWith("urn:publicid:")) { 1403 publicId = PublicId.decodeURN(publicId); 1404 } 1405 1406 if (systemId != null && systemId.startsWith("urn:publicid:")) { 1407 systemId = PublicId.decodeURN(systemId); 1408 if (publicId != null && !publicId.equals(systemId)) { 1409 catalogManager.debug.message(1, "urn:publicid: system identifier differs from public identifier; using public identifier"); 1410 systemId = null; 1411 } else { 1412 publicId = systemId; 1413 systemId = null; 1414 } 1415 } 1416 1417 if (systemId != null) { 1418 // If there's a SYSTEM entry in this catalog, use it 1419 resolved = resolveLocalSystem(systemId); 1420 if (resolved != null) { 1421 return resolved; 1422 } 1423 } 1424 1425 if (publicId != null) { 1426 // If there's a PUBLIC entry in this catalog, use it 1427 resolved = resolveLocalPublic(NOTATION, 1428 notationName, 1429 publicId, 1430 systemId); 1431 if (resolved != null) { 1432 return resolved; 1433 } 1434 } 1435 1436 // If there's a NOTATION entry in this catalog, use it 1437 boolean over = default_override; 1438 Enumeration en = catalogEntries.elements(); 1439 while (en.hasMoreElements()) { 1440 CatalogEntry e = (CatalogEntry) en.nextElement(); 1441 if (e.getEntryType() == OVERRIDE) { 1442 over = e.getEntryArg(0).equalsIgnoreCase("YES"); 1443 continue; 1444 } 1445 1446 if (e.getEntryType() == NOTATION 1447 && e.getEntryArg(0).equals(notationName)) { 1448 if (over || systemId == null) { 1449 return e.getEntryArg(1); 1450 } 1451 } 1452 } 1453 1454 // Otherwise, look in the subordinate catalogs 1455 return resolveSubordinateCatalogs(NOTATION, 1456 notationName, 1457 publicId, 1458 systemId); 1459 } 1460 1461 /** 1462 * Return the applicable PUBLIC or SYSTEM identifier. 1463 * 1464 * <p>This method searches the Catalog and returns the system 1465 * identifier specified for the given system or 1466 * public identifiers. If 1467 * no appropriate PUBLIC or SYSTEM entry is found in the Catalog, 1468 * null is returned.</p> 1469 * 1470 * @param publicId The public identifier to locate in the catalog. 1471 * Public identifiers are normalized before comparison. 1472 * @param systemId The nominal system identifier for the entity 1473 * in question (as provided in the source document). 1474 * 1475 * @throws MalformedURLException The formal system identifier of a 1476 * subordinate catalog cannot be turned into a valid URL. 1477 * @throws IOException Error reading subordinate catalog file. 1478 * 1479 * @return The system identifier to use. 1480 * Note that the nominal system identifier is not returned if a 1481 * match is not found in the catalog, instead null is returned 1482 * to indicate that no match was found. 1483 */ 1484 public String resolvePublic(String publicId, String systemId) 1485 throws MalformedURLException, IOException { 1486 1487 catalogManager.debug.message(3, "resolvePublic("+publicId+","+systemId+")"); 1488 1489 systemId = normalizeURI(systemId); 1490 1491 if (publicId != null && publicId.startsWith("urn:publicid:")) { 1492 publicId = PublicId.decodeURN(publicId); 1493 } 1494 1495 if (systemId != null && systemId.startsWith("urn:publicid:")) { 1496 systemId = PublicId.decodeURN(systemId); 1497 if (publicId != null && !publicId.equals(systemId)) { 1498 catalogManager.debug.message(1, "urn:publicid: system identifier differs from public identifier; using public identifier"); 1499 systemId = null; 1500 } else { 1501 publicId = systemId; 1502 systemId = null; 1503 } 1504 } 1505 1506 // If there's a SYSTEM entry in this catalog, use it 1507 if (systemId != null) { 1508 String resolved = resolveLocalSystem(systemId); 1509 if (resolved != null) { 1510 return resolved; 1511 } 1512 } 1513 1514 // If there's a PUBLIC entry in this catalog, use it 1515 String resolved = resolveLocalPublic(PUBLIC, 1516 null, 1517 publicId, 1518 systemId); 1519 if (resolved != null) { 1520 return resolved; 1521 } 1522 1523 // Otherwise, look in the subordinate catalogs 1524 return resolveSubordinateCatalogs(PUBLIC, 1525 null, 1526 publicId, 1527 systemId); 1528 } 1529 1530 /** 1531 * Return the applicable PUBLIC or SYSTEM identifier. 1532 * 1533 * <p>This method searches the Catalog and returns the system 1534 * identifier specified for the given system or public identifiers. 1535 * If no appropriate PUBLIC or SYSTEM entry is found in the Catalog, 1536 * delegated Catalogs are interrogated.</p> 1537 * 1538 * <p>There are four possible cases:</p> 1539 * 1540 * <ul> 1541 * <li>If the system identifier provided matches a SYSTEM entry 1542 * in the current catalog, the SYSTEM entry is returned. 1543 * <li>If the system identifier is not null, the PUBLIC entries 1544 * that were encountered when OVERRIDE YES was in effect are 1545 * interrogated and the first matching entry is returned.</li> 1546 * <li>If the system identifier is null, then all of the PUBLIC 1547 * entries are interrogated and the first matching entry 1548 * is returned. This may not be the same as the preceding case, if 1549 * some PUBLIC entries are encountered when OVERRIDE NO is in effect. In 1550 * XML, the only place where a public identifier may occur without 1551 * a system identifier is in a notation declaration.</li> 1552 * <li>Finally, if the public identifier matches one of the partial 1553 * public identifiers specified in a DELEGATE* entry in 1554 * the Catalog, the delegated catalog is interrogated. The first 1555 * time that the delegated catalog is required, it will be 1556 * retrieved and parsed. It is subsequently cached. 1557 * </li> 1558 * </ul> 1559 * 1560 * @param entityType The CatalogEntry type for which this query is 1561 * being conducted. This is necessary in order to do the approprate 1562 * query on a delegated catalog. 1563 * @param entityName The name of the entity being searched for, if 1564 * appropriate. 1565 * @param publicId The public identifier of the entity in question. 1566 * @param systemId The nominal system identifier for the entity 1567 * in question (as provided in the source document). 1568 * 1569 * @throws MalformedURLException The formal system identifier of a 1570 * delegated catalog cannot be turned into a valid URL. 1571 * @throws IOException Error reading delegated catalog file. 1572 * 1573 * @return The system identifier to use. 1574 * Note that the nominal system identifier is not returned if a 1575 * match is not found in the catalog, instead null is returned 1576 * to indicate that no match was found. 1577 */ 1578 protected synchronized String resolveLocalPublic(int entityType, 1579 String entityName, 1580 String publicId, 1581 String systemId) 1582 throws MalformedURLException, IOException { 1583 1584 // Always normalize the public identifier before attempting a match 1585 publicId = PublicId.normalize(publicId); 1586 1587 // If there's a SYSTEM entry in this catalog, use it 1588 if (systemId != null) { 1589 String resolved = resolveLocalSystem(systemId); 1590 if (resolved != null) { 1591 return resolved; 1592 } 1593 } 1594 1595 // If there's a PUBLIC entry in this catalog, use it 1596 boolean over = default_override; 1597 Enumeration en = catalogEntries.elements(); 1598 while (en.hasMoreElements()) { 1599 CatalogEntry e = (CatalogEntry) en.nextElement(); 1600 if (e.getEntryType() == OVERRIDE) { 1601 over = e.getEntryArg(0).equalsIgnoreCase("YES"); 1602 continue; 1603 } 1604 1605 if (e.getEntryType() == PUBLIC 1606 && e.getEntryArg(0).equals(publicId)) { 1607 if (over || systemId == null) { 1608 return e.getEntryArg(1); 1609 } 1610 } 1611 } 1612 1613 // If there's a DELEGATE_PUBLIC entry in this catalog, use it 1614 over = default_override; 1615 en = catalogEntries.elements(); 1616 Vector delCats = new Vector(); 1617 while (en.hasMoreElements()) { 1618 CatalogEntry e = (CatalogEntry) en.nextElement(); 1619 if (e.getEntryType() == OVERRIDE) { 1620 over = e.getEntryArg(0).equalsIgnoreCase("YES"); 1621 continue; 1622 } 1623 1624 if (e.getEntryType() == DELEGATE_PUBLIC 1625 && (over || systemId == null)) { 1626 String p = (String) e.getEntryArg(0); 1627 if (p.length() <= publicId.length() 1628 && p.equals(publicId.substring(0, p.length()))) { 1629 // delegate this match to the other catalog 1630 1631 delCats.addElement(e.getEntryArg(1)); 1632 } 1633 } 1634 } 1635 1636 if (delCats.size() > 0) { 1637 Enumeration enCats = delCats.elements(); 1638 1639 if (catalogManager.debug.getDebug() > 1) { 1640 catalogManager.debug.message(2, "Switching to delegated catalog(s):"); 1641 while (enCats.hasMoreElements()) { 1642 String delegatedCatalog = (String) enCats.nextElement(); 1643 catalogManager.debug.message(2, "\t" + delegatedCatalog); 1644 } 1645 } 1646 1647 Catalog dcat = newCatalog(); 1648 1649 enCats = delCats.elements(); 1650 while (enCats.hasMoreElements()) { 1651 String delegatedCatalog = (String) enCats.nextElement(); 1652 dcat.parseCatalog(delegatedCatalog); 1653 } 1654 1655 return dcat.resolvePublic(publicId, null); 1656 } 1657 1658 // Nada! 1659 return null; 1660 } 1661 1662 /** 1663 * Return the applicable SYSTEM system identifier. 1664 * 1665 * <p>If a SYSTEM entry exists in the Catalog 1666 * for the system ID specified, return the mapped value.</p> 1667 * 1668 * <p>On Windows-based operating systems, the comparison between 1669 * the system identifier provided and the SYSTEM entries in the 1670 * Catalog is case-insensitive.</p> 1671 * 1672 * @param systemId The system ID to locate in the catalog. 1673 * 1674 * @return The resolved system identifier. 1675 * 1676 * @throws MalformedURLException The formal system identifier of a 1677 * subordinate catalog cannot be turned into a valid URL. 1678 * @throws IOException Error reading subordinate catalog file. 1679 */ 1680 public String resolveSystem(String systemId) 1681 throws MalformedURLException, IOException { 1682 1683 catalogManager.debug.message(3, "resolveSystem("+systemId+")"); 1684 1685 systemId = normalizeURI(systemId); 1686 1687 if (systemId != null && systemId.startsWith("urn:publicid:")) { 1688 systemId = PublicId.decodeURN(systemId); 1689 return resolvePublic(systemId, null); 1690 } 1691 1692 // If there's a SYSTEM entry in this catalog, use it 1693 if (systemId != null) { 1694 String resolved = resolveLocalSystem(systemId); 1695 if (resolved != null) { 1696 return resolved; 1697 } 1698 } 1699 1700 // Otherwise, look in the subordinate catalogs 1701 return resolveSubordinateCatalogs(SYSTEM, 1702 null, 1703 null, 1704 systemId); 1705 } 1706 1707 /** 1708 * Return the applicable SYSTEM system identifier in this 1709 * catalog. 1710 * 1711 * <p>If a SYSTEM entry exists in the catalog file 1712 * for the system ID specified, return the mapped value.</p> 1713 * 1714 * @param systemId The system ID to locate in the catalog 1715 * 1716 * @return The mapped system identifier or null 1717 */ 1718 protected String resolveLocalSystem(String systemId) 1719 throws MalformedURLException, IOException { 1720 1721 String osname = SecuritySupport.getSystemProperty("os.name"); 1722 boolean windows = (osname.indexOf("Windows") >= 0); 1723 Enumeration en = catalogEntries.elements(); 1724 while (en.hasMoreElements()) { 1725 CatalogEntry e = (CatalogEntry) en.nextElement(); 1726 if (e.getEntryType() == SYSTEM 1727 && (e.getEntryArg(0).equals(systemId) 1728 || (windows 1729 && e.getEntryArg(0).equalsIgnoreCase(systemId)))) { 1730 return e.getEntryArg(1); 1731 } 1732 } 1733 1734 // If there's a REWRITE_SYSTEM entry in this catalog, use it 1735 en = catalogEntries.elements(); 1736 String startString = null; 1737 String prefix = null; 1738 while (en.hasMoreElements()) { 1739 CatalogEntry e = (CatalogEntry) en.nextElement(); 1740 1741 if (e.getEntryType() == REWRITE_SYSTEM) { 1742 String p = (String) e.getEntryArg(0); 1743 if (p.length() <= systemId.length() 1744 && p.equals(systemId.substring(0, p.length()))) { 1745 // Is this the longest prefix? 1746 if (startString == null 1747 || p.length() > startString.length()) { 1748 startString = p; 1749 prefix = e.getEntryArg(1); 1750 } 1751 } 1752 } 1753 } 1754 1755 if (prefix != null) { 1756 // return the systemId with the new prefix 1757 return prefix + systemId.substring(startString.length()); 1758 } 1759 1760 // If there's a SYSTEM_SUFFIX entry in this catalog, use it 1761 en = catalogEntries.elements(); 1762 String suffixString = null; 1763 String suffixURI = null; 1764 while (en.hasMoreElements()) { 1765 CatalogEntry e = (CatalogEntry) en.nextElement(); 1766 1767 if (e.getEntryType() == SYSTEM_SUFFIX) { 1768 String p = (String) e.getEntryArg(0); 1769 if (p.length() <= systemId.length() 1770 && systemId.endsWith(p)) { 1771 // Is this the longest prefix? 1772 if (suffixString == null 1773 || p.length() > suffixString.length()) { 1774 suffixString = p; 1775 suffixURI = e.getEntryArg(1); 1776 } 1777 } 1778 } 1779 } 1780 1781 if (suffixURI != null) { 1782 // return the systemId for the suffix 1783 return suffixURI; 1784 } 1785 1786 // If there's a DELEGATE_SYSTEM entry in this catalog, use it 1787 en = catalogEntries.elements(); 1788 Vector delCats = new Vector(); 1789 while (en.hasMoreElements()) { 1790 CatalogEntry e = (CatalogEntry) en.nextElement(); 1791 1792 if (e.getEntryType() == DELEGATE_SYSTEM) { 1793 String p = (String) e.getEntryArg(0); 1794 if (p.length() <= systemId.length() 1795 && p.equals(systemId.substring(0, p.length()))) { 1796 // delegate this match to the other catalog 1797 1798 delCats.addElement(e.getEntryArg(1)); 1799 } 1800 } 1801 } 1802 1803 if (delCats.size() > 0) { 1804 Enumeration enCats = delCats.elements(); 1805 1806 if (catalogManager.debug.getDebug() > 1) { 1807 catalogManager.debug.message(2, "Switching to delegated catalog(s):"); 1808 while (enCats.hasMoreElements()) { 1809 String delegatedCatalog = (String) enCats.nextElement(); 1810 catalogManager.debug.message(2, "\t" + delegatedCatalog); 1811 } 1812 } 1813 1814 Catalog dcat = newCatalog(); 1815 1816 enCats = delCats.elements(); 1817 while (enCats.hasMoreElements()) { 1818 String delegatedCatalog = (String) enCats.nextElement(); 1819 dcat.parseCatalog(delegatedCatalog); 1820 } 1821 1822 return dcat.resolveSystem(systemId); 1823 } 1824 1825 return null; 1826 } 1827 1828 /** 1829 * Return the applicable URI. 1830 * 1831 * <p>If a URI entry exists in the Catalog 1832 * for the URI specified, return the mapped value.</p> 1833 * 1834 * <p>URI comparison is case sensitive.</p> 1835 * 1836 * @param uri The URI to locate in the catalog. 1837 * 1838 * @return The resolved URI. 1839 * 1840 * @throws MalformedURLException The system identifier of a 1841 * subordinate catalog cannot be turned into a valid URL. 1842 * @throws IOException Error reading subordinate catalog file. 1843 */ 1844 public String resolveURI(String uri) 1845 throws MalformedURLException, IOException { 1846 1847 catalogManager.debug.message(3, "resolveURI("+uri+")"); 1848 1849 uri = normalizeURI(uri); 1850 1851 if (uri != null && uri.startsWith("urn:publicid:")) { 1852 uri = PublicId.decodeURN(uri); 1853 return resolvePublic(uri, null); 1854 } 1855 1856 // If there's a URI entry in this catalog, use it 1857 if (uri != null) { 1858 String resolved = resolveLocalURI(uri); 1859 if (resolved != null) { 1860 return resolved; 1861 } 1862 } 1863 1864 // Otherwise, look in the subordinate catalogs 1865 return resolveSubordinateCatalogs(URI, 1866 null, 1867 null, 1868 uri); 1869 } 1870 1871 /** 1872 * Return the applicable URI in this catalog. 1873 * 1874 * <p>If a URI entry exists in the catalog file 1875 * for the URI specified, return the mapped value.</p> 1876 * 1877 * @param uri The URI to locate in the catalog 1878 * 1879 * @return The mapped URI or null 1880 */ 1881 protected String resolveLocalURI(String uri) 1882 throws MalformedURLException, IOException { 1883 Enumeration en = catalogEntries.elements(); 1884 while (en.hasMoreElements()) { 1885 CatalogEntry e = (CatalogEntry) en.nextElement(); 1886 if (e.getEntryType() == URI 1887 && (e.getEntryArg(0).equals(uri))) { 1888 return e.getEntryArg(1); 1889 } 1890 } 1891 1892 // If there's a REWRITE_URI entry in this catalog, use it 1893 en = catalogEntries.elements(); 1894 String startString = null; 1895 String prefix = null; 1896 while (en.hasMoreElements()) { 1897 CatalogEntry e = (CatalogEntry) en.nextElement(); 1898 1899 if (e.getEntryType() == REWRITE_URI) { 1900 String p = (String) e.getEntryArg(0); 1901 if (p.length() <= uri.length() 1902 && p.equals(uri.substring(0, p.length()))) { 1903 // Is this the longest prefix? 1904 if (startString == null 1905 || p.length() > startString.length()) { 1906 startString = p; 1907 prefix = e.getEntryArg(1); 1908 } 1909 } 1910 } 1911 } 1912 1913 if (prefix != null) { 1914 // return the uri with the new prefix 1915 return prefix + uri.substring(startString.length()); 1916 } 1917 1918 // If there's a URI_SUFFIX entry in this catalog, use it 1919 en = catalogEntries.elements(); 1920 String suffixString = null; 1921 String suffixURI = null; 1922 while (en.hasMoreElements()) { 1923 CatalogEntry e = (CatalogEntry) en.nextElement(); 1924 1925 if (e.getEntryType() == URI_SUFFIX) { 1926 String p = (String) e.getEntryArg(0); 1927 if (p.length() <= uri.length() 1928 && uri.endsWith(p)) { 1929 // Is this the longest prefix? 1930 if (suffixString == null 1931 || p.length() > suffixString.length()) { 1932 suffixString = p; 1933 suffixURI = e.getEntryArg(1); 1934 } 1935 } 1936 } 1937 } 1938 1939 if (suffixURI != null) { 1940 // return the uri for the suffix 1941 return suffixURI; 1942 } 1943 1944 // If there's a DELEGATE_URI entry in this catalog, use it 1945 en = catalogEntries.elements(); 1946 Vector delCats = new Vector(); 1947 while (en.hasMoreElements()) { 1948 CatalogEntry e = (CatalogEntry) en.nextElement(); 1949 1950 if (e.getEntryType() == DELEGATE_URI) { 1951 String p = (String) e.getEntryArg(0); 1952 if (p.length() <= uri.length() 1953 && p.equals(uri.substring(0, p.length()))) { 1954 // delegate this match to the other catalog 1955 1956 delCats.addElement(e.getEntryArg(1)); 1957 } 1958 } 1959 } 1960 1961 if (delCats.size() > 0) { 1962 Enumeration enCats = delCats.elements(); 1963 1964 if (catalogManager.debug.getDebug() > 1) { 1965 catalogManager.debug.message(2, "Switching to delegated catalog(s):"); 1966 while (enCats.hasMoreElements()) { 1967 String delegatedCatalog = (String) enCats.nextElement(); 1968 catalogManager.debug.message(2, "\t" + delegatedCatalog); 1969 } 1970 } 1971 1972 Catalog dcat = newCatalog(); 1973 1974 enCats = delCats.elements(); 1975 while (enCats.hasMoreElements()) { 1976 String delegatedCatalog = (String) enCats.nextElement(); 1977 dcat.parseCatalog(delegatedCatalog); 1978 } 1979 1980 return dcat.resolveURI(uri); 1981 } 1982 1983 return null; 1984 } 1985 1986 /** 1987 * Search the subordinate catalogs, in order, looking for a match. 1988 * 1989 * <p>This method searches the Catalog and returns the system 1990 * identifier specified for the given entity type with the given 1991 * name, public, and system identifiers. In some contexts, these 1992 * may be null.</p> 1993 * 1994 * @param entityType The CatalogEntry type for which this query is 1995 * being conducted. This is necessary in order to do the approprate 1996 * query on a subordinate catalog. 1997 * @param entityName The name of the entity being searched for, if 1998 * appropriate. 1999 * @param publicId The public identifier of the entity in question 2000 * (as provided in the source document). 2001 * @param systemId The nominal system identifier for the entity 2002 * in question (as provided in the source document). This parameter is 2003 * overloaded for the URI entry type. 2004 * 2005 * @throws MalformedURLException The formal system identifier of a 2006 * delegated catalog cannot be turned into a valid URL. 2007 * @throws IOException Error reading delegated catalog file. 2008 * 2009 * @return The system identifier to use. 2010 * Note that the nominal system identifier is not returned if a 2011 * match is not found in the catalog, instead null is returned 2012 * to indicate that no match was found. 2013 */ 2014 protected synchronized String resolveSubordinateCatalogs(int entityType, 2015 String entityName, 2016 String publicId, 2017 String systemId) 2018 throws MalformedURLException, IOException { 2019 2020 for (int catPos = 0; catPos < catalogs.size(); catPos++) { 2021 Catalog c = null; 2022 2023 try { 2024 c = (Catalog) catalogs.elementAt(catPos); 2025 } catch (ClassCastException e) { 2026 String catfile = (String) catalogs.elementAt(catPos); 2027 c = newCatalog(); 2028 2029 try { 2030 c.parseCatalog(catfile); 2031 } catch (MalformedURLException mue) { 2032 catalogManager.debug.message(1, "Malformed Catalog URL", catfile); 2033 } catch (FileNotFoundException fnfe) { 2034 catalogManager.debug.message(1, "Failed to load catalog, file not found", 2035 catfile); 2036 } catch (IOException ioe) { 2037 catalogManager.debug.message(1, "Failed to load catalog, I/O error", catfile); 2038 } 2039 2040 catalogs.setElementAt(c, catPos); 2041 } 2042 2043 String resolved = null; 2044 2045 // Ok, now what are we supposed to call here? 2046 if (entityType == DOCTYPE) { 2047 resolved = c.resolveDoctype(entityName, 2048 publicId, 2049 systemId); 2050 } else if (entityType == DOCUMENT) { 2051 resolved = c.resolveDocument(); 2052 } else if (entityType == ENTITY) { 2053 resolved = c.resolveEntity(entityName, 2054 publicId, 2055 systemId); 2056 } else if (entityType == NOTATION) { 2057 resolved = c.resolveNotation(entityName, 2058 publicId, 2059 systemId); 2060 } else if (entityType == PUBLIC) { 2061 resolved = c.resolvePublic(publicId, systemId); 2062 } else if (entityType == SYSTEM) { 2063 resolved = c.resolveSystem(systemId); 2064 } else if (entityType == URI) { 2065 resolved = c.resolveURI(systemId); 2066 } 2067 2068 if (resolved != null) { 2069 return resolved; 2070 } 2071 } 2072 2073 return null; 2074 } 2075 2076 // ----------------------------------------------------------------- 2077 2078 /** 2079 * Replace backslashes with forward slashes. (URLs always use 2080 * forward slashes.) 2081 * 2082 * @param sysid The input system identifier. 2083 * @return The same system identifier with backslashes turned into 2084 * forward slashes. 2085 */ 2086 protected String fixSlashes (String sysid) { 2087 return sysid.replace('\\', '/'); 2088 } 2089 2090 /** 2091 * Construct an absolute URI from a relative one, using the current 2092 * base URI. 2093 * 2094 * @param sysid The (possibly relative) system identifier 2095 * @return The system identifier made absolute with respect to the 2096 * current {@link #base}. 2097 */ 2098 protected String makeAbsolute(String sysid) { 2099 URL local = null; 2100 2101 sysid = fixSlashes(sysid); 2102 2103 try { 2104 local = new URL(base, sysid); 2105 } catch (MalformedURLException e) { 2106 catalogManager.debug.message(1, "Malformed URL on system identifier", sysid); 2107 } 2108 2109 if (local != null) { 2110 return local.toString(); 2111 } else { 2112 return sysid; 2113 } 2114 } 2115 2116 /** 2117 * Perform character normalization on a URI reference. 2118 * 2119 * @param uriref The URI reference 2120 * @return The normalized URI reference. 2121 */ 2122 protected String normalizeURI(String uriref) { 2123 if (uriref == null) { 2124 return null; 2125 } 2126 2127 byte[] bytes; 2128 try { 2129 bytes = uriref.getBytes("UTF-8"); 2130 } catch (UnsupportedEncodingException uee) { 2131 // this can't happen 2132 catalogManager.debug.message(1, "UTF-8 is an unsupported encoding!?"); 2133 return uriref; 2134 } 2135 2136 StringBuilder newRef = new StringBuilder(bytes.length); 2137 for (int count = 0; count < bytes.length; count++) { 2138 int ch = bytes[count] & 0xFF; 2139 2140 if ((ch <= 0x20) // ctrl 2141 || (ch > 0x7F) // high ascii 2142 || (ch == 0x22) // " 2143 || (ch == 0x3C) // < 2144 || (ch == 0x3E) // > 2145 || (ch == 0x5C) // \ 2146 || (ch == 0x5E) // ^ 2147 || (ch == 0x60) // ` 2148 || (ch == 0x7B) // { 2149 || (ch == 0x7C) // | 2150 || (ch == 0x7D) // } 2151 || (ch == 0x7F)) { 2152 newRef.append(encodedByte(ch)); 2153 } else { 2154 newRef.append((char) bytes[count]); 2155 } 2156 } 2157 2158 return newRef.toString(); 2159 } 2160 2161 /** 2162 * Perform %-encoding on a single byte. 2163 * 2164 * @param b The 8-bit integer that represents th byte. (Bytes are signed 2165 but encoding needs to look at the bytes unsigned.) 2166 * @return The %-encoded string for the byte in question. 2167 */ 2168 protected String encodedByte (int b) { 2169 String hex = Integer.toHexString(b).toUpperCase(); 2170 if (hex.length() < 2) { 2171 return "%0" + hex; 2172 } else { 2173 return "%" + hex; 2174 } 2175 } 2176 2177 // ----------------------------------------------------------------- 2178 2179 /** 2180 * Add to the current list of delegated catalogs. 2181 * 2182 * <p>This method always constructs the {@link #localDelegate} 2183 * vector so that it is ordered by length of partial 2184 * public identifier.</p> 2185 * 2186 * @param entry The DELEGATE catalog entry 2187 */ 2188 protected void addDelegate(CatalogEntry entry) { 2189 int pos = 0; 2190 String partial = entry.getEntryArg(0); 2191 2192 Enumeration local = localDelegate.elements(); 2193 while (local.hasMoreElements()) { 2194 CatalogEntry dpe = (CatalogEntry) local.nextElement(); 2195 String dp = dpe.getEntryArg(0); 2196 if (dp.equals(partial)) { 2197 // we already have this prefix 2198 return; 2199 } 2200 if (dp.length() > partial.length()) { 2201 pos++; 2202 } 2203 if (dp.length() < partial.length()) { 2204 break; 2205 } 2206 } 2207 2208 // now insert partial into the vector at [pos] 2209 if (localDelegate.size() == 0) { 2210 localDelegate.addElement(entry); 2211 } else { 2212 localDelegate.insertElementAt(entry, pos); 2213 } 2214 } 2215 }