0

Here is the folder structure of my project.

src/main
├── assembly
│   └── package.xml
├── java
│   └── org
│       └── legible
│           ├── kafka
│           │   ├── CamelSftpSourceConnector.java
│           │   ├── CamelSftpSourceConnectorConfig.java
│           │   ├── CamelSftpSourceTask.java
│           │   ├── CamelSourceTask.java
│           │   └── package-info.java
│           └── reference_tag_converter
│               └── ReferenceTagTransformer.java
└── resources
    ├── schemas
    │   ├── ONIX_BookProduct_3.0_reference.xsd
    │   ├── ONIX_BookProduct_3.0_short.xsd
    │   ├── ONIX_BookProduct_CodeLists.xsd
    │   └── ONIX_XHTML_Subset.xsd
    ├── switch-onix-3.0-tagnames-2.0_v1.3.xsl
    └── switch-onixmessage-tag.xsl   

Inside of ReferenceTagTransformer, I'm instantiating an XSLT transformer as follows:



        StreamSource switchOnixMessageTagStylesheet = new StreamSource(this.getClass().getClassLoader().getResourceAsStream("switch-onixmessage-tag.xsl"));
        StreamSource transformReferenceTagStylesheet = new StreamSource(this.getClass().getClassLoader().getResourceAsStream("switch-onix-3.0-tagnames-2.0_v1.3.xsl"));

        Processor processor = new Processor(false);

        XsltCompiler compiler = processor.newXsltCompiler();

        XsltExecutable switchOnixMessageStylesheet = compiler.compile(switchOnixMessageTagStylesheet);
        XsltTransformer onixMessageTagTransformer = switchOnixMessageStylesheet.load();

        XsltExecutable convertTagsStylesheet = compiler.compile(transformReferenceTagStylesheet);
        XsltTransformer referenceTagTransformer = convertTagsStylesheet.load();

        referenceTagTransformer.setParameter(new QName("result-document"), new XdmAtomicValue(Paths.get("", "temp.xml").toString()));

        // path to XSD schemas
        referenceTagTransformer.setParameter(new QName("input-path"), new XdmAtomicValue("schemas"));

When I call transform on the transformer, I get an error that comes from the stylesheet saying that it cannot resolve the path to schemas, which is assigned to the input-path variable in my code above.

How can I set the input-path parameter on the referenceTagTransformer such that the XSL stylesheet I've loaded can find schemas in the appropriate place?

Any advice would be very helpful. Thank you very much in advance!

Here's the contents of the stylesheet for reference:

<?xml version="1.0" encoding="UTF-8"?>
<!-- switch ONIX tagnames for ONIX 3.0, XSLT 2.0 -->
<!-- © EDitEUR, licence doi:https://doi.org/10.4400/nwgj -->
<!-- version 1.1, modified to deal with XHTML markup -->
<!-- version 1.2, modified to deal with xmlns attribute in root element -->
<!-- version 1.3, modified to use XSD schema when available -->
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://ns.editeur.org/">
    <xsl:param name="input-path" required="yes"/>
    <xsl:param name="result-document" required="yes"/>
    <xsl:param name="dtd-path" select="''"/>
    <xsl:variable name="new-namespace">
        <xsl:choose>
            <xsl:when test="local-name(/*)='ONIXMessage'">
                <xsl:value-of select="replace(namespace-uri(/*),'/reference','/short')"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="replace(namespace-uri(/*),'/short','/reference')"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <xsl:variable name="release" select="/*/@release"/>
    <xsl:variable name="input-path-cleaned" select="translate(replace($input-path,' ','%20'),'\','/')"/>
    <xsl:variable name="xsd-path" select="tokenize(/*/@xsi:schemaLocation,' ')[2]"/>
    <xsl:variable name="xsd-path-absolute">
        <xsl:call-template name="make-absolute-xsd-path">
            <xsl:with-param name="input-path" select="$input-path-cleaned"/>
            <xsl:with-param name="xsd-path" select="$xsd-path"/>
        </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="xsd-content">
        <xsl:if test="$xsd-path!=''">
            <xsl:copy-of select="document($xsd-path-absolute)"/>
        </xsl:if>
    </xsl:variable>
    <xsl:variable name="target">
        <xsl:choose>
            <!-- v1.1 was xsl:when test="/ONIXMessage">short</xsl:when -->
            <xsl:when test="local-name(/*)='ONIXMessage'">short</xsl:when>
            <xsl:otherwise>reference</xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <xsl:template match="/">
        <xsl:choose>
            <xsl:when test="/*[not(@refname or @shortname)] and count($xsd-content/*) = 0">
                <xsl:message>TRANSFORMATION ABORTED! No XSD schema or DTD found!</xsl:message>
            </xsl:when>
            <xsl:when test="not($dtd-path='')">
                <xsl:result-document href="{$result-document}" method="xml" doctype-system="{$dtd-path}">
                    <xsl:apply-templates/>
                </xsl:result-document>
            </xsl:when>
            <xsl:otherwise>
                <xsl:result-document href="{$result-document}" method="xml" exclude-result-prefixes="xs">
                    <xsl:apply-templates/>
                </xsl:result-document>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    <xsl:template match="*">
        <xsl:variable name="target-name">
            <xsl:choose>
                <xsl:when test="$target='short' and $xsd-content//xs:element[@name=local-name(current())]">
                    <xsl:value-of select="$xsd-content//xs:element[@name=local-name(current())]//xs:attribute[@name='shortname']//xs:enumeration/@value"/>
                </xsl:when>
                <xsl:when test="$target='short' and not(@shortname)">
                    <xsl:value-of select="name()"/>
                </xsl:when>
                <xsl:when test="$target='short'">
                    <xsl:value-of select="@shortname"/>
                </xsl:when>
                <xsl:when test="$xsd-content//xs:element[@name=local-name(current())]">
                    <xsl:value-of select="$xsd-content//xs:element[@name=local-name(current())]//xs:attribute[@name='refname']//xs:enumeration/@value"/>
                </xsl:when>
                <xsl:when test="not(@refname)">
                    <xsl:value-of select="name()"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="@refname"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:variable>
        <xsl:element name="{$target-name}" namespace="{$new-namespace}">
            <xsl:for-each select="@*">
                <xsl:choose>
                    <xsl:when test="name()='refname' or name()='shortname'"/>
                    <xsl:when test="name()='xsi:schemaLocation' and $target='short'">
                        <xsl:attribute name="xsi:schemaLocation">
                            <xsl:value-of select="replace(.,'(/|_)reference','$1short')"/>
                        </xsl:attribute>
                    </xsl:when>
                    <xsl:when test="name()='xsi:schemaLocation'">
                        <xsl:attribute name="xsi:schemaLocation">
                            <xsl:value-of select="replace(.,'(/|_)short','$1reference')"/>
                        </xsl:attribute>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:copy-of select="."/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:for-each>
            <xsl:apply-templates select="*|text()"/>
        </xsl:element>
    </xsl:template>
    <xsl:template match="text()">
        <xsl:copy/>
    </xsl:template>
    <xsl:template name="make-absolute-xsd-path">
        <xsl:param name="input-path"/>
        <xsl:param name="xsd-path"/>
        <xsl:choose>
            <xsl:when test="starts-with($xsd-path,'file:/') and contains('ABCDEFGHIJKLMNOPQRSTUVWXYZ',substring($xsd-path,7,1)) and substring($xsd-path,8,1)=':'">
                <xsl:value-of select="concat('///',substring-after($xsd-path,'file:/'))"/>
            </xsl:when>
            <xsl:when test="starts-with($xsd-path,'file:/')">
                <xsl:value-of select="substring-after($xsd-path,'file:')"/>
            </xsl:when>
            <xsl:when test="starts-with($xsd-path,'/')">
                <xsl:value-of select="$xsd-path"/>
            </xsl:when>
            <xsl:when test="starts-with($xsd-path,'./')">
                <xsl:value-of select="concat($input-path,substring-after($xsd-path,'.'))"/>
            </xsl:when>
            <xsl:when test="starts-with($xsd-path,'../')">
                <xsl:variable name="new-input-path">
                    <xsl:call-template name="remove-input-path-segment">
                        <xsl:with-param name="input-path" select="$input-path"/>
                    </xsl:call-template>
                </xsl:variable>
                <xsl:call-template name="make-absolute-xsd-path">
                    <xsl:with-param name="input-path" select="$new-input-path"/>
                    <xsl:with-param name="xsd-path" select="substring-after($xsd-path,'../')"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="concat($input-path,'/',$xsd-path)"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
    <xsl:template name="remove-input-path-segment">
        <xsl:param name="input-path"/>
        <xsl:param name="in-path" select="false()"/>
        <xsl:if test="contains($input-path,'/')">
            <xsl:if test="$in-path=true()">/</xsl:if>
            <xsl:value-of select="substring-before($input-path,'/')"/>
            <xsl:call-template name="remove-input-path-segment">
                <xsl:with-param name="input-path" select="substring-after($input-path,'/')"/>
                <xsl:with-param name="in-path" select="true()"/>
            </xsl:call-template>
        </xsl:if>
    </xsl:template>
</xsl:stylesheet>

IlyaMeer
  • 108
  • 5

1 Answers1

1

The problem is that when executing this line

XsltExecutable switchOnixMessageStylesheet = compiler.compile(switchOnixMessageTagStylesheet);

the XSLT compiler just reads a byte stream. It does not know where those bytes came from. It could be a file in some directory, it could come from the classpath, it could even come from the network. Your code though should know where the data came from.

Inside the XSLT the schema and other stylesheets are referenced via URIs. All you need to do is give the compiler a URI resolver via setURIResolver(). Your code knows where the stylesheet came from, so you should know how where to lookup the dependencies.

EDIT: The same may apply when the XSLT is executed on the target document. So you may also want to set the URIResolver on the transformer using setURIResolver().

Queeg
  • 7,748
  • 1
  • 16
  • 42
  • Thank you for your answer! Yes, I've looked into [this thread](https://stackoverflow.com/questions/68639704/java-saxon-10-5-xsl-3-transformation-href-file-not-found) about using `setURIResolver` but the documentation I was looking at mentioned using it to resolve `xsl:import` and `xsl:include`. I didn't realize there was also `xsl:import-schema`. I'll try to modify my stylesheet to reference the schema using `xsl:import-schema` and see if maybe that works. I'll give it a shot now – IlyaMeer Nov 12 '21 at 21:20
  • Oh, I just realized that `xsl:import-schema` is only available in the Saxon EE. I guess this won't quite work. – IlyaMeer Nov 12 '21 at 21:40
  • 1
    @IlyaMeer, to resolve a `document` call set the resolver on the XsltTransformer. On the XsltCompiler you indeed set a resolver to handle xsl:import or xsl:include, but your XSLT code just wants to use the XSLT function `document`. – Martin Honnen Nov 12 '21 at 23:05
  • @MartinHonnen Yes! This was the crucial detail. I didn't realize that the XsltTransformer has its own URI resolver. That was exactly what I needed to resolve the path to the schemas. Thank you! – IlyaMeer Nov 14 '21 at 20:42