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