Project

General

Profile

1
// XMLCatalogReader.java - Read XML Catalog files
2
// Written by Norman Walsh, nwalsh@arbortext.com
3
// NO WARRANTY! This class is in the public domain.
4

    
5
package com.arbortext.catalog;
6

    
7
import java.lang.Integer;
8
import java.util.Vector;
9
import java.util.Enumeration;
10
import java.io.IOException;
11
import java.net.URL;
12
import java.net.MalformedURLException;
13
import com.arbortext.catalog.CatalogEntry;
14
import com.arbortext.catalog.CatalogReader;
15
import com.arbortext.catalog.InvalidCatalogEntryTypeException;
16
import com.arbortext.catalog.InvalidCatalogEntryException;
17
import com.arbortext.catalog.UnknownCatalogFormatException;
18
import com.arbortext.catalog.NotXMLCatalogException;
19
import com.arbortext.catalog.NoXMLParserException;
20

    
21
import org.xml.sax.*;
22

    
23
/**
24
 * <p>Parses XML Catalog files.</p>
25
 *
26
 * <blockquote>
27
 * <em>This module, both source code and documentation, is in the
28
 * Public Domain, and comes with <strong>NO WARRANTY</strong>.</em>
29
 * </blockquote>
30
 *
31
 * <p>This class reads XML Catalog files, returning a stream
32
 * of tokens. At present, it recognizes John Cowan's
33
 * <a href="http://www.ccil.org/~cowan/XML/XCatalog.html">XML Catalogs</a>
34
 * (formerly XCatalogs). In the future, additional XML Catalog formats
35
 * may be supported.</p>
36
 *
37
 * <p>This code interrogates the following non-standard system properties:</p>
38
 *
39
 * <dl>
40
 * <dt><b>xml.catalog.debug</b></dt>
41
 * <dd><p>Sets the debug level. A value of 0 is assumed if the
42
 * property is not set or is not a number.</p></dd>
43
 * </dl>
44
 *
45
 * @see Catalog
46
 *
47
 * @author Arbortext, Inc.
48
 * @author Norman Walsh
49
 *         <a href="mailto:nwalsh@arbortext.com">nwalsh@arbortext.com</a>
50
 * @version 1.0
51
 */
52
public class XMLCatalogReader implements DocumentHandler {
53
    // These are class variables so that several methods can access them
54
    /** The filename (URL) of the catalog being read */
55
    private String catfilename = null;
56

    
57
    /**
58
     * <p>The debug level</p>
59
     *
60
     * <p>In general, higher numbers produce more information:</p>
61
     * <ul>
62
     * <li>0, no messages
63
     * <li>1, minimal messages (high-level status)
64
     * <li>2, detailed messages
65
     * </ul>
66
     */
67
    public int debug = 0;
68

    
69
    /**
70
     * Indicates that the catalog type is not XML
71
     */
72
    private static final int NOTXMLCATALOG = -1;
73

    
74
    /**
75
     * Indicates that the catalog type is unknown.
76
     */
77
    private static final int UNKNOWNCATALOG = 0;
78

    
79
    /**
80
     * Indicates that the catalog type is an XML Catalog (John Cowan's
81
     * XCatalog)
82
     */
83
    private static final int XCATALOG = 1;
84

    
85
    /**
86
     * Indicates the catalog type.
87
     */
88
    private int catalogType = NOTXMLCATALOG;
89

    
90
    /**
91
     * <p>The list of entries scanned from the catalog.</p>
92
     *
93
     * <p>The SAX Parser is event-driven, but the Catalog class expects
94
     * to iterate through the entries with
95
     * <a href="#nextToken()">nextToken()</a>. So this class builds a
96
     * vector of entries during the parse and returns them sequentially
97
     * when <a href="#nextToken()">nextToken()</a> is called.</p>
98
     *
99
     * @see Catalog
100
     */
101
    private Vector catalogEntries = new Vector();
102

    
103
    /**
104
     * An enumerator for walking through the list of catalogEntries.
105
     */
106
    private Enumeration catalogEnum = null;
107

    
108
    /**
109
     * <p>The name of the parser class to load when parsing XML Catalogs.</p>
110
     *
111
     * <p>If a parser class is provided,
112
     * subsequent attempts to parse Catalog files will begin
113
     * by attemptiing an XML parse of the catalog file using a parser
114
     * of this class.
115
     * If the XML parse fails, the "default" text parse will be done
116
     * instead.</p>
117
     */
118
    private String parserClass = null;
119

    
120
    /**
121
     * <p>Construct an XMLCatalogReader object.</p>
122
     */
123
    public XMLCatalogReader() {
124
	String property = System.getProperty("xml.catalog.debug");
125

    
126
	if (property != null) {
127
	    try {
128
		debug = Integer.parseInt(property);
129
	    } catch (NumberFormatException e) {
130
		debug = 0;
131
	    }
132
	}
133
    }
134

    
135
    /**
136
     * <p>Sets the parser class, enabling XML Catalog parsing.</p>
137
     *
138
     * <p>Sets the parser class that will be used for loading XML Catalogs.
139
     * If this method is not called, all attempts to use the
140
     * <code>XMLCatalogParser</code> will fail, throwing a
141
     * <code>NoXMLParserException</code>.</p>
142
     *
143
     * @param parser The name of a class implementing the SAX Parser
144
     * interface to be used for subsequent XML Catalog parsing.
145
     *
146
     * @see com.arbortext.catalog.NoXMLParserException
147
     */
148
    public void setParserClass(String parser) {
149
	parserClass = parser;
150
    }
151

    
152
    /**
153
     * <p>Attempt to parse an XML Catalog file.</p>
154
     *
155
     * @param fileUrl   The URL or filename of the catalog file to process
156
     * @param catParser A SAX-compliant parser to use for reading the files
157
     *
158
     * @throws SAXException Error parsing catalog file.
159
     * @throws IOException Error reading catalog file.
160
     * @throws NoXMLParserException No Parser class provided.
161
     * @throws NotXMLCatalogException The Catalog appears not to be XML.
162
     * @throws UnknownCatalogFormatException Unexpected XML catalog type.
163
     * @throws ClassNotFoundException Parser class can't be found.
164
     * @throws InstantiationException Parser class can't be instantiated.
165
     * @throws IllegalAccessException Error instantiating parser class.
166
     * @throws ClassCastException Parser class isn't a SAX Parser.
167
     */
168
    public void parseCatalog(String fileUrl)
169
	throws SAXException, IOException,
170
	       NotXMLCatalogException, NoXMLParserException,
171
	       UnknownCatalogFormatException, ClassNotFoundException,
172
	       InstantiationException, IllegalAccessException,
173
	       ClassCastException {
174
	// Create an instance of the parser
175
	if (parserClass == null) {
176
	    throw new NoXMLParserException();
177
	}
178

    
179
	Parser parser = (Parser) Class.forName(parserClass).newInstance();
180

    
181
	catfilename = fileUrl;
182
	parser.setDocumentHandler(this);
183
	parser.parse(fileUrl);
184

    
185
	if (catalogType == NOTXMLCATALOG) {
186
	    // Why doesn't the attempt to parse this file throw a
187
	    // SAX Exception???
188
	    throw new NotXMLCatalogException();
189
	}
190

    
191
	if (catalogType == UNKNOWNCATALOG) {
192
	    throw new UnknownCatalogFormatException();
193
	}
194
    }
195

    
196
    /**
197
     * <p>Get the next entry from the file</p>
198
     *
199
     * @throws IOException Error reading catalog file
200
     * @return A CatalogEntry object for the next entry in the catalog
201
     */
202
    public CatalogEntry nextEntry() throws IOException {
203
	if (catalogEnum == null) {
204
	    catalogEnum = catalogEntries.elements();
205
	}
206

    
207
	if (catalogEnum.hasMoreElements()) {
208
	    return (CatalogEntry) catalogEnum.nextElement();
209
	} else {
210
	    return null;
211
	}
212
    }
213

    
214
    // ----------------------------------------------------------------------
215

    
216
    /*
217
     * <p>Parse elements from John Cowan's XML Catalog doctype.</p>
218
     *
219
     * <p>Each recognized element is turned into an appropriate
220
     * CatalogEntry and put onto the entries vector for later
221
     * retrieval.</p>
222
     *
223
     * @param name The name of the element.
224
     * @param atts The list of attributes on the element.
225
     *
226
     * @see CatalogEntry
227
     */
228
    private void xCatalogEntry (String name, AttributeList atts) {
229
	CatalogEntry ce = null;
230

    
231
	try {
232
	    if (name.equals("Base")) {
233
		ce = new CatalogEntry(CatalogEntry.BASE,
234
				      atts.getValue("HRef"));
235
		debug(3, "Base", atts.getValue("HRef"));
236
	    }
237

    
238
	    if (name.equals("Delegate")) {
239
		ce = new CatalogEntry(CatalogEntry.DELEGATE,
240
				      CatalogReader.normalize(atts.getValue("PublicId")),
241
				      atts.getValue("HRef"));
242
		debug(3, "Delegate",
243
		      CatalogReader.normalize(atts.getValue("PublicId")),
244
		      atts.getValue("HRef"));
245
	    }
246

    
247
	    if (name.equals("Extend")) {
248
		ce = new CatalogEntry(CatalogEntry.CATALOG,
249
				      atts.getValue("HRef"));
250
		debug(3, "Extend", atts.getValue("HRef"));
251
	    }
252

    
253
	    if (name.equals("Map")) {
254
		ce = new CatalogEntry(CatalogEntry.PUBLIC,
255
				      CatalogReader.normalize(atts.getValue("PublicId")),
256
				      atts.getValue("HRef"));
257
		debug(3, "Map",
258
		      CatalogReader.normalize(atts.getValue("PublicId")),
259
		      atts.getValue("HRef"));
260
	    }
261

    
262
	    if (name.equals("Remap")) {
263
		ce = new CatalogEntry(CatalogEntry.SYSTEM,
264
				      atts.getValue("SystemId"),
265
				      atts.getValue("HRef"));
266
		debug(3, "Remap",
267
		      CatalogReader.normalize(atts.getValue("SystemId")),
268
		      atts.getValue("HRef"));
269
	    }
270

    
271
	    if (ce == null) {
272
		// This is equivalent to an invalid catalog entry type
273
		debug(1, "Invalid catalog entry type", name);
274
	    }
275
	} catch (InvalidCatalogEntryTypeException icete) {
276
	    debug(1, "Invalid catalog entry type", name);
277
	} catch (InvalidCatalogEntryException icete) {
278
	    debug(1, "Invalid catalog entry", name);
279
	}
280

    
281
	if (ce != null) {
282
	    catalogEntries.addElement(ce);
283
	}
284
    }
285

    
286
    // ----------------------------------------------------------------------
287
    // Implement the SAX DocumentHandler interface
288

    
289
    /** <p>The SAX <code>setDocumentLocator</code> method. Does nothing.</p> */
290
    public void setDocumentLocator (Locator locator) {
291
	return;
292
    }
293

    
294
    /** <p>The SAX <code>startDocument</code> method. Does nothing.</p> */
295
    public void startDocument ()
296
	throws SAXException {
297
	return;
298
    }
299

    
300
    /** <p>The SAX <code>endDocument</code> method. Does nothing.</p> */
301
    public void endDocument ()
302
	throws SAXException {
303
	return;
304
    }
305

    
306
    /**
307
     * <p>The SAX <code>startElement</code> method.</p>
308
     *
309
     * <p>This element attempts to identify the type of catalog
310
     * by looking at the name of the first element encountered.
311
     * If it recognizes the element, it sets the <code>catalogType</code>
312
     * appropriately.</p>
313
     *
314
     * <p>After the catalog type has been identified, the appropriate
315
     * entry parser is called for each subsequent element in the
316
     * catalog.</p>
317
     *
318
     * @param name The name of the element.
319
     * @param atts The list of attributes on the element.
320
     *
321
     */
322
    public void startElement (String name, AttributeList atts)
323
	throws SAXException {
324

    
325
	if (catalogType == UNKNOWNCATALOG || catalogType == NOTXMLCATALOG) {
326
	    if (name.equals("XMLCatalog")) {
327
		catalogType = XCATALOG;
328
		return;
329
	    }
330
	}
331

    
332
	if (catalogType == XCATALOG) {
333
	    xCatalogEntry(name, atts);
334
	}
335
    }
336

    
337
    /** <p>The SAX <code>endElement</code> method. Does nothing.</p> */
338
    public void endElement (String name)
339
	throws SAXException {
340
	return;
341
    }
342

    
343
    /** <p>The SAX <code>characters</code> method. Does nothing.</p> */
344
    public void characters (char ch[], int start, int length)
345
	throws SAXException {
346
	return;
347
    }
348

    
349
    /** <p>The SAX <code>ignorableWhitespace</code> method. Does nothing.</p> */
350
    public void ignorableWhitespace (char ch[], int start, int length)
351
	throws SAXException {
352
	return;
353
    }
354

    
355
    /** <p>The SAX <code>processingInstruction</code> method. Does nothing.</p> */
356
    public void processingInstruction (String target, String data)
357
	throws SAXException {
358
	return;
359
    }
360

    
361
    // -----------------------------------------------------------------
362

    
363
    /**
364
     * <p>Print debug message (if the debug level is high enough).</p>
365
     *
366
     * @param level The debug level of this message. This message
367
     * will only be
368
     * displayed if the current debug level is at least equal to this
369
     * value.
370
     * @param message The text of the message.
371
     * @param token The catalog file token being processed.
372
     */
373
    private void debug(int level, String message, String token) {
374
	if (debug >= level) {
375
	    System.out.println(message + ": " + token);
376
	}
377
    }
378

    
379
    /**
380
     * <p>Print debug message (if the debug level is high enough).</p>
381
     *
382
     * @param level The debug level of this message. This message
383
     * will only be
384
     * displayed if the current debug level is at least equal to this
385
     * value.
386
     * @param message The text of the message.
387
     * @param token The catalog file token being processed.
388
     * @param spec The argument to the token.
389
     */
390
    private void debug(int level, String message, String token, String spec) {
391
	if (debug >= level) {
392
	    System.out.println(message + ": " + token + " " + spec);
393
	}
394
    }
395

    
396
    /**
397
     * <p>Print debug message (if the debug level is high enough).</p>
398
     *
399
     * @param level The debug level of this message. This message
400
     * will only be
401
     * displayed if the current debug level is at least equal to this
402
     * value.
403
     * @param message The text of the message.
404
     * @param token The catalog file token being processed.
405
     * @param spec1 The first argument to the token.
406
     * @param spec2 The second argument to the token.
407
     */
408
    private void debug(int level, String message,
409
		       String token, String spec1, String spec2) {
410
	if (debug >= level) {
411
	    System.out.println(message + ": " + token + " " + spec1);
412
	    System.out.println("\t" + spec2);
413
	}
414
    }
415
}
(10-10/11)