package nl.tudelft.simulation.xml;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import nl.tudelft.simulation.language.io.URLResource;
import nl.tudelft.simulation.logger.Logger;
import org.apache.xerces.parsers.DOMParser;
import org.jdom2.Element;
import org.jdom2.input.DOMBuilder;
import org.w3c.dom.Document;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.DefaultHandler2;
import org.xml.sax.ext.EntityResolver2;
import org.xml.sax.helpers.XMLReaderFactory;
/**
*
* (c) copyright 2002-2005-2004 Delft University of Technology , the
* Netherlands.
* See for project information www.simulation.tudelft.nl
* License of use: Lesser General Public License (LGPL) , no
* warranty.
* @version Jun 27, 2004
* @author Alexander Verbraeck
*/
public abstract class AbstractXMLParser
{
/** schema validation on/off. */
private boolean validateSchema;
/** the URL of the file to parse. */
private URL url;
/** the URL of the schema file. */
private URL schema;
/** the namespace of the schema. */
private String schemaNamespace;
/**
* Parses an XML file and validates it against a schema XSD. Explicitly call parse() from the extended class, after
* other initialization tasks have been carried out. The parse() method will, after schema validation and xml-file
* validation, call the parse(Element) method that has been declared in the extended class.
* @param url the URL of the XML file to read.
* @param schema the URL of the XSD schema file to validate against.
* @param schemaNamespace the namespace of the schema
* @param validateSchema whether to validate the schema file.
*/
public AbstractXMLParser(final URL url, final URL schema, final String schemaNamespace, final boolean validateSchema)
{
this.validateSchema = validateSchema;
this.url = url;
this.schema = schema;
this.schemaNamespace = schemaNamespace;
}
/**
* This method carries out the task of parsing an XML file and validates it against a schema XSD. dsol-xml now
* contains the 2001 definitions of the schema files: XMLSchema.xsd, XMLSchema.dtd, xml.xsd, and datatypes.dtd to
* validate against. When no network connection is available, these definitions can help for validation when
* validation
is true.
* @return the JDOM root element of the XML file
* @throws Exception on failure
*/
protected Element parse() throws Exception
{
if (this.validateSchema)
{
// create the parser to validate the schema file with SAX
XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
parser.setErrorHandler(new ValidHandler());
// turns on Schema Validation with Xerces
parser.setFeature("http://xml.org/sax/features/validation", true);
parser.setFeature("http://apache.org/xml/features/validation/schema", true);
// fatal error handler
parser.setEntityResolver(new RelativePathResolver());
parser.setErrorHandler(new ValidHandler());
parser.parse(this.schema.toExternalForm());
}
// build the DOM document for the content -- validation on
DOMParser domParser = new DOMParser();
domParser.setFeature("http://xml.org/sax/features/validation", true);
domParser.setFeature("http://apache.org/xml/features/validation/schema", true);
domParser.setProperty("http://apache.org/xml/properties/schema/external-schemaLocation", this.schemaNamespace
+ " " + this.schema.toExternalForm());
domParser.setEntityResolver(new RelativePathResolver());
domParser.setErrorHandler(new ValidHandler());
// System.exit(0);
domParser.parse(this.url.toExternalForm());
Document document = domParser.getDocument();
DOMBuilder builder = new DOMBuilder();
org.jdom2.Document jdomDocument = builder.build(document);
Element xmlRootElement = jdomDocument.getRootElement();
return xmlRootElement;
}
/**
* The actual parsing method to implement, based on the availability of the entire JDOM tree, starting from the root
* element.
* @param xmlRootElement the root element
* @throws Exception on failure
*/
protected abstract void parse(final Element xmlRootElement) throws Exception;
/**
* The ValidHandler ErrorHandler gives a more extensive explanation about errors that occur during parsing. If
* needed, this inner class can be overridden to provide other forms of error handling.
*
* (c) copyright 2002-2005-2004 Delft University of Technology , the
* Netherlands.
* See for project information www.simulation.tudelft.nl
* License of use: Lesser General Public License (LGPL) , no
* warranty.
* @version Jun 27, 2004
* @author Alexander Verbraeck
*/
private static class ValidHandler implements ErrorHandler
{
/**
* format the exception with line number, column number, etc.
* @param exception the excpetion to format
* @return the String
*/
private String formatError(final SAXParseException exception)
{
return "SAXParseException: \nsystemId=" + exception.getSystemId() + "\npublicId=" + exception.getSystemId()
+ "\nMessage=" + exception.getMessage() + "\nline,col=" + exception.getLineNumber() + ","
+ exception.getColumnNumber();
}
/** {@inheritDoc} */
@Override
public void warning(final SAXParseException exception)
{
// ignore, but log
Logger.warning(this, formatError(exception), exception);
}
/** {@inheritDoc} */
@Override
public void error(final SAXParseException e) throws SAXException
{
throw new SAXException(formatError(e));
}
/** {@inheritDoc} */
@Override
public void fatalError(final SAXParseException e) throws SAXException
{
throw new SAXException(formatError(e));
}
}
/**
* The relative path resolver takes care of resolving xsd-files or xml-files, that are in the current classpath.
* Example use is the include
tag in the schema file, that can now point to a relative path.
*
* (c) copyright 2002-2005-2004 Delft University of Technology , the
* Netherlands.
* See for project information www.simulation.tudelft.nl
* License of use: Lesser General Public License (LGPL) , no
* warranty.
* @version Jun 27, 2004
* @author Alexander Verbraeck
*/
private static class RelativePathResolver implements EntityResolver2
{
/** the fallback defaultHandler2 */
private DefaultHandler2 defaultHandler2 = new DefaultHandler2();
/** {@inheritDoc} */
@Override
public InputSource getExternalSubset(final String name, final String baseURI) throws SAXException, IOException
{
return this.defaultHandler2.getExternalSubset(name, baseURI);
}
/** {@inheritDoc} */
@Override
public InputSource resolveEntity(final String name, final String publicId, final String baseURI,
final String systemId) throws SAXException, IOException
{
return this.defaultHandler2.resolveEntity(name, publicId, baseURI, systemId);
}
/** {@inheritDoc} */
@Override
public InputSource resolveEntity(final String publicId, final String systemId) throws SAXException, IOException
{
URL url;
if (systemId.startsWith("file:"))
{
url = URLResource.getResource(systemId.substring(5));
}
else
{
url = URLResource.getResource(systemId);
}
InputStream localStream = URLResource.getResourceAsStream(url.toExternalForm());
if (localStream != null)
{
return new InputSource(localStream);
}
return this.defaultHandler2.resolveEntity(publicId, systemId);
}
}
}