Search code examples
javaspringxslttransformationdtd

java - spring - xsl transformation dtd not found error


I have a code which transform xml file using xsl, my peace of code as following. My problem is when i run the execution point it gives me following error.

StackTrace: javax.xml.transform.TransformerException: javax.xml.transform.TransformerException: com.sun.org.apache.xml.internal.utils.WrappedRuntimeException: /home/app/myapp/bin/xhtml11-flat.dtd (No such file or directory)
    at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:720)
    at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(TransformerImpl.java:313)
    at com.core.util.XmlUtils.transform(XmlUtils.java:151)
    at com.core.util.XmlUtils.transform(XmlUtils.java:147)

Long story short it is trying to find dtd file inside the bin directory from where i executed the application.

/home/app/myapp/bin/xhtml11-flat.dtd

I have the xhtml11-flat.dtd file if i copy this file in bin directory it works fine, instead of the bin directory i want to load it from classpath any idea how can i achieve this with minimum changes ? I don't know from where it is laoding .dtd code so that i can set my path in it.

//Execution Point
function transform(){
    Templates templates = getTemplates();
    StringWriter result = new StringWriter();
    XmlUtils.transform(templates.newTransformer(), input, new StreamResult(result));

    ...
}

private Templates getTemplates() throws Exception {
    if (templates == null) {
        templates = XmlUtils.createTemplates(XslRdcSourceDocTransformer.class.getResourceAsStream("/xsl/" + getXslFileName()));
    }
    return templates;
}


public static Templates createTemplates(InputStream stream) throws Exception {
    TransformerFactory tfactory = TransformerFactory.newInstance();
    return tfactory.newTemplates(new StreamSource(stream));
}

Solution

  • Your xml files are probably containing a doctype declaration containing a relative path to the dtd:

    <!DOCTYPE html SYSTEM "xhtml11-flat.dtd">
    

    The transformer api tries to resolve this path to the current working directory of the java program. To customize how the path is resolved you need to implement a EntityResolver. This EntityResolver can return an InputSource referring to a copy of the dtd loaded from the classpath.

    public InputSource resolveEntity(final String publicId, final String systemId) throws SAXException {
        if ("xhtml11-flat.dtd".equals(systemId)) {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            InputSource is = new InputSource();
            is.setSystemId(systemId);
            is.setByteStream(cl.getResourceAsStream("/com/example/dtd/xhtml11-flat.dtd"));
            return is;
        } else {
            return null;
        }
    }
    

    How you use this class depends on the type of source for your transformation. For a DOMSource you have to configure the DocumentBuilder:

    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setValidating(false);
    factory.setNamespaceAware(true);
    
    DocumentBuilder builder = factory.newDocumentBuilder();
    DocumentBuilder builder = ...
    builder.setEntityResolver(entityResolver);
    
    Source source = new DOMSource(builder.parse(inputStream));
    

    For a SAXSource the setting is on the XMLReader instance:

    SAXParserFactory factory1 = SAXParserFactory.newInstance();
    factory1.setValidating(false);
    factory1.setNamespaceAware(true);
    
    SAXParser parser = factory1.newSAXParser();
    XMLReader xmlreader = parser.getXMLReader();
    xmlreader.setEntityResolver(entityResolver);
    
    Source source = new SAXSource(xmlreader, new InputSource(stream));
    

    The code for the transformation is the same regardless of the source type and should look similar to the code you currently have in your XmlUtils class:

    Templates templates = ...
    Result result = new StreamResult(...);
    Transformer transformer = templates.newTransformer();
    transformer.transform(source, result);