I use saxon (HE 9.9.1-6) to transform a XML to a HTML file. Saxon is used because the XSLT is version 2 and the default java classes failed.
The XSLT contains two statements to copy in the content of other files:
<xsl:value-of select="unparsed-text('file.ext')"/>
This works fine as long as the Xslt and those files are in the same directory and the xslt is given as a file source
Source xslt = new StreamSource(new File("c:/somedir/file.xsl"));
But my xslt is inside a resource directory (later on it's supposed to be packed in a jar). If I use it in that context, saxon fails to find the included files becuase it looks in the root directory of my project:
Source xslt = new StreamSource(getClass().getClassLoader().getResourceAsStream("file.xsl"));
results in:
Error evaluating (fn:unparsed-text(...)) in xsl:value-of/@select on line 22 column 66
FOUT1170: Failed to read input file: <project root directory>\included_file.css (File not found)
Is there any way that I could supply saxon with additional StreamSources for the files it needs to include? I was unable to find anything.
Ideally, I'd like something like this:
transformer.addInput(new StreamSource(getClass().getClassLoader().getResourceAsStream("inputfile.css")));
The only solution I've came up with was pretty ugly: Copy the xslt and the files it needs from the resources to a temporary directory and then do the conversion using that as a source.
Example code
I'm not knowledgeable in writing XSLT so I can only offer non-minimal example files.
The xslt and its two reuqired files (css and js) can be found here. The ones you need are the three "xrechnung" ones. Direct links: xrechnung-html.xsl, xrechnung-viewer.css, xrechnung-viewer.js.
Please put them in a resource directory (just in case, in eclipse: make a resources-folder and add it as a source directory in properties->build path).
The xml was generated by the first step of above project using it's own example files, I put it on pastebin here
(originally included directly but got character limit error)
Finally, the Java-Code including the ugly workaround:
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Comparator;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import org.xml.sax.SAXException;
import net.sf.saxon.s9api.Processor;
import net.sf.saxon.s9api.SaxonApiException;
import net.sf.saxon.s9api.Serializer;
import net.sf.saxon.s9api.Xslt30Transformer;
import net.sf.saxon.s9api.XsltCompiler;
import net.sf.saxon.s9api.XsltExecutable;
public class SaxonProblem {
public static void main(String[] args) throws IOException, SaxonApiException, SAXException {
Path xml = Paths.get("path/to/the.xml");
//working(xml);
notWorking(xml);
}
public static void working(Path xmlFile) throws IOException, SaxonApiException, SAXException {
Path dir = Files.createTempDirectory("saxon");
System.out.println("Temp dir: " + dir.toString());
Path xsltFile = dir.resolve("xrechnung-html.xsl");
Files.copy(SaxonProblem.class.getClassLoader().getResourceAsStream("xrechnung-html.xsl"),
xsltFile, StandardCopyOption.REPLACE_EXISTING);
Files.copy(SaxonProblem.class.getClassLoader().getResourceAsStream("xrechnung-viewer.css"),
dir.resolve("xrechnung-viewer.css"), StandardCopyOption.REPLACE_EXISTING);
Files.copy(SaxonProblem.class.getClassLoader().getResourceAsStream("xrechnung-viewer.js"),
dir.resolve("xrechnung-viewer.js"), StandardCopyOption.REPLACE_EXISTING);
// for the sake of brevity, the html is made where the xml was
Path html = xmlFile.resolveSibling(xmlFile.getFileName().toString() + ".html");
Source xslt = new StreamSource(xsltFile.toFile());
Source xml = new StreamSource(xmlFile.toFile());
transformXml(xml, xslt, html);
// cleanup
Files.walk(dir).sorted(Comparator.reverseOrder()).map(Path::toFile).forEach(File::delete);
}
public static void notWorking(Path xmlFile) throws SaxonApiException, SAXException, IOException {
// for the sake of brevity, the html is made where the xml was
Path html = xmlFile.resolveSibling(xmlFile.getFileName().toString() + ".html");
Source xslt = new StreamSource(SaxonProblem.class.getClassLoader().getResourceAsStream("xrechnung-html.xsl"));
Source xml = new StreamSource(xmlFile.toFile());
transformXml(xml, xslt, html);
}
public static void transformXml(Source xml, Source xslt, Path output) throws SaxonApiException, SAXException, IOException {
Processor processor = new Processor(false);
XsltCompiler compiler = processor.newXsltCompiler();
XsltExecutable stylesheet = compiler.compile(xslt);
Serializer out = processor.newSerializer(output.toFile());
out.setOutputProperty(Serializer.Property.METHOD, "html");
out.setOutputProperty(Serializer.Property.INDENT, "yes");
Xslt30Transformer transformer = stylesheet.load30();
transformer.transform(xml, out);
}
}
Solution
Thanks to the comment by Martin Honnen and the answer by Michael Kay, I have a solution using UnparsedTextURIResolver. Does feel more like a hack, but it works and is better than my previous workaround:
Processor processor = new Processor(false);
UnparsedTextURIResolver defaultUtur = processor.getUnderlyingConfiguration().getUnparsedTextURIResolver();
processor.getUnderlyingConfiguration().setUnparsedTextURIResolver(new UnparsedTextURIResolver() {
@Override
public Reader resolve(URI arg0, String arg1, Configuration arg2) throws XPathException {
if (arg0.toString().endsWith("myfilename.css")) {
InputStream css = SaxonProblem.class.getClassLoader().getResourceAsStream("myfilename.css");
return new InputStreamReader(css);
}
return defaultUtur.resolve(arg0, arg1, arg2);
}
});
//[...]
docordocumentfunction used in your XSLT code, then you can use saxonica.com/html/documentation/javadoc/net/sf/saxon/s9api/…. I thinkunparsed-texthas its own resolver saxonica.com/html/documentation/javadoc/net/sf/saxon/lib/…. I am not quite sure whethertransformer.addInput(new StreamSource(getClass().getClassLoader().getResourceAsStream("inputfile.css")));can not simply be solved by adding and setting a global parameter. - Martin Honnen