使用输入参数时xml-to-json number-formatter函数抛出类型转换异常的解决方法
问题
编写了如下XSLT样式表,定义test:format-json-number函数作为fn:xml-to-json的number-formatter参数。当函数引用输入参数$input时,转换会抛出ClassCastException,提示net.sf.saxon.value.SingletonClosure无法转换为net.sf.saxon.value.StringValue;但如果函数返回xsl:text字面量或引用字面量变量则运行正常。请问如何定义可引用输入参数的number-formatter函数以避免该异常?
样式表代码
<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>
异常堆栈信息
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
解决方案
这个问题的根源在于Saxon处理number-formatter函数时,对参数的延迟求值导致类型转换异常。解决方法是将XSLT函数改写为纯XPath函数(使用XPath表达式实现,而非xsl:value-of这类指令),确保参数能被正确求值为字符串类型。
修改后的样式表代码
<xsl:stylesheet version="3.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"> <!-- 使用纯XPath表达式实现函数,避免指令导致的延迟求值 --> <xsl:function name="test:format-json-number" as="xs:string"> <xsl:param name="input" as="xs:string"/> <xsl:sequence 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>
关键修改点
- 将原函数中的
<xsl:value-of select="$input"/>替换为<xsl:sequence select="$input"/>:xsl:sequence会直接返回参数的实际值,而非创建延迟求值的闭包,确保Saxon能正确识别参数类型。 - 将样式表版本从
1.0升级到3.0:fn:xml-to-json和number-formatter参数是XSLT 3.0的特性,原代码中使用1.0版本属于不规范用法,升级后能更好地兼容Saxon的函数处理逻辑。
复杂逻辑示例
如果需要在格式化函数中添加数字格式化、字符串替换等复杂逻辑,同样要使用XPath表达式实现,避免使用xsl:value-of、xsl:choose等指令。例如,实现保留两位小数的格式化:
<xsl:function name="test:format-json-number" as="xs:string"> <xsl:param name="input" as="xs:string"/> <xsl:sequence select="format-number(xs:decimal($input), '0.00')"/> </xsl:function>
内容的提问来源于Stack Exchange,提问作者Apion
相关产品推荐
相关产品推荐

