1

I have the following stylesheet

<xsl:stylesheet
    version="1.0"
    xmlns:test="test"
    xmlns:fn="http://www.w3.org/2005/xpath-functions"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    exclude-result-prefixes="#all">

    <xsl:function name="test:format-json-number" as="xs:string">
        <xsl:param name="input" as="xs:string"/>
        <xsl:value-of select="$input"/>
    </xsl:function>
    
    <xsl:template match="test:message">
        <xsl:variable name="result">
            <fn:map>
                <fn:number key="studentId">
                        <xsl:text>1</xsl:text>
                </fn:number>
            </fn:map>
        </xsl:variable>
        <xsl:value-of select="fn:xml-to-json($result, map{'number-formatter': test:format-json-number#1})"/>
    </xsl:template>
</xsl:stylesheet>

When executing this stylesheet I get the following stack trace:

java.lang.ClassCastException: class net.sf.saxon.value.SingletonClosure cannot be cast to class net.sf.saxon.value.StringValue (net.sf.saxon.value.SingletonClosure and net.sf.saxon.value.StringValue are in unnamed module of loader 'app')
        at net.sf.saxon.ma.json.JsonReceiver.endElement(JsonReceiver.java:287)
        at net.sf.saxon.event.DocumentValidator.endElement(DocumentValidator.java:79)
        at net.sf.saxon.tree.tiny.TinyElementImpl.copy(TinyElementImpl.java:477)
        at net.sf.saxon.tree.tiny.TinyDocumentImpl.copy(TinyDocumentImpl.java:303)
        at net.sf.saxon.ma.json.XMLToJsonFn.convertToJson(XMLToJsonFn.java:118)
        at net.sf.saxon.ma.json.XMLToJsonFn.process(XMLToJsonFn.java:100)
        at net.sf.saxon.expr.SystemFunctionCall$SystemFunctionCallElaborator.lambda$elaborateForPush$8(SystemFunctionCall.java:692)
        at net.sf.saxon.expr.instruct.ValueOf$ValueOfElaborator.lambda$elaborateForPush$1(ValueOf.java:425)
        at net.sf.saxon.expr.instruct.TemplateRule.applyLeavingTail(TemplateRule.java:376)
        at net.sf.saxon.trans.Mode.handleRuleNotNull(Mode.java:587)
        at net.sf.saxon.trans.Mode.applyTemplates(Mode.java:521)
        at net.sf.saxon.trans.rules.TextOnlyCopyRuleSet.process(TextOnlyCopyRuleSet.java:72)
        at net.sf.saxon.trans.Mode.applyTemplates(Mode.java:518)
        at net.sf.saxon.trans.XsltController.applyTemplates(XsltController.java:684)
        at net.sf.saxon.s9api.AbstractXsltTransformer.applyTemplatesToSource(AbstractXsltTransformer.java:431)
        at net.sf.saxon.s9api.Xslt30Transformer.applyTemplates(Xslt30Transformer.java:307)
        at net.sf.saxon.Transform.processFile(Transform.java:1405)
        at net.sf.saxon.Transform.doTransform(Transform.java:894)
        at net.sf.saxon.Transform.main(Transform.java:85)
java.lang.RuntimeException: Internal error evaluating template rule  at line 14 in module file:test.xslt
        at net.sf.saxon.expr.instruct.TemplateRule.applyLeavingTail(TemplateRule.java:386)
        at net.sf.saxon.trans.Mode.handleRuleNotNull(Mode.java:587)
        at net.sf.saxon.trans.Mode.applyTemplates(Mode.java:521)
        at net.sf.saxon.trans.rules.TextOnlyCopyRuleSet.process(TextOnlyCopyRuleSet.java:72)
        at net.sf.saxon.trans.Mode.applyTemplates(Mode.java:518)
        at net.sf.saxon.trans.XsltController.applyTemplates(XsltController.java:684)
        at net.sf.saxon.s9api.AbstractXsltTransformer.applyTemplatesToSource(AbstractXsltTransformer.java:431)
        at net.sf.saxon.s9api.Xslt30Transformer.applyTemplates(Xslt30Transformer.java:307)
        at net.sf.saxon.Transform.processFile(Transform.java:1405)
        at net.sf.saxon.Transform.doTransform(Transform.java:894)
        at net.sf.saxon.Transform.main(Transform.java:85)
Caused by: java.lang.ClassCastException: class net.sf.saxon.value.SingletonClosure cannot be cast to class net.sf.saxon.value.StringValue (net.sf.saxon.value.SingletonClosure and net.sf.saxon.value.StringValue are in unnamed module of loader 'app')
        at net.sf.saxon.ma.json.JsonReceiver.endElement(JsonReceiver.java:287)
        at net.sf.saxon.event.DocumentValidator.endElement(DocumentValidator.java:79)
        at net.sf.saxon.tree.tiny.TinyElementImpl.copy(TinyElementImpl.java:477)
        at net.sf.saxon.tree.tiny.TinyDocumentImpl.copy(TinyDocumentImpl.java:303)
        at net.sf.saxon.ma.json.XMLToJsonFn.convertToJson(XMLToJsonFn.java:118)
        at net.sf.saxon.ma.json.XMLToJsonFn.process(XMLToJsonFn.java:100)
        at net.sf.saxon.expr.SystemFunctionCall$SystemFunctionCallElaborator.lambda$elaborateForPush$8(SystemFunctionCall.java:692)
        at net.sf.saxon.expr.instruct.ValueOf$ValueOfElaborator.lambda$elaborateForPush$1(ValueOf.java:425)
        at net.sf.saxon.expr.instruct.TemplateRule.applyLeavingTail(TemplateRule.java:376)
        ... 10 more
Fatal error during transformation: java.lang.RuntimeException: Internal error evaluating template rule at line 14 in module file:test.xslt

It seems that however this XSLT-defined number-formatter function is being called is not actually evaluating the xsl:value-of call, it is leaving it as a lazy-evaluated SingletonClosure, and instead of evaluating the expression, it simply tries to cast, and fails. This seems to happen when anything in the test:format-json-number function references the input param. Returning a xsl:text literal works fine, and defining a new variable, assigning a literal, and returning a xsl:value-of of the new variable works fine as well.

Is there a way to define a number-formatter function entirely in XSLT that references the input parameter that doesn't result in an exception?

3

2 Answers 2

1

I would try

<xsl:function name="test:format-json-number" as="xs:string">
    <xsl:param name="input" as="xs:string"/>
    <xsl:sequence select="$input"/>
</xsl:function>
Sign up to request clarification or add additional context in comments.

Comments

1

There's a Saxon bug here, which @MartinHonnen has kindly logged at https://saxonica.plan.io/issues/6799 . It's essentially a problem with function coercion (conversion of the supplied parameters of a function to the required type, and conversion of the returned value to the expected type). Conversion is needed here because your function is returning a text node which has to be converted to the declared type of xs:string, but the xml-to-json code that's calling your function isn't expecting to have to do the conversion. Changing the function to use xsl:sequence rather than xsl:value-of avoids the need for any conversion, and thereby avoids the problem.

Note also that the number-formatter is an experimental XPath 4.0 addition to the xml-to-json() function and it has been superseded in the latest draft of the XPath 4.0 proposal. It won't be there in Saxon 13. For discussion of the change, see https://github.com/qt4cg/qtspecs/issues/1445

If you are using features from later XSLT and XPath specifications, then I would recommend setting the version attribute on the stylesheet to something higher than "1.0". Some of the new features (especially experimental features) may not interact well with the use of 1.0 compatibility mode - certainly you're likely to be using combinations of functionality that aren't well tested.

There's also an oversight in the Saxon implementation of the feature: our policy is that experimental 4.0 features should only be available in Saxon-EE for the time being, until the specification become more stable. We slipped up by making this work in Saxon-HE. The intent of this policy is to make sure that the only people using these experimental features (and getting bitten when they change) are people who make a very deliberate and considered decision to take the risk.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.