package org.jdom.input; import java.io.*; import java.lang.reflect.*; import java.net.*; import java.util.*; import org.jdom.*; import org.xml.sax.*; import org.xml.sax.ext.LexicalHandler; import org.xml.sax.helpers.DefaultHandler; import org.xml.sax.helpers.XMLReaderFactory; /** *

SAXBuilder builds a JDOM tree using SAX.

* *

Known issues: Relative paths for a DocType or EntityRef may be * converted by the SAX parser into absolute paths

* * @author Brett McLaughlin * @author Jason Hunter * @author Dan Schaffer * @author Philip Nelson * @version 1.0 */ public class SAXBuilder { private static final String CVS_ID = "@(#) $RCSfile: SAXBuilder.java,v $ $Revision: 1.46 $ $Date: 2001/05/09 07:23:24 $ $Name: $"; /** * Default parser class to use. This is used when no other parser * is given and JAXP isn't available. */ private static final String DEFAULT_SAX_DRIVER = "org.apache.xerces.parsers.SAXParser"; /** Whether validation should occur */ private boolean validate; /** Whether expansion of entities should occur */ private boolean expand = true; /** Adapter class to use */ private String saxDriverClass; /** ErrorHandler class to use */ private ErrorHandler saxErrorHandler = null; /** EntityResolver class to use */ private EntityResolver saxEntityResolver = null; /** DTDHandler class to use */ private DTDHandler saxDTDHandler = null; /** XMLFilter instance to use */ private XMLFilter saxXMLFilter = null; /** The factory for creating new JDOM objects */ private JDOMFactory factory = new DefaultJDOMFactory(); /** *

* Creates a new SAXBuilder which will attempt to first locate * a parser via JAXP, then will try to use a set of default * SAX Drivers. The underlying parser will not validate. *

*/ public SAXBuilder() { this(false); } /** *

* Creates a new SAXBuilder using the specified SAX parser. * The underlying parser will not validate. *

* * @param saxDriverClass String name of SAX Driver * to use for parsing. */ public SAXBuilder(String saxDriverClass) { this(saxDriverClass, false); } /** *

* Creates a new SAXBuilder using the specified SAX parser. * The underlying parser will validate or not * according to the given parameter. *

* * @param saxDriverClass String name of SAX Driver * to use for parsing. * @param validate boolean indicating if * validation should occur. */ public SAXBuilder(String saxDriverClass, boolean validate) { this.saxDriverClass = saxDriverClass; this.validate = validate; } /** *

* Creates a new SAXBuilder which will attempt to first locate * a parser via JAXP, then will try to use a set of default * SAX Drivers. The underlying parser will validate or not * according to the given parameter. *

* * @param validate boolean indicating if * validation should occur. */ public SAXBuilder(boolean validate) { this.validate = validate; } /** *

* This builds a document from the supplied * filename. *

* * @param file File to read from. * @return Document - resultant Document object. * @throws JDOMException when errors occur in parsing. */ public Document build(File file) throws JDOMException { try { URL url = fileToURL(file); return build(url); } catch (MalformedURLException e) { throw new JDOMException("Error in building", e); } } /** *

* This builds a document from the supplied * input stream. *

* * @param in InputStream to read from. * @return Document - resultant Document object. * @throws JDOMException when errors occur in parsing. */ public Document build(InputStream in) throws JDOMException { return build(new InputSource(in)); } /** *

* This builds a document from the supplied * input stream. *

* * @param in InputStream to read from. * @param systemId base for resolving relative URIs * @return Document - resultant Document object. * @throws JDOMException when errors occur in parsing. */ public Document build(InputStream in, String systemId) throws JDOMException { InputSource src = new InputSource(in); src.setSystemId(systemId); return build(src); } /** *

* This builds a document from the supplied * Reader. *

* * @param in Reader to read from. * @return Document - resultant Document object. * @throws JDOMException when errors occur in parsing. */ public Document build(Reader characterStream) throws JDOMException { return build(new InputSource(characterStream)); } /** *

* This builds a document from the supplied * Reader. *

* * @param in Reader to read from. * @param systemId base for resolving relative URIs * @return Document - resultant Document object. * @throws JDOMException when errors occur in parsing. */ public Document build(Reader characterStream, String SystemId) throws JDOMException { InputSource src = new InputSource(characterStream); src.setSystemId(SystemId); return build(src); } /** *

* This builds a document from the supplied * URI. *

* @param systemId URI for the input * @return Document - resultant Document object. * @throws JDOMException when errors occur in parsing. */ public Document build(String systemId) throws JDOMException { return build(new InputSource(systemId)); } /** *

* This builds a document from the supplied * URL. *

* * @param url URL to read from. * @return Document - resultant Document object. * @throws JDOMException when errors occur in parsing. */ public Document build(URL url) throws JDOMException { String systemID = url.toExternalForm(); return build(new InputSource(systemID)); } /** *

* This builds a document from the supplied * input source. *

* * @param in InputSource to read from. * @return Document - resultant Document object. * @throws JDOMException when errors occur in parsing. */ protected Document build(InputSource in) throws JDOMException { Document doc = factory.document((Element)null); try { XMLReader parser = null; if (saxDriverClass != null) { // The user knows that they want to use a particular class parser = XMLReaderFactory.createXMLReader(saxDriverClass); // System.out.println("using specific " + saxDriverClass); } else { // Try using JAXP... // Note we need JAXP 1.1, and if JAXP 1.0 is all that's // available then the getXMLReader call fails and we skip // to the hard coded default parser try { Class factoryClass = Class.forName("javax.xml.parsers.SAXParserFactory"); // factory = SAXParserFactory.newInstance(); Method newParserInstance = factoryClass.getMethod("newInstance", null); Object factory = newParserInstance.invoke(null, null); // factory.setValidating(validate); Method setValidating = factoryClass.getMethod("setValidating", new Class[]{boolean.class}); setValidating.invoke(factory, new Object[]{new Boolean(validate)}); // jaxpParser = factory.newSAXParser(); Method newSAXParser = factoryClass.getMethod("newSAXParser", null); Object jaxpParser = newSAXParser.invoke(factory, null); // parser = jaxpParser.getXMLReader(); Class parserClass = jaxpParser.getClass(); Method getXMLReader = parserClass.getMethod("getXMLReader", null); parser = (XMLReader)getXMLReader.invoke(jaxpParser, null); saxDriverClass = parser.getClass().getName(); // System.out.println("Using jaxp " + // parser.getClass().getName()); } catch (ClassNotFoundException e) { //e.printStackTrace(); } catch (InvocationTargetException e) { //e.printStackTrace(); } catch (NoSuchMethodException e) { //e.printStackTrace(); } } // Check to see if we got a parser yet, if not, try to use a // hard coded default if (parser == null) { parser = XMLReaderFactory.createXMLReader(DEFAULT_SAX_DRIVER); // System.out.println("using default " + DEFAULT_SAX_DRIVER); saxDriverClass = parser.getClass().getName(); } // Install optional filter if (saxXMLFilter != null) { // Connect filter chain to parser XMLFilter root = saxXMLFilter; while (root.getParent() instanceof XMLFilter) { root = (XMLFilter)root.getParent(); } root.setParent(parser); // Read from filter parser = saxXMLFilter; } SAXHandler contentHandler = new SAXHandler(doc,factory); contentHandler.setExpandEntities(expand); // pass thru behavior parser.setContentHandler(contentHandler); if (saxEntityResolver != null) { parser.setEntityResolver(saxEntityResolver); } if (saxDTDHandler != null) { parser.setDTDHandler(saxDTDHandler); } boolean lexicalReporting = false; try { parser.setProperty("http://xml.org/sax/handlers/LexicalHandler", contentHandler); lexicalReporting = true; } catch (SAXNotSupportedException e) { // No lexical reporting available } catch (SAXNotRecognizedException e) { // No lexical reporting available } // Some parsers use alternate property for lexical handling (grr...) if (!lexicalReporting) { try { parser.setProperty( "http://xml.org/sax/properties/lexical-handler", contentHandler); lexicalReporting = true; } catch (SAXNotSupportedException e) { // No lexical reporting available } catch (SAXNotRecognizedException e) { // No lexical reporting available } } // Try setting the DeclHandler if entity expansion is off if (!expand) { try { parser.setProperty( "http://xml.org/sax/properties/declaration-handler", contentHandler); } catch (SAXNotSupportedException e) { // No lexical reporting available } catch (SAXNotRecognizedException e) { // No lexical reporting available } } // Set validation try { parser.setFeature( "http://xml.org/sax/features/validation", validate); parser.setFeature( "http://xml.org/sax/features/namespaces", true); parser.setFeature( "http://xml.org/sax/features/namespace-prefixes", false); if (saxErrorHandler != null) { parser.setErrorHandler(saxErrorHandler); } else { parser.setErrorHandler(new BuilderErrorHandler()); } } catch (SAXNotRecognizedException e) { // No validation available if (validate) { throw new JDOMException( "Validation feature not recognized by " + saxDriverClass); } } catch (SAXNotSupportedException e) { // No validation available if (validate) { throw new JDOMException( "Validation not supported by " + saxDriverClass); } } /* // Set entity expansion try { //Crimson doesn't support this feature but does report it // as true so... if (parser.getFeature("http://xml.org/sax/features/external-general-entities") != expand) { parser.setFeature("http://xml.org/sax/features/external-general-entities", expand); } } catch (SAXNotRecognizedException e) { // No entity expansion available throw new JDOMException( "Entity expansion feature not recognized by " + saxDriverClass); } catch (SAXNotSupportedException e) { // No entity expansion available throw new JDOMException( "Entity expansion feature not supported by " + saxDriverClass); } */ parser.parse(in); return doc; } catch (Exception e) { if (e instanceof SAXParseException) { SAXParseException p = (SAXParseException)e; String systemId = p.getSystemId(); if (systemId != null) { throw new JDOMException("Error on line " + p.getLineNumber() + " of document " + systemId, e); } else { throw new JDOMException("Error on line " + p.getLineNumber(), e); } } else { throw new JDOMException("Error in building", e); } } } /** * Imitation of File.toURL(), a JDK 1.2 method, reimplemented * here to work with JDK 1.1. * * @see java.io.File * * @param f the file to convert * @return the file path converted to a file: URL */ protected URL fileToURL(File f) throws MalformedURLException { String path = f.getAbsolutePath(); if (File.separatorChar != '/') { path = path.replace(File.separatorChar, '/'); } if (!path.startsWith("/")) { path = "/" + path; } if (!path.endsWith("/") && f.isDirectory()) { path = path + "/"; } return new URL("file", "", path); } /** *

* This sets custom DTDHandler for the Builder. *

* * @param dtdHandler DTDHandler */ public void setDTDHandler(DTDHandler dtdHandler) { saxDTDHandler = dtdHandler; } /** *

* This sets custom EntityResolver for the Builder. *

* * @param entityResolver EntityResolver */ public void setEntityResolver(EntityResolver entityResolver) { saxEntityResolver = entityResolver; } /** *

* This sets custom ErrorHandler for the Builder. *

* * @param errorHandler ErrorHandler */ public void setErrorHandler(ErrorHandler errorHandler) { saxErrorHandler = errorHandler; } /** *

* This sets whether or not to expand entities for the builder. * A true means to expand entities as normal content. A false means to * leave entities unexpanded as EntityRef objects. The * default is true. *

* * @param expand boolean indicating whether entity expansion * should occur. */ public void setExpandEntities(boolean expand) { this.expand = expand; } /** *

* This sets validation for the builder. *

* * @param validate boolean indicating whether validation * should occur. */ public void setValidation(boolean validate) { this.validate = validate; } /** *

* This sets custom XMLFilter for the Builder. *

* * @param xmlFilter XMLFilter */ public void setXMLFilter(XMLFilter xmlFilter) { saxXMLFilter = xmlFilter; } /* This sets a custom JDOMFactory for the Builder. * Use this to build the tree with you own subclasses of the * JDOM classes. *

* * @param factory JDOMFactory */ public void setFactory(JDOMFactory factory) { this.factory = factory; } }