Support #6505
closedUnable to resolve nested xsl:include statements in Java code
0%
Description
I am writing a Java application to execute XSLT.
The top level stylesheet includes another stylesheet, which includes another. All using relative URLs.
In the Java code I have tried setting the System ID of the XSLT resource when building my XsltExecutable:
public void setXSLT(String filepath) {
ClassLoader cl = getClass().getClassLoader();
InputStream is = cl.getResourceAsStream(filepath);
URL url = cl.getResource(filepath);
this.xsltStream = new StreamSource(is);
xsltStream.setSystemId(url.toExternalForm());
this.xsltExec = this.xsltCompiler.compile( this.xsltStream);
}
To resolve xsl:include/@href in the XSLT I wrote an implementation of URIResolver:
public class BasicURIResolver implements URIResolver {
@Override
public Source resolve(String href, String base) throws TransformerException {
System.out.println("[BasicURIResolver] href = " + href);
System.out.println("[BasicURIResolver] base = " + base);
ClassLoader cl = this.getClass().getClassLoader();
try {
InputStream is = cl.getResourceAsStream(href);
URL url = cl.getResource(href);
Source source = new StreamSource(is);
source.setSystemId(url.toExternalForm());
return source;
}
catch (Exception ex) {
System.out.println("ERROR: (resolving " + href + ") " + ex.getMessage());
}
return null;
}
}
This is registered in the Saxon config file:
<global
uriResolver="com.oup.bits2word.xml.BasicURIResolver"
/>
This works for the first level of xsl:include, but at the next level down the URL is null and I get the errors:
ERROR: (resolving log4xslt.xsl) Cannot invoke "java.net.URL.toExternalForm()" because "url" is null
ERROR: (resolving common.templates.xsl) Cannot invoke "java.net.URL.toExternalForm()" because "url" is null
ERROR: (resolving common.variables.xsl) Cannot invoke "java.net.URL.toExternalForm()" because "url" is null
The base URI is supplied to the resolve() method as shown in the console log:
[BasicURIResolver] href = lib/utilities/log4xslt/log4xslt.config.xsl
[BasicURIResolver] base = file:/C:/SVN/software/General/HTMLtools/BITS2WORD/BITS2Word/target/classes/BITS2GenXHTML.xslt
[BasicURIResolver] href = log4xslt.xsl
[BasicURIResolver] base = file:/C:/SVN/software/General/HTMLtools/BITS2WORD/BITS2Word/target/classes/lib/utilities/log4xslt/log4xslt.config.xsl
ERROR: (resolving log4xslt.xsl) Cannot invoke "java.net.URL.toExternalForm()" because "url" is null
...
I'm guessing this is a problem in my Java code, or an error in my Saxon configuration, rather than any kind of bug in Saxon but I'm unable to see how to fix it.
Files
Updated by Norm Tovey-Walsh 3 months ago
The Cannot invoke
message isn't one of ours and the only calls to toExternalForm
that I can find are in unit tests. :-/
Can you persuade the system to give you a stack trace?
Can you provide an example stylesheet? (I'm willing to try to craft a URIResolver
along the lines you outline, but without reproducible test case, it'll be hard to tell if I've, uh, reproduced it.)
Updated by Mark Dunn 3 months ago
Stack trace:
java.lang.NullPointerException: Cannot invoke "java.net.URL.toExternalForm()" because "url" is null at com.oup.bits2word.xml.BasicURIResolver.resolve(BasicURIResolver.java:34) at net.sf.saxon.lib.ResourceResolverWrappingURIResolver.resolve(ResourceResolverWrappingURIResolver.java:58) at net.sf.saxon.lib.ResourceRequest.resolve(ResourceRequest.java:130) at net.sf.saxon.style.UseWhenFilter.processIncludeImport(UseWhenFilter.java:255) at net.sf.saxon.style.UseWhenFilter.handleInXsltNamespace(UseWhenFilter.java:380) at net.sf.saxon.style.UseWhenFilter.startElement(UseWhenFilter.java:176) at net.sf.saxon.event.Stripper.startElement(Stripper.java:101) at net.sf.saxon.event.CommentStripper.startElement(CommentStripper.java:48) at net.sf.saxon.event.ReceivingContentHandler.startElement(ReceivingContentHandler.java:387) at java.xml/com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.startElement(AbstractSAXParser.java:518) at java.xml/com.sun.org.apache.xerces.internal.parsers.AbstractXMLDocumentParser.emptyElement(AbstractXMLDocumentParser.java:183) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(XMLNSDocumentScannerImpl.java:351) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2726) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:605) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:112) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:542) at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:889) at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:825) at java.xml/com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141) at java.xml/com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1224) at java.xml/com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:637) at net.sf.saxon.resource.ActiveSAXSource.deliver(ActiveSAXSource.java:190) at net.sf.saxon.resource.ActiveStreamSource.deliver(ActiveStreamSource.java:65) at net.sf.saxon.event.Sender.send(Sender.java:104) at net.sf.saxon.style.StylesheetModule.sendStylesheetSource(StylesheetModule.java:153) at net.sf.saxon.style.StylesheetModule.loadStylesheetModule(StylesheetModule.java:109) at net.sf.saxon.style.UseWhenFilter.processIncludeImport(UseWhenFilter.java:273) at net.sf.saxon.style.UseWhenFilter.handleInXsltNamespace(UseWhenFilter.java:380) at net.sf.saxon.style.UseWhenFilter.startElement(UseWhenFilter.java:176) at net.sf.saxon.event.Stripper.startElement(Stripper.java:101) at net.sf.saxon.event.CommentStripper.startElement(CommentStripper.java:48) at net.sf.saxon.event.ProxyReceiver.startElement(ProxyReceiver.java:140) at net.sf.saxon.event.Valve.startElement(Valve.java:64) at net.sf.saxon.event.ReceivingContentHandler.startElement(ReceivingContentHandler.java:387) at java.xml/com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.startElement(AbstractSAXParser.java:518) at java.xml/com.sun.org.apache.xerces.internal.parsers.AbstractXMLDocumentParser.emptyElement(AbstractXMLDocumentParser.java:183) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.scanStartElement(XMLNSDocumentScannerImpl.java:351) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2726) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:605) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:112) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:542) at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:889) at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:825) at java.xml/com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141) at java.xml/com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1224)
Updated by Mark Dunn 3 months ago
- File debug.zip debug.zip added
- File xslt-include-resources.png xslt-include-resources.png added
I've attached a trivial stylesheet with nested xsl:include.
Curiously this one failed on the first include.
The difference is that the top-level stylesheet is in a subfolder. I've attached a screenshot showing where the XSLT sits when the Java code is running from NetBeans (xslt-include-resources.png).
Previously my top-level stylesheet was in the top-level classes folder:
SAXON.setXSLT("BITS2GenXHTML.xslt");
Replaced by
SAXON.setXSLT("debug/debug1.xsl");
With both the debug and real stylesheets, the failure occurs when trying to parse xsl:include/@href occuring in an XSLT file in a subfolder.
So something to do with the presence of subfolders?
Thanks for your help Norm - I appreciate the problem probably lies outside Saxon!
Updated by Norm Tovey-Walsh 3 months ago
Okay. I see now. I think. Maybe. You're attempting to resolve the URI with:
InputStream is = cl.getResourceAsStream(href);
URL url = cl.getResource(href);
That suggests that you think href
is a path that you can resolve against the classpath. I expect the problem is that the URI your importing at the second level has not been resolved against what you think of as the base URI.
The first URI you resolve is lib/utilities/log4xslt/log4xslt.config.xsl
and that is (indeed) on your classpath so resolution succeeds.
Now, my guess is that log4xslt.config.xsl
contains an import like this:
<xsl:import href="log4xslt.xsl"/>
You think of that as being relative to lib/utilities/log4xslt/
but that isn't an absolute URI. So that has been resolved against some base URI.
You try to load cl.getResourceAsStream("log4xslt.xsl")
but that doesn't work because you've "lost" the path.
If you look at the base URI you've been passed, you can see that the path part you need is in there. What you need to do is something like:
URI absuri = URI(base)
URI srcuri = absuri.resolve(href).toString()
int pos = srcuri.indexOf("/classes/lib")
String path = srcuri.substring(pos + 11) // Or 12 or 10 or something
InputStream is = cl.getResourceAsStream(path);
URL url = cl.getResource(path);
I think that will work for both the first call and subsequent calls. Exactly how you work out what part of the srcuri
you want is going to depend on how your deployment works.
Hope that helps.
Updated by Mark Dunn 3 months ago
Diagnosis and solution spot on Norm, thanks very much!
I implemented it slightly differently, as I needed to get it to work on Windows and Linux, and in a jar (or not):
public Source resolve(String href, String base) throws TransformerException {
File baseFile = new File(base);
String baseFolder = baseFile.getParent();
String baseFolderParsed = baseFolder.replaceAll("\\\\", "/") + "/";
int pos = baseFolderParsed.indexOf(".jar!/");
String path = baseFolderParsed.substring(pos + 6) + href; // 6 is the length of ".jar!/"
if (pos == -1) { // ".jar!/" will only be found when running the jar file
pos = baseFolderParsed.indexOf("/classes/");
path = baseFolderParsed.substring(pos + 9) + href; // 9 is the length of "/classes/"
}
try {
ClassLoader cl = this.getClass().getClassLoader();
InputStream is = cl.getResourceAsStream(path);
URL url = cl.getResource(path);
Source source = new StreamSource(is);
source.setSystemId(url.toExternalForm());
return source;
}
catch (Exception ex) {
//ex.printStackTrace();
System.out.println("ERROR: (resolving " + href + ") " + ex.getMessage());
}
return null;
}
Thanks again for your help Norm!
Updated by Norm Tovey-Walsh 3 months ago
- Status changed from New to Closed
Happy to help. Glad you found a solution!
Please register to edit this issue