001: /*
002: * The Apache Software License, Version 1.1
003: *
004: *
005: * Copyright (c) 1999,2000 The Apache Software Foundation. All rights
006: * reserved.
007: *
008: * Redistribution and use in source and binary forms, with or without
009: * modification, are permitted provided that the following conditions
010: * are met:
011: *
012: * 1. Redistributions of source code must retain the above copyright
013: * notice, this list of conditions and the following disclaimer.
014: *
015: * 2. Redistributions in binary form must reproduce the above copyright
016: * notice, this list of conditions and the following disclaimer in
017: * the documentation and/or other materials provided with the
018: * distribution.
019: *
020: * 3. The end-user documentation included with the redistribution,
021: * if any, must include the following acknowledgment:
022: * "This product includes software developed by the
023: * Apache Software Foundation (http://www.apache.org/)."
024: * Alternately, this acknowledgment may appear in the software itself,
025: * if and wherever such third-party acknowledgments normally appear.
026: *
027: * 4. The names "Xerces" and "Apache Software Foundation" must
028: * not be used to endorse or promote products derived from this
029: * software without prior written permission. For written
030: * permission, please contact apache@apache.org.
031: *
032: * 5. Products derived from this software may not be called "Apache",
033: * nor may "Apache" appear in their name, without prior written
034: * permission of the Apache Software Foundation.
035: *
036: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
037: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
038: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
039: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
040: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
041: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
042: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
043: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
044: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
045: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
046: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
047: * SUCH DAMAGE.
048: * ====================================================================
049: *
050: * This software consists of voluntary contributions made by many
051: * individuals on behalf of the Apache Software Foundation and was
052: * originally based on software copyright (c) 1999, International
053: * Business Machines, Inc., http://www.apache.org. For more
054: * information on the Apache Software Foundation, please see
055: * <http://www.apache.org/>.
056: */
057:
058: package org.apache.xerces.readers;
059:
060: import org.apache.xerces.framework.XMLParser;
061: import org.apache.xerces.parsers.SAXParser;
062: import org.apache.xerces.utils.StringPool;
063:
064: import org.xml.sax.AttributeList;
065: import org.xml.sax.DocumentHandler;
066: import org.xml.sax.EntityResolver;
067: import org.xml.sax.InputSource;
068: import org.xml.sax.SAXException;
069:
070: import java.io.InputStream;
071: import java.io.InputStreamReader;
072: import java.io.IOException;
073: import java.util.Enumeration;
074: import java.util.Hashtable;
075: import java.util.Vector;
076:
077: /**
078: * This catalog supports the XCatalog proposal draft 0.2 posted
079: * to the xml-dev mailing list by
080: * <a href="mailto:cowan@locke.ccil.org">John Cowan</a>. XCatalog
081: * is an XML representation of the SGML Open TR9401:1997 catalog
082: * format. The current proposal supports public identifier maps,
083: * system identifier aliases, and public identifier prefix
084: * delegates. Refer to the XCatalog DTD for the full specification
085: * of this catalog format at
086: * <a href="http://www.ccil.org/~cowan/XML/XCatalog.html">http://www.ccil.org/~cowan/XML/XCatalog.html</a>.
087: * <p>
088: * In order to use XCatalogs, you must write the catalog files
089: * with the following restrictions:
090: * <ul>
091: * <li>You must follow the XCatalog grammar.
092: * <li>You must specify the <tt><!DOCTYPE></tt> line with
093: * the <tt>PUBLIC</tt> specified as "-//DTD XCatalog//EN" or
094: * make sure that the system identifier is able to locate the
095: * XCatalog 0.2 DTD (which is included in the Jar file containing
096: * the org.apache.xerces.readers.XCatalog class).
097: * For example:
098: * <pre>
099: * <!DOCTYPE XCatalog PUBLIC "-//DTD XCatalog//EN" "org/apache/xerces/readers/xcatalog.dtd">
100: * </pre>
101: * <li>The enclosing <tt><XCatalog></tt> document root
102: * element is <b>not</b> optional -- it <b>must</b> be specified.
103: * <li>The <tt>Version</tt> attribute of the <tt><XCatalog></tt>
104: * has been modified from '<tt><i>#FIXED "1.0"</i></tt>' to
105: * '<tt><i>(0.1|0.2) "0.2"</i></tt>'.
106: * </ul>
107: * <p>
108: * To use this catalog in a parser, set an XCatalog instance as the
109: * parser's <tt>EntityResolver</tt>. For example:
110: * <pre>
111: * XMLParser parser = new AnyParser();
112: * Catalog catalog = <font color="blue">new XCatalog()</font>;
113: * <font color="blue">parser.getEntityHandler().setEntityResolver(catalog);</font>
114: * </pre>
115: * <p>
116: * Once installed, catalog files that conform to the XCatalog grammar
117: * can be appended to the catalog by calling the <tt>loadCatalog</tt>
118: * method on the parser or the catalog instance. The following example
119: * loads the contents of two catalog files:
120: * <pre>
121: * parser.loadCatalog(new InputSource("catalogs/cat1.xml"));
122: * parser.loadCatalog(new InputSource("http://host/catalogs/cat2.xml"));
123: * </pre>
124: * <p>
125: * <b>Limitations:</b> The following are the current limitations
126: * of this XCatalog implementation:
127: * <ul>
128: * <li>No error checking is done to avoid circular <tt>Delegate</tt>
129: * or <tt>Extend</tt> references. Do not specify a combination of
130: * catalog files that reference each other.
131: * </ul>
132: *
133: * @author Andy Clark, IBM
134: * @version $Id: XCatalog.java,v 1.2 2000/04/04 21:14:25 andyc Exp $
135: */
136: public class XCatalog extends XMLCatalogHandler {
137:
138: //
139: // Constants
140: //
141:
142: // public
143:
144: /** XCatalog public identifier string ("-//DTD XCatalog//EN"). */
145: public static final String XCATALOG_DTD_PUBLICID = "-//DTD XCatalog//EN";
146:
147: // "default"
148:
149: /** XCatalog DTD resource name ("xcatalog.dtd"). */
150: static final String DTD = "xcatalog.dtd";
151:
152: /** XCatalog element name ("XCatalog"). */
153: static final String XCATALOG = "XCatalog";
154:
155: /** Map element name ("Map"). */
156: static final String MAP = "Map";
157:
158: /** PublicID attribute name ("PublicID"). */
159: static final String PUBLICID = "PublicID";
160:
161: /** HRef attribute name ("HRef"). */
162: static final String HREF = "HRef";
163:
164: /** Delegate element name ("Delegate"). */
165: static final String DELEGATE = "Delegate";
166:
167: /** Extend element name ("Extend"). */
168: static final String EXTEND = "Extend";
169:
170: /** Base element name ("Base"). */
171: static final String BASE = "Base";
172:
173: /** Remap element name ("Remap"). */
174: static final String REMAP = "Remap";
175:
176: /** SystemID attribute name ("SystemID"). */
177: static final String SYSTEMID = "SystemID";
178:
179: // private
180:
181: /** Set to true and recompile to include debugging code in class. */
182: private static final boolean DEBUG = false;
183:
184: //
185: // Data
186: //
187:
188: /** Delegates. */
189: private Hashtable delegate = new Hashtable();
190:
191: /** Delegates ordering. */
192: private Vector delegateOrder = new Vector();
193:
194: //
195: // Constructors
196: //
197:
198: /**
199: * Constructs an XCatalog instance.
200: */
201: public XCatalog() {
202: }
203:
204: //
205: // Catalog methods
206: //
207:
208: /**
209: * Loads the catalog stream specified by the given input source and
210: * appends the contents to the catalog.
211: *
212: * @param source The catalog source.
213: *
214: * @exception org.xml.sax.SAXException Throws exception on SAX error.
215: * @exception java.io.IOException Throws exception on i/o error.
216: */
217: public void loadCatalog(InputSource source) throws SAXException,
218: IOException {
219:
220: new Parser(source);
221:
222: /***
223: if (DEBUG) {
224: print("");
225: }
226: /***/
227:
228: } // loadCatalog(InputSource)
229:
230: /***
231: void print(String indent) {
232: System.out.println(indent+"# "+this);
233: Enumeration maps = getMapKeys();
234: while (maps.hasMoreElements()) {
235: String key = (String)maps.nextElement();
236: String value = getMapValue(key);
237: System.out.println(indent+"MAP \""+key+"\" -> \""+value+"\"");
238: }
239: Enumeration delegates = getDelegateKeys();
240: while (delegates.hasMoreElements()) {
241: String key = (String)delegates.nextElement();
242: XCatalog value = getDelegateValue(key);
243: System.out.println(indent+"DELEGATE \""+key+"\" -> "+value);
244: value.print(indent+" ");
245: }
246: Enumeration remaps = getRemapKeys();
247: while (remaps.hasMoreElements()) {
248: String key = (String)remaps.nextElement();
249: String value = getRemapValue(key);
250: System.out.println(indent+"REMAP \""+key+"\" -> \""+value+"\"");
251: }
252: }
253: /***/
254:
255: //
256: // EntityResolver methods
257: //
258: /**
259: * Resolves external entities.
260: *
261: * @param publicId The public identifier used for entity resolution.
262: * @param systemId If the publicId is not null, this systemId is
263: * to be considered the default system identifier;
264: * else a system identifier alias mapping is
265: * requested.
266: *
267: * @return Returns the input source of the resolved entity or null
268: * if no resolution is possible.
269: *
270: * @exception org.xml.sax.SAXException Exception thrown on SAX error.
271: * @exception java.io.IOException Exception thrown on i/o error.
272: */
273: public InputSource resolveEntity(String publicId, String systemId)
274: throws SAXException, IOException {
275:
276: if (DEBUG) {
277: System.out.println("resolveEntity(\"" + publicId + "\", \""
278: + systemId + "\")");
279: }
280:
281: // public identifier resolution
282: if (publicId != null) {
283: // direct public id mappings
284: String value = getPublicMapping(publicId);
285: if (DEBUG) {
286: System.out.println(" map: \"" + publicId + "\" -> \""
287: + value + "\"");
288: }
289: if (value != null) {
290: InputSource source = resolveEntity(null, value);
291: if (source == null) {
292: source = new InputSource(value);
293: }
294: source.setPublicId(publicId);
295: return source;
296: }
297:
298: // delegates
299: Enumeration delegates = getDelegateCatalogKeys();
300: while (delegates.hasMoreElements()) {
301: String key = (String) delegates.nextElement();
302: if (DEBUG) {
303: System.out.println(" delegate: \"" + key + "\"");
304: }
305: if (publicId.startsWith(key)) {
306: XMLCatalogHandler catalog = getDelegateCatalog(key);
307: InputSource source = catalog.resolveEntity(
308: publicId, systemId);
309: if (source != null) {
310: return source;
311: }
312: }
313: }
314: }
315:
316: // system identifier resolution
317: String value = getSystemMapping(systemId);
318: if (value != null) {
319: if (DEBUG) {
320: System.out.println(" remap: \"" + systemId
321: + "\" -> \"" + value + "\"");
322: }
323: InputSource source = new InputSource(value);
324: source.setPublicId(publicId);
325: return source;
326: }
327:
328: // use default behavior
329: if (DEBUG) {
330: System.out.println(" returning null!");
331: }
332: return null;
333:
334: } // resolveEntity(String,String):InputSource
335:
336: //
337: // Public methods
338: //
339:
340: /**
341: * Adds a delegate mapping. If the prefix of a public identifier
342: * matches a delegate prefix, then the delegate catalog is
343: * searched in order to resolve the identifier.
344: * <p>
345: * This method makes sure that prefixes that match each other
346: * are inserted into the delegate list in order of longest prefix
347: * length first.
348: *
349: * @param prefix The delegate prefix.
350: * @param catalog The delegate catalog.
351: */
352: public void addDelegateCatalog(String prefix, XCatalog catalog) {
353:
354: synchronized (delegate) {
355: // insert prefix in proper order
356: if (!delegate.containsKey(prefix)) {
357: int size = delegateOrder.size();
358: boolean found = false;
359: for (int i = 0; i < size; i++) {
360: String element = (String) delegateOrder
361: .elementAt(i);
362: if (prefix.startsWith(element)
363: || prefix.compareTo(element) < 0) {
364: delegateOrder.insertElementAt(prefix, i);
365: found = true;
366: break;
367: }
368: }
369: if (!found) {
370: delegateOrder.addElement(prefix);
371: }
372: }
373:
374: // replace (or add new) prefix mapping
375: delegate.put(prefix, catalog);
376: }
377:
378: } // addDelegateCatalog(String,XCatalog)
379:
380: /**
381: * Removes a delegate.
382: *
383: * @param prefix The delegate prefix to remove.
384: */
385: public void removeDelegateCatalog(String prefix) {
386:
387: synchronized (delegate) {
388: delegate.remove(prefix);
389: delegateOrder.removeElement(prefix);
390: }
391:
392: } // removeDelegateCatalog(String)
393:
394: /** Returns an enumeration of delegate prefixes. */
395: public Enumeration getDelegateCatalogKeys() {
396: return delegateOrder.elements();
397: }
398:
399: /** Returns the catalog for the given delegate prefix. */
400: public XCatalog getDelegateCatalog(String prefix) {
401: return (XCatalog) delegate.get(prefix);
402: }
403:
404: //
405: // "default" methods
406: //
407:
408: /** Returns true if the string is a valid URL. */
409: boolean isURL(String str) {
410: try {
411: new java.net.URL(str);
412: return true;
413: } catch (java.net.MalformedURLException e) {
414: // assume the worst
415: }
416: return false;
417: }
418:
419: //
420: // Classes
421: //
422:
423: /** Parser for XCatalog document instances. */
424: class Parser extends SAXParser implements DocumentHandler {
425:
426: //
427: // Data
428: //
429:
430: /** The base. */
431: private String base;
432:
433: //
434: // Constructors
435: //
436:
437: /** Parses the specified input source. */
438: public Parser(InputSource source) throws SAXException,
439: IOException {
440:
441: // setup parser
442: setEntityResolver(new Resolver());
443: setDocumentHandler((DocumentHandler) this );
444:
445: // set base and parse
446: setBase(source.getSystemId());
447: parse(source);
448:
449: } // <init>(InputSource)
450:
451: //
452: // Protected methods
453: //
454:
455: /**
456: * Sets the base from the given system identifier. The base is
457: * the same as the system identifier with the least significant
458: * part (the filename) removed.
459: */
460: protected void setBase(String systemId) throws SAXException {
461:
462: // normalize system id
463: if (systemId == null) {
464: systemId = "";
465: }
466:
467: // expand system id
468: systemId = fEntityHandler.expandSystemId(systemId);
469:
470: // cut off the least significant part
471: int index = systemId.lastIndexOf('/');
472: if (index != -1) {
473: systemId = systemId.substring(0, index + 1);
474: }
475:
476: // save base
477: base = systemId;
478:
479: } // setBase(String)
480:
481: //
482: // DocumentHandler methods
483: //
484:
485: /** Not implemented. */
486: public void processingInstruction(String target, String data) {
487: }
488:
489: /** Not implemented. */
490: public void setDocumentLocator(org.xml.sax.Locator locator) {
491: }
492:
493: /** Not implemented. */
494: public void startDocument() {
495: }
496:
497: /** Not implemented. */
498: public void endElement(String elementName) {
499: }
500:
501: /** Not implemented. */
502: public void endDocument() {
503: }
504:
505: /** Not implemented. */
506: public void characters(char ch[], int start, int length) {
507: }
508:
509: /** Not implemented. */
510: public void ignorableWhitespace(char ch[], int start, int length) {
511: }
512:
513: /** The start of an element. */
514: public void startElement(String elementName,
515: AttributeList attrList) throws SAXException {
516:
517: try {
518: // <XCatalog Version="...">
519: if (elementName.equals(XCATALOG)) {
520: return;
521: }
522:
523: // <Map PublicID="..." HRef="..."/>
524: if (elementName.equals(MAP)) {
525: // get attributes
526: String publicId = attrList.getValue(PUBLICID);
527: String href = attrList.getValue(HREF);
528: if (DEBUG) {
529: System.out.println("MAP \"" + publicId
530: + "\" \"" + href + "\"");
531: }
532:
533: // create mapping
534: if (!isURL(href)) {
535: href = base + href;
536: }
537: addPublicMapping(publicId, href);
538: }
539:
540: // <Delegate PublicId="..." HRef="..."/>
541: else if (elementName.equals(DELEGATE)) {
542: // get attributes
543: String publicId = attrList.getValue(PUBLICID);
544: String href = attrList.getValue(HREF);
545: if (DEBUG) {
546: System.out.println("DELEGATE \"" + publicId
547: + "\" \"" + href + "\"");
548: }
549:
550: // expand system id
551: if (!isURL(href)) {
552: href = base + href;
553: }
554: String systemId = fEntityHandler
555: .expandSystemId(href);
556:
557: // create delegate
558: XCatalog catalog = new XCatalog();
559: catalog.loadCatalog(new InputSource(systemId));
560: addDelegateCatalog(publicId, catalog);
561: }
562:
563: // <Extend HRef="..."/>
564: else if (elementName.equals(EXTEND)) {
565: // get attributes
566: String href = attrList.getValue(HREF);
567: if (DEBUG) {
568: System.out.println("EXTEND \"" + href + "\"");
569: }
570:
571: // expand system id
572: if (!isURL(href)) {
573: href = base + href;
574: }
575: String systemId = fEntityHandler
576: .expandSystemId(href);
577:
578: // create catalog
579: XCatalog.this
580: .loadCatalog(new InputSource(systemId));
581: }
582:
583: // <Base HRef="..."/>
584: else if (elementName.equals(BASE)) {
585: // get attributes
586: String href = attrList.getValue(HREF);
587:
588: // set new base
589: setBase(href);
590: if (DEBUG) {
591: System.out.println("BASE \"" + href
592: + "\" -> \"" + base + "\"");
593: }
594: }
595:
596: // <Remap SystemID="..." HRef="..."/>
597: else if (elementName.equals(REMAP)) {
598: // get attributes
599: String systemId = attrList.getValue(SYSTEMID);
600: String href = attrList.getValue(HREF);
601: if (DEBUG) {
602: System.out.println("REMAP \"" + systemId
603: + "\" \"" + href + "\"");
604: }
605:
606: // create mapping
607: if (!isURL(href)) {
608: href = base + href;
609: }
610: addSystemMapping(systemId, href);
611: }
612: } catch (Exception e) {
613: throw new SAXException(e);
614: }
615:
616: } // startElement(String,AttributeList)
617:
618: //
619: // Classes
620: //
621:
622: /** Resolver for locating the XCatalog DTD resource. */
623: class Resolver implements EntityResolver {
624:
625: /** Resolves the XCatalog DTD entity. */
626: public InputSource resolveEntity(String publicId,
627: String systemId) throws SAXException, IOException {
628:
629: // resolve the XCatalog DTD?
630: if (publicId != null
631: && publicId.equals(XCATALOG_DTD_PUBLICID)) {
632: InputSource src = new InputSource();
633: src.setPublicId(publicId);
634: InputStream is = getClass()
635: .getResourceAsStream(DTD);
636: src.setByteStream(is);
637: src.setCharacterStream(new InputStreamReader(is));
638: return src;
639: }
640:
641: // no resolution possible
642: return null;
643:
644: } // resolveEntity(String,String):InputSource
645:
646: } // class Resolver
647:
648: } // class Parser
649:
650: } // class XCatalog
|