/*--
$Id: SAXBuilder.java,v 1.2 2013-09-20 15:11:33 fbusquets Exp $
Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions, and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions, and the disclaimer that follows
these conditions in the documentation and/or other materials
provided with the distribution.
3. The name "JDOM" must not be used to endorse or promote products
derived from this software without prior written permission. For
written permission, please contact
* Known issues: Relative paths for a {@link DocType} or {@link EntityRef} may
* be converted by the SAX parser into absolute paths.
*
* @version $Revision: 1.2 $, $Date: 2013-09-20 15:11:33 $
* @author Jason Hunter
* @author Brett McLaughlin
* @author Dan Schaffer
* @author Philip Nelson
* @author Alex Rosen
*/
public class SAXBuilder {
private static final String CVS_ID =
"@(#) $RCSfile: SAXBuilder.java,v $ $Revision: 1.2 $ $Date: 2013-09-20 15:11:33 $ $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();
/** Whether to ignore ignorable whitespace */
private boolean ignoringWhite = false;
/** User-specified features to be set on the SAX parser */
private HashMap features = new HashMap(5);
/** User-specified properties to be set on the SAX parser */
private HashMap properties = new HashMap(5);
/**
* Whether parser reuse is allowed.
* Default:
* Note: As SAX parser instances are not thread safe,
* the parser reuse feature should not be used with SAXBuilder instances
* shared among threads.true
boolean
indicating if
* validation should occur.
*/
public SAXBuilder(boolean validate) {
this.validate = validate;
}
/**
* 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;
}
/**
* Returns the driver class assigned in the constructor, or null if none.
*
* @return the driver class assigned in the constructor
*/
public String getDriverClass() {
return saxDriverClass;
}
/**
* Returns the current {@link org.jdom.JDOMFactory} in use.
* @return the factory in use
*/
public JDOMFactory getFactory() {
return factory;
}
/**
* This sets a custom JDOMFactory for the builder. Use this to build
* the tree with your own subclasses of the JDOM classes.
*
* @param factory JDOMFactory
to use
*/
public void setFactory(JDOMFactory factory) {
this.factory = factory;
}
/**
* Returns whether validation is to be performed during the build.
*
* @return whether validation is to be performed during the build
*/
public boolean getValidation() {
return validate;
}
/**
* This sets validation for the builder.
*
* @param validate boolean
indicating whether validation
* should occur.
*/
public void setValidation(boolean validate) {
this.validate = validate;
}
/**
* Returns the {@link ErrorHandler} assigned, or null if none.
* @return the ErrorHandler assigned, or null if none
*/
public ErrorHandler getErrorHandler() {
return saxErrorHandler;
}
/**
* This sets custom ErrorHandler for the Builder
.
*
* @param errorHandler ErrorHandler
*/
public void setErrorHandler(ErrorHandler errorHandler) {
saxErrorHandler = errorHandler;
}
/**
* Returns the {@link EntityResolver} assigned, or null if none.
*
* @return the EntityResolver assigned
*/
public EntityResolver getEntityResolver() {
return saxEntityResolver;
}
/**
* This sets custom EntityResolver for the Builder
.
*
* @param entityResolver EntityResolver
*/
public void setEntityResolver(EntityResolver entityResolver) {
saxEntityResolver = entityResolver;
}
/**
* Returns the {@link DTDHandler} assigned, or null if none.
*
* @return the DTDHandler assigned
*/
public DTDHandler getDTDHandler() {
return saxDTDHandler;
}
/**
* This sets custom DTDHandler for the Builder
.
*
* @param dtdHandler DTDHandler
*/
public void setDTDHandler(DTDHandler dtdHandler) {
saxDTDHandler = dtdHandler;
}
/**
* Returns the {@link XMLFilter} used during parsing, or null if none.
*
* @return the XMLFilter used during parsing
*/
public XMLFilter getXMLFilter() {
return saxXMLFilter;
}
/**
* This sets a custom {@link org.xml.sax.XMLFilter} for the builder.
*
* @param xmlFilter the filter to use
*/
public void setXMLFilter(XMLFilter xmlFilter) {
saxXMLFilter = xmlFilter;
}
/**
* Returns whether element content whitespace is to be ignored during the
* build.
*
* @return whether element content whitespace is to be ignored during the
* build
*/
public boolean getIgnoringElementContentWhitespace() {
return ignoringWhite;
}
/**
* Specifies whether or not the parser should elminate whitespace in
* element content (sometimes known as "ignorable whitespace") when
* building the document. Only whitespace which is contained within
* element content that has an element only content model will be
* eliminated (see XML Rec 3.2.1). For this setting to take effect
* requires that validation be turned on. The default value of this
* setting is false
.
*
* @param ignoringWhite Whether to ignore ignorable whitespace
*/
public void setIgnoringElementContentWhitespace(boolean ignoringWhite) {
this.ignoringWhite = ignoringWhite;
}
/**
* Returns whether the contained SAX parser instance is reused across
* multiple parses. The default is true.
*
* @return whether the contained SAX parser instance is reused across
* multiple parses
*/
public boolean getReuseParser() {
return reuseParser;
}
/**
* Specifies whether this builder shall reuse the same SAX parser
* when performing subsequent parses or allocate a new parser for
* each parse. The default value of this setting is
* true
(parser reuse).
*
* NOTE: SAXBuilder requires that some particular features of the SAX parser be * set up in certain ways for it to work properly. The list of such features * may change in the future. Therefore, the use of this method may cause * parsing to break, and even if it doesn't break anything today it might * break parsing in a future JDOM version, because what JDOM parsers require * may change over time. Use with caution. *
* * @param name The feature name, which is a fully-qualified URI. * @param value The requested state of the feature (true or false). */ public void setFeature(String name, boolean value) { // Save the specified feature for later. features.put(name, new Boolean(value)); } /** * This sets a property on the SAX parser. See the SAX documentation for * more information. ** NOTE: SAXBuilder requires that some particular properties of the SAX parser be * set up in certain ways for it to work properly. The list of such properties * may change in the future. Therefore, the use of this method may cause * parsing to break, and even if it doesn't break anything today it might * break parsing in a future JDOM version, because what JDOM parsers require * may change over time. Use with caution. *
* * @param name The property name, which is a fully-qualified URI. * @param value The requested value for the property. */ public void setProperty(String name, Object value) { // Save the specified property for later. properties.put(name, value); } /** * This builds a document from the supplied * input source. * * @param inInputSource
to read from
* @return Document
resultant Document object
* @throws JDOMException when errors occur in parsing
* @throws IOException when an I/O error prevents a document
* from being fully parsed
*/
public Document build(InputSource in)
throws JDOMException, IOException {
SAXHandler contentHandler = null;
try {
// Create and configure the content handler.
contentHandler = createContentHandler();
configureContentHandler(contentHandler);
XMLReader parser = this.saxParser;
if (parser == null) {
// Create and configure the parser.
parser = createParser();
// 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;
}
// Configure parser
configureParser(parser, contentHandler);
if (reuseParser == true) {
this.saxParser = parser;
}
}
else {
// Reset content handler as SAXHandler instances cannot
// be reused
configureParser(parser, contentHandler);
}
// Parse the document.
parser.parse(in);
return contentHandler.getDocument();
}
catch (SAXParseException e) {
Document doc = contentHandler.getDocument();
if (doc.hasRootElement() == false) {
doc = null;
}
String systemId = e.getSystemId();
if (systemId != null) {
throw new JDOMParseException("Error on line " +
e.getLineNumber() + " of document " + systemId, e, doc);
} else {
throw new JDOMParseException("Error on line " +
e.getLineNumber(), e, doc);
}
}
catch (SAXException e) {
throw new JDOMParseException("Error in building: " +
e.getMessage(), e, contentHandler.getDocument());
}
finally {
// Explicitly nullify the handler to encourage GC
// It's a stack var so this shouldn't be necessary, but it
// seems to help on some JVMs
contentHandler = null;
}
}
/**
* This creates the SAXHandler that will be used to build the Document.
*
* @return SAXHandler
- resultant SAXHandler object.
*/
protected SAXHandler createContentHandler() {
SAXHandler contentHandler = new SAXHandler(factory);
return contentHandler;
}
/**
* This configures the SAXHandler that will be used to build the Document.
* * The default implementation simply passes through some configuration * settings that were set on the SAXBuilder: setExpandEntities() and * setIgnoringElementContentWhitespace(). *
*/ protected void configureContentHandler(SAXHandler contentHandler) { // Setup pass through behavior contentHandler.setExpandEntities(expand); contentHandler.setIgnoringElementContentWhitespace(ignoringWhite); } /** * This creates the XMLReader to be used for reading the XML document. ** The default behavior is to (1) use the saxDriverClass, if it has been * set, (2) try to obtain a parser from JAXP, if it is available, and * (3) if all else fails, use a hard-coded default parser (currently * the Xerces parser). Subclasses may override this method to determine * the parser to use in a different way. *
* * @returnXMLReader
- resultant XMLReader object.
*/
protected XMLReader createParser() throws JDOMException {
XMLReader parser = null;
if (saxDriverClass != null) {
// The user knows that they want to use a particular class
try {
parser = XMLReaderFactory.createXMLReader(saxDriverClass);
// Configure parser
setFeaturesAndProperties(parser, true);
}
catch (SAXException e) {
throw new JDOMException("Could not load " + saxDriverClass, e);
}
} 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 {
// Get factory class and method.
Class factoryClass =
Class.forName("org.jdom.input.JAXPParserFactory");
Method createParser =
factoryClass.getMethod("createParser",
new Class[] { boolean.class, Map.class, Map.class });
// Create SAX parser.
parser = (XMLReader)createParser.invoke(null,
new Object[] { new Boolean(validate),
features, properties });
// Configure parser
setFeaturesAndProperties(parser, false);
}
catch (JDOMException e) {
throw e;
}
catch (NoClassDefFoundError e) {
// The class loader failed to resolve the dependencies
// of org.jdom.input.JAXPParserFactory. This probably means
// that no JAXP parser is present in its class path.
// => Ignore and try allocating default SAX parser instance.
}
catch (Exception e) {
// Ignore and try allocating default SAX parser instance.
}
}
// Check to see if we got a parser yet, if not, try to use a
// hard coded default
if (parser == null) {
try {
parser = XMLReaderFactory.createXMLReader(DEFAULT_SAX_DRIVER);
// System.out.println("using default " + DEFAULT_SAX_DRIVER);
saxDriverClass = parser.getClass().getName();
// Configure parser
setFeaturesAndProperties(parser, true);
}
catch (SAXException e) {
throw new JDOMException("Could not load default SAX parser: "
+ DEFAULT_SAX_DRIVER, e);
}
}
return parser;
}
/**
* This configures the XMLReader to be used for reading the XML document.
*
* The default implementation sets various options on the given XMLReader,
* such as validation, DTD resolution, entity handlers, etc., according
* to the options that were set (e.g. via setEntityResolver
)
* and set various SAX properties and features that are required for JDOM
* internals. These features may change in future releases, so change this
* behavior at your own risk.
*
* Tries to set a property on the parser. If the property cannot be set, * throws a JDOMException describing the problem. *
*/ private void internalSetProperty(XMLReader parser, String property, Object value, String displayName) throws JDOMException { try { parser.setProperty(property, value); } catch (SAXNotSupportedException e) { throw new JDOMException( displayName + " property not supported for SAX driver " + parser.getClass().getName()); } catch (SAXNotRecognizedException e) { throw new JDOMException( displayName + " property not recognized for SAX driver " + parser.getClass().getName()); } } /** ** This builds a document from the supplied * input stream. *
* * @param inInputStream
to read from
* @return Document
resultant Document object
* @throws JDOMException when errors occur in parsing
* @throws IOException when an I/O error prevents a document
* from being fully parsed.
*/
public Document build(InputStream in)
throws JDOMException, IOException {
return build(new InputSource(in));
}
/**
* * This builds a document from the supplied * filename. *
* * @param fileFile
to read from
* @return Document
resultant Document object
* @throws JDOMException when errors occur in parsing
* @throws IOException when an I/O error prevents a document
* from being fully parsed
*/
public Document build(File file)
throws JDOMException, IOException {
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 * URL. *
* * @param urlURL
to read from.
* @return Document
- resultant Document object.
* @throws JDOMException when errors occur in parsing
* @throws IOException when an I/O error prevents a document
* from being fully parsed.
*/
public Document build(URL url)
throws JDOMException, IOException {
String systemID = url.toExternalForm();
return build(new InputSource(systemID));
}
/**
* * This builds a document from the supplied * input stream. *
* * @param inInputStream
to read from.
* @param systemId base for resolving relative URIs
* @return Document
resultant Document object
* @throws JDOMException when errors occur in parsing
* @throws IOException when an I/O error prevents a document
* from being fully parsed
*/
public Document build(InputStream in, String systemId)
throws JDOMException, IOException {
InputSource src = new InputSource(in);
src.setSystemId(systemId);
return build(src);
}
/**
* * This builds a document from the supplied * Reader. It's the programmer's responsibility to make sure * the reader matches the encoding of the file. It's often easier * and safer to use an InputStream rather than a Reader, and to let the * parser auto-detect the encoding from the XML declaration. *
* * @param characterStreamReader
to read from
* @return Document
resultant Document object
* @throws JDOMException when errors occur in parsing
* @throws IOException when an I/O error prevents a document
* from being fully parsed
*/
public Document build(Reader characterStream)
throws JDOMException, IOException {
return build(new InputSource(characterStream));
}
/**
* * This builds a document from the supplied * Reader. It's the programmer's responsibility to make sure * the reader matches the encoding of the file. It's often easier * and safer to use an InputStream rather than a Reader, and to let the * parser auto-detect the encoding from the XML declaration. *
* * @param characterStreamReader
to read from.
* @param systemId base for resolving relative URIs
* @return Document
resultant Document object
* @throws JDOMException when errors occur in parsing
* @throws IOException when an I/O error prevents a document
* from being fully parsed
*/
public Document build(Reader characterStream, String systemId)
throws JDOMException, IOException {
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 * @returnDocument
resultant Document object
* @throws JDOMException when errors occur in parsing
* @throws IOException when an I/O error prevents a document
* from being fully parsed
*/
public Document build(String systemId)
throws JDOMException, IOException {
return build(new InputSource(systemId));
}
// /**
// * 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);
// }
/** Custom File.toUrl() implementation to handle special chars in file names
*
* @param file file object whose path will be converted
* @return URL form of the file, with special characters handled
* @throws MalformedURLException if there's a problem constructing a URL
*/
private static URL fileToURL(File file) throws MalformedURLException {
StringBuilder buffer = new StringBuilder();
String path = file.getAbsolutePath();
// Convert non-URL style file separators
if (File.separatorChar != '/') {
path = path.replace(File.separatorChar, '/');
}
// Make sure it starts at root
if (!path.startsWith("/")) {
buffer.append('/');
}
// Copy, converting URL special characters as we go
int len = path.length();
for (int i = 0; i < len; i++) {
char c = path.charAt(i);
if (c == ' ')
buffer.append("%20");
else if (c == '#')
buffer.append("%23");
else if (c == '%')
buffer.append("%25");
else if (c == '&')
buffer.append("%26");
else if (c == ';')
buffer.append("%3B");
else if (c == '<')
buffer.append("%3C");
else if (c == '=')
buffer.append("%3D");
else if (c == '>')
buffer.append("%3E");
else if (c == '?')
buffer.append("%3F");
else if (c == '~')
buffer.append("%7E");
else
buffer.append(c);
}
// Make sure directories end with slash
if (!path.endsWith("/") && file.isDirectory()) {
buffer.append('/');
}
// Return URL
return new URL("file", "", buffer
// JCLIC modification
//.toString()
.substring(0)
);
}
/**
* Returns whether or not entities are being expanded into normal text
* content.
*
* @return whether entities are being expanded
*/
public boolean getExpandEntities() {
return expand;
}
/**
*
* 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.
*
* When this setting is false, the internal DTD subset is retained; when * this setting is true, the internal DTD subset is not retained. *
** Note that Xerces (at least up to 1.4.4) has a bug where entities * in attribute values will be misreported if this flag is turned off, * resulting in entities to appear within element content. When turning * entity expansion off either avoid entities in attribute values, or * use another parser like Crimson. * http://nagoya.apache.org/bugzilla/show_bug.cgi?id=6111 *
* * @param expandboolean
indicating whether entity expansion
* should occur.
*/
public void setExpandEntities(boolean expand) {
this.expand = expand;
}
}