0

I'm writing a Java application that does a XML transformation using XSLT3, using Saxon-HE 10.5 (as a Maven project). My XSLT sheet imports other XSLT sheets, using <xsl:import> (e.g. <xsl:import href="sheet1.xsl"/>). All of the XSLT sheets are located inside ./src/main/resources. However, when I try to run the program, I get a FileNotFound Exception from Saxon, since it is looking for the files at the project base directory.

I assume there is some way to change where Saxon is looking for the files, but I was not able to find out how to achieve this when using the s9api API.

Here's my Java code performing the transformation:

public void transformXML(String xmlFile, String output) throws SaxonApiException, IOException, XPathExpressionException, ParserConfigurationException, SAXException {

        Processor processor = new Processor(false);
        XsltCompiler compiler = processor.newXsltCompiler();
        XsltExecutable stylesheet = compiler.compile(new StreamSource(this.getClass().getClassLoader().getResourceAsStream("transform.xsl")));

        Serializer out = processor.newSerializer(new File(output));
        out.setOutputProperty(Serializer.Property.METHOD, "text");
        Xslt30Transformer transformer = stylesheet.load30();
        transformer.transform(new StreamSource(new File(xmlFile)), out);
    }

Any help is appreciated.

Edit: My solution based on @Michael Kay's recommendation:

public void transformXML(String xmlFile, String output) throws SaxonApiException, IOException, XPathExpressionException, ParserConfigurationException, SAXException {

        Processor processor = new Processor(false);
        XsltCompiler compiler = processor.newXsltCompiler();
        compiler.setURIResolver(new ClasspathResourceURIResolver());
        XsltExecutable stylesheet = compiler.compile(new StreamSource(this.getClass().getClassLoader().getResourceAsStream("transform.xsl")));

        Serializer out = processor.newSerializer(new File(output));
        out.setOutputProperty(Serializer.Property.METHOD, "text");
        Xslt30Transformer transformer = stylesheet.load30();
        transformer.transform(new StreamSource(new File(xmlFile)), out);
    }
}

class ClasspathResourceURIResolver implements URIResolver
{
    @Override
    public Source resolve(String href, String base) throws TransformerException {
        return new StreamSource(this.getClass().getClassLoader().getResourceAsStream(href));
    }
}
  • I guess you will need to use https://docs.oracle.com/javase/8/docs/api/javax/xml/transform/stream/StreamSource.html#StreamSource-java.io.InputStream-java.lang.String- to ensure the base URI of the stylesheet is known by Saxon, a StreamSource over a Stream doesn't carry that information. Or set up a URIResolver https://www.saxonica.com/html/documentation10/javadoc/net/sf/saxon/s9api/XsltCompiler.html#setURIResolver-javax.xml.transform.URIResolver- on the compiler. – Martin Honnen Aug 03 '21 at 17:58
  • https://stackoverflow.com/a/12453881/252228 might have an example for a URIResolver using a class loader – Martin Honnen Aug 03 '21 at 18:04

1 Answers1

0

Saxon doesn't know the base URI of the stylesheet (it has no way of knowing, because you haven't told it), so it can't resolve relative URIs appearing in xsl:import/@href.

Normally I would suggest supplying a base URI in the second argument of new StreamSource(). However, since the main stylesheet is loaded using getResourceAsStream(), I suspect you want to load secondary stylesheet modules using the same mechanism, and this can be done by setting a URIResolver on the XsltCompiler object.

Michael Kay
  • 156,231
  • 11
  • 92
  • 164