3
votes

I am attempting to validate my instance documents with schematron and having trouble with Java XSLT processor implementations. When I try to generate an XSL from my schematron, even a simple one, I get different results from what I expect to get.

When xsltproc (cygwin) is used to do the transformation everything's okay. But using Saxon-B 9.1.0.8, the default java 1.6 implementation or Oxygen IDE's XSLT debugger with Saxon-PE 9.3.0.5 generates a non-valid XSL file. The cause being the extension-element-prefixes attribute of the xsl:stylesheet element containing the exsl prefix, for which a namespace is not declared anywhere. Such a stylesheet is bound to fail when used.

Manually editing the generated files is out of the question since schematron is generated in an application at runtime. I've got to be doing something wrong, right? I've tracked the problem into the iso_schematron_skeleton_for_saxon.xsl (or iso_schematron_skeleton_for_xslt1.xsl, depending on the implementation used, yes I tried both with the same end result). Even though the skeleton clearly creates a proper stylesheet with the missing namespace, it is later somehow magically removed. I'm am not really an XSLT expert, so I have no idea what I'm doing wrong here. I'm sure it's just some silly thing I missed somewhere. I'd like to know why the output for the input below differs.

Here's a sample schematron which does nothing but defines some namespaces (I hope this is considered a valid schema):

<?xml version="1.0" encoding="utf-8"?>
<sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron" 
    queryBinding="exslt">
   <sch:ns uri="http://exslt.org/dynamic" prefix="dyn"/>
   <sch:ns xmlns:rng="http://relaxng.org/ns/structure/1.0" 
      xmlns:nma="urn:ietf:params:xml:ns:netmod:dsdl-annotations:1" 
      uri="urn:ietf:params:xml:ns:yang:ietf-inet-types" 
      prefix="inet"/>
   <sch:ns xmlns:rng="http://relaxng.org/ns/structure/1.0" 
      xmlns:nma="urn:ietf:params:xml:ns:netmod:dsdl-annotations:1" 
      uri="urn:ietf:params:xml:ns:yang:ietf-ipfix-psamp" 
      prefix="ipfix"/>
   <sch:ns xmlns:rng="http://relaxng.org/ns/structure/1.0" 
      xmlns:nma="urn:ietf:params:xml:ns:netmod:dsdl-annotations:1" 
      uri="urn:ietf:params:xml:ns:yang:ietf-yang-types" 
      prefix="yang"/>
   <sch:ns xmlns:rng="http://relaxng.org/ns/structure/1.0" 
      xmlns:nma="urn:ietf:params:xml:ns:netmod:dsdl-annotations:1" 
      uri="urn:ietf:params:xml:ns:netconf:base:1.0" 
      prefix="nc"/>   
</sch:schema>

Here's the snippet I get from Java XSLT processors (this one is from Oxygen, doesn't matter if iso for xslt1 or xslt2 is used, the problem persists):

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--This XSLT was automatically generated from a Schematron schema.-->
<xsl:stylesheet xmlns:sch="http://www.ascc.net/xml/schematron" 
    xmlns:set="http://exslt.org/sets"
    xmlns:str="http://exslt.org/strings"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:date="http://exslt.org/dates-and-times"
    xmlns:dyn="http://exslt.org/dynamic"
    xmlns:iso="http://purl.oclc.org/dsdl/schematron"
    xmlns:math="http://exslt.org/math"
    xmlns:random="http://exslt.org/random"
    xmlns:regexp="http://exslt.org/regular-expressions"
    xmlns:inet="urn:ietf:params:xml:ns:yang:ietf-inet-types"
    xmlns:ipfix="urn:ietf:params:xml:ns:yang:ietf-ipfix-psamp"
    xmlns:yang="urn:ietf:params:xml:ns:yang:ietf-yang-types"
    xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"
    extension-element-prefixes="date dyn exsl math random regexp set str"
    version="1.0">
    <!--Removed in favor of brevity.-->
</xsl:stylesheet>

Here's what xsltproc generates and what I wish java would too:

<?xml version="1.0" standalone="yes"?>
<!--This XSLT was automatically generated from a Schematron schema.-->
<axsl:stylesheet 
    xmlns:date="http://exslt.org/dates-and-times" 
    xmlns:dyn="http://exslt.org/dynamic" 
    xmlns:exsl="http://exslt.org/common" 
    xmlns:math="http://exslt.org/math" 
    xmlns:random="http://exslt.org/random" 
    xmlns:regexp="http://exslt.org/regular-expressions" 
    xmlns:set="http://exslt.org/sets" 
    xmlns:str="http://exslt.org/strings" 
    xmlns:axsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:sch="http://www.ascc.net/xml/schematron" 
    xmlns:iso="http://purl.oclc.org/dsdl/schematron" 
    xmlns:inet="urn:ietf:params:xml:ns:yang:ietf-inet-types" 
    xmlns:ipfix="urn:ietf:params:xml:ns:yang:ietf-ipfix-psamp" 
    xmlns:yang="urn:ietf:params:xml:ns:yang:ietf-yang-types" 
    xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" 
    extension-element-prefixes="date dyn exsl math random regexp set str" 
    version="1.0">
    <!--Removed in favor of brevity.-->
</axsl:stylesheet>

As you can see the Java output is missing an exsl namespace for the corresponding prefix. I realize that the part of iso skeleton which is supposed to process the namespaces is flagged as experimental..I'm willing to apply dirty hacks to get this to work if neccessary (unless I'm asking something embarrassing due not learning schematron in depth). Any ideas?

Edit: as Martin suggested in a comment below I tried using a pure XSLT 1.0 processor. I used xalan (both compiled and interpretive versions) and saxon 6.5.5. Neither of the xalans appends the exsl namespace. Saxon even fails to process the schematron with "cannot add a namespace after attributes have been added" or something similar (line 1534 in the iso skeleton for xslt1). The only processor able to transform a schematron with queryBinding="exslt" for me remains xsltproc. It is also the only non-java processor I tried. After reading a bit of the offending XSL I noticed a comment in which a claim is made that the transformation has been tested with saxon9. So this SHOULD work.

P.S.: apologies for the long post but I think it's vital to give an example of this.

1
I don't have time to debug that in detail but looking at your schema and you mentioning Saxon 9 (which is an XSLT 2.0 processor) I wonder if you can't simply avoid the problem with EXSLT namespaces by using queryBinding="xslt2" in your schema instead of queryBinding="exslt" you have. After all once you use an XSLT/XPath 2.0 implementation of Schematron you shouldn't have any need to use EXSLT extensions.Martin Honnen
I see your point. Though I think I'll try to use Saxon 6.5.5 but I don't think that will work for me in the end either. I just realized that the reason exslt is used is the dyn:evaluate() function for evaluating XPath expressions inside XML instances. And that's probably not even implemented in open source versions of saxon. But still..the problem I described should not appear regardless of the XSLT processor implementation type.predi

1 Answers

4
votes

I hacked my way through this problem by debugging it myself despite my lack of XSLT knowledge. This solution focuses on using Xalan (interpretive version, xalan.jar or to be more precise org.apache.xalan.processor.TransformerFactoryImpl) as a java XSLT processor of choice. The solution requires making changes to the schematron skeleton and also making it non-portable.

After isolating the problematic piece of XSL code from iso_schematron_skeleton_for_xslt1.xsl I found out what is the cause for "http://exslt.org/common" namespace simply disappearing from the stylesheet which was supposed to be generated by the transformation (the skeleton is a meta-stylesheet, which means it's output is also a stylesheet). Both the generating and the generated stylesheet use functions from that EXSLT namespace and specifying it in two xsl:stylesheet elements inside a single XSL file seems to make all java XSLT processors explode. Seriously. If you do this (see example below) the namespace in the generated stylesheet will simply disappear.

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:axsl="http://www.w3.org/1999/XSL/TransformAlias"
 xmlns:exsl="http://exslt.org/common"
 extension-element-prefixes="exsl">
    <xsl:output method="xml" omit-xml-declaration="yes" />
    <xsl:strip-space elements="*"/>
    <xsl:namespace-alias stylesheet-prefix="axsl" result-prefix="xsl"/>

    <xsl:template match="/">
        <xsl:call-template name="disappearance-act" />
    </xsl:template>

    <xsl:template name="disappearance-act">
        <axsl:stylesheet
        xmlns:exsl="http://exslt.org/common"
        extension-element-prefixes="exsl" >
            <xsl:attribute name="version">1.0</xsl:attribute>
        </axsl:stylesheet>
    </xsl:template>
</xsl:stylesheet>

So you'll need to avoid doing this if you want to transform your schematron into an XSL with java. Since the skeleton only uses EXSLT in a single place because of the exsl:node-set function which is used to construct a namespace attribute in the handle-namespace template I avoided this problem by simply moving this template into an external stylesheet and then including it in the skeleton. If the disappearing namespace is only specified for the generated xsl:stylesheet element, the problem simply goes away. As to why this might be happening...I have absolutely no idea. Perhaps someone more proficient in XSLT can answer this question.

Next (no, not done yet) you have to be aware of the fact that there seems to be a bug in Xalan when you use function-available('exsl:node-set')" in a test anywhere (which is exactly what is done in handle-namespace). The test will return false even though this function is quite clearly implemented and supported by Xalan. So don't use this. I resolved it by simply presuming that this function exists and not doing the test in the handle-namespace template (this also makes all the other xsl:choose branches in that template redundant so I simply removed those too). Below you'll find all the changes I made to get the example schema from my question to transform properly. Note that I do not know if there are other problems with the generated stylesheet. This only solves the problem in my question.

Parts of the schematron skeleton xsl which I modified:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:axsl="http://www.w3.org/1999/XSL/TransformAlias"
 xmlns:sch="http://www.ascc.net/xml/schematron"
 xmlns:iso="http://purl.oclc.org/dsdl/schematron" >
     <!--Removed no longer neccessary stuff from root stylesheet-->
    <xsl:output method="xml" omit-xml-declaration="no" standalone="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:namespace-alias stylesheet-prefix="axsl" result-prefix="xsl"/>

     <!--This was added.-->
    <xsl:include href="namespaces.xsl"/>

    <xsl:template match="/">
        <xsl:apply-templates />
    </xsl:template>

    <xsl:template match="iso:schema[@queryBinding='exslt']">
        <axsl:stylesheet
        xmlns:date="http://exslt.org/dates-and-times"
        xmlns:dyn="http://exslt.org/dynamic"
        xmlns:exsl="http://exslt.org/common"
        xmlns:math="http://exslt.org/math"
        xmlns:random="http://exslt.org/random"
        xmlns:regexp="http://exslt.org/regular-expressions"
        xmlns:set="http://exslt.org/sets"
        xmlns:str="http://exslt.org/strings"
        extension-element-prefixes="date dyn exsl math random regexp set str" >

            <xsl:apply-templates select="iso:ns"/>
            <xsl:attribute name="version">1.0</xsl:attribute>
     <!--Irrelevant changes here in order to make this stylesheet runnable.-->
        </axsl:stylesheet>
    </xsl:template>

    <xsl:template match="iso:ns">
        <xsl:call-template name="handle-namespace" />
    </xsl:template>

    <!--handle-namespace template was removed here-->

</xsl:stylesheet>

This are the contents of the include file:

<!--namespaces.xsl-->
<!--The include stylesheet which handles the namespaces from schematron.-->
<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:exsl="http://exslt.org/common"
 extension-element-prefixes="exsl">

    <xsl:template name="handle-namespace">
        <xsl:variable name="ns-dummy-elements">
            <xsl:element name="{@prefix}:dummy" namespace="{@uri}"/>
        </xsl:variable>
        <xsl:variable name="p" select="@prefix"/>
        <xsl:copy-of select="exsl:node-set($ns-dummy-elements)
                                  /*/namespace::*[local-name()=$p]"/>
    </xsl:template>

</xsl:stylesheet>

This only provides a workaround for my problem. I do not consider this a complete answer to my question so I shall not mark it as answered until someone explains why that namespace is disappearing.

Edit: after posting a question in the xalan-j mailing list and reading this - EXSLT - I have come to a conclusion that the disappearing namespace is actually what's supposed to happen. The extension-element-prefixes attribute is used to prevent the extension namespaces from being output in the result tree. Once again I've found out that xsltproc does not conform to XSLT 1.0 specs. This also means that the iso schematron skeleton in bugged. I'd gladly report this to them but their mailing list seems to have succumbed to spam.