Project

Profile

Help

Support #6505

closed

Unable to resolve nested xsl:include statements in Java code

Added by Mark Dunn 3 months ago. Updated 3 months ago.

Status:
Closed
Priority:
Low
Assignee:
-
Category:
-
Sprint/Milestone:
-
Start date:
2024-08-13
Due date:
% Done:

0%

Estimated time:
Legacy ID:
Applies to branch:
12
Fix Committed on Branch:
Fixed in Maintenance Release:
Platforms:

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

debug.zip (891 Bytes) debug.zip Mark Dunn, 2024-08-13 14:49
xslt-include-resources.png (16.7 KB) xslt-include-resources.png Mark Dunn, 2024-08-13 14:49
Actions #1

Updated by Norm Tovey-Walsh 3 months ago

What version of Saxon?

Actions #2

Updated by Mark Dunn 3 months ago

In my POM:

<dependency>
            <groupId>net.sf.saxon</groupId>
            <artifactId>Saxon-HE</artifactId>
            <version>12.0</version>
        </dependency>
Actions #3

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.)

Actions #4

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)

Actions #5

Updated by Mark Dunn 3 months ago

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!

Actions #6

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.

Actions #7

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!

Actions #8

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

Also available in: Atom PDF