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