Although we now have a successful
demonstration of SAX parsing, there is a glaring problem with our
code. Let’s take a look again at how we obtain an instance of
XMLReader
:
try { // Instantiate a parser XMLReader parser = new SAXParser( ); // Register the content handler parser.setContentHandler(contentHandler); // Register the error handler parser.setErrorHandler(errorHandler); // Parse the document parser.parse(uri); } catch (IOException e) { System.out.println("Error reading URI: " + e.getMessage( )); } catch (SAXException e) { System.out.println("Error in parsing: " + e.getMessage( )); }
Do you see anything that rubs you wrong? Let’s look at another line of our code that may give you a hint:
// Import your vendor's XMLReader implementation here import org.apache.xerces.parsers.SAXParser;
We have to explicitly import our vendor’s
XMLReader
implementation, and then instantiate
that implementation directly. The problem here is not the difficulty
of this task, but that we have broken one of Java’s biggest
tenets: portability. Our code cannot run or even be compiled on a
platform that does not use the Apache Xerces parser. In fact, it is
conceivable that an updated version of Xerces might even change the
name of the class used here! Our “portable” Java code is
no longer very portable.
What is preferred is to request an instance of a class by the name of
the implementation class. This allows a simple
String
parameter to be changed in your source
code. Luckily, this facility is available in SAX 2.0. The
org.xml.sax.helpers.XMLReaderFactory
class
provides the method you should be looking for:
/** * Attempt to create an XML reader from a class name. * * <p>Given a class name, this method attempts to load * and instantiate the class as an XML reader.</p> * * @return A new XML reader. * @exception org.xml.sax.SAXException If the class cannot be * loaded, instantiated, and cast to XMLReader. * @see #createXMLReader( ) */ public static XMLReader createXMLReader (String className) throws SAXException { // Implementation }
We can use this method in our code like this:
try { // Instantiate a parserXMLReader parser =
XMLReaderFactory.createXMLReader(
"org.apache.xerces.parsers.SAXParser");
// Register the content handler parser.setContentHandler(contentHandler); // Register the error handler parser.setErrorHandler(errorHandler); // Parse the document parser.parse(uri); } catch (IOException e) { System.out.println("Error reading URI: " + e.getMessage( )); } catch (SAXException e) { System.out.println("Error in parsing: " + e.getMessage( )); }
This static method takes in the name of the parser class to load and
returns an instantiated version of the class, cast to the
XMLReader
interface (assuming that it actually
does implement XMLReader
). If any problems occur,
they are all handled and then wrapped in a
SAXException
that is thrown to the calling
program. Add in the additional import statement, remove the
vendor-specific parser reference, make the changes noted above, and
you should be able to recompile your source file:
import java.io.IOException; import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.ErrorHandler; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.XMLReader;import org.xml.sax.helpers.XMLReaderFactory;
// This goes away
// import org.apache.xerces.parsers.SAXParser;
Suddenly you are writing portable code again! To further portability, it makes sense to store the name of the parser class in a properties file. This allows easy loading of the class at runtime, and it means your code can be moved from platform to platform without recompilation based on the parser being used; instead, only the properties file could be changed. Although the code to read properties files is not provided here, take a look at some code that performs this behavior:
try { // Instantiate a parserXMLReader parser =
XMLReaderFactory.createXMLReader(
PropertiesReader( ).getInstance( )
.getProperty("parserClass"));
// Register the content handler parser.setContentHandler(contentHandler); // Register the error handler parser.setErrorHandler(errorHandler); // Parse the document parser.parse(uri); } catch (IOException e) { System.out.println("Error reading URI: " + e.getMessage( )); } catch (SAXException e) { System.out.println("Error in parsing: " + e.getMessage( )); }
Here a utility class, PropertiesReader
, is being
used to read a properties file and return the value for the key
“parserClass”, which would contain the parser class name
to use on the specific platform the code was being used for. In our
examples, this would be our old friend
org.apache.xerces.parsers.SAXParser
. Of course,
Java system properties could also be used, but they are not as
appropriate for web-centric distributed applications like the ones we
focus on in this book, as they must be specified on a command line.
Often, distributed applications are started up in whole, rather than
individually, making specification of a system property to one
particular component difficult.
3.144.20.1