我正在使用XSLT 1.0输入 xml 文档:
<Table>
<Numbers>10,100,1000</Numbers>
<Values>
<Value>1</Value>
<Value>2</Value>
<Value>3</Value>
</Values>
</Table>
预期产出:
<Table>
<Results>
<Values>
<Value>1</Value>
<Number>10</Number>
</Values>
<Values>
<Value>2</Value>
<Number>100</Number>
</Values>
<Values>
<Value>3</Value>
<Number>1000</Number>
</Values>
</Results>
</Table>
我已经在 XSLT 1.0 中实现了标记化函数的通用解决方案,描述如下:http://www.heber.it/?p=1088
当前实现:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:template match="/">
<table name="Results">
<xsl:variable name="Numbers">
<xsl:value-of select="Table/Numbers"/>
</xsl:variable>
<Results>
<xsl:for-each select="Table/Values/Value">
<Values>
<xsl:variable name="Value" select="."/>
<xsl:variable name="n" select="count(preceding-sibling::*)+1."/>
<Value>
<xsl:value-of select="$Value"/>
</Value>
<xsl:call-template name="tokenizeString">
<xsl:with-param name="list" select="$Numbers"/>
<xsl:with-param name="delimiter" select="','"/>
</xsl:call-template>
</Values>
</xsl:for-each>
</Results>
</table>
</xsl:template>
<xsl:template name="tokenizeString">
<!--passed template parameter -->
<xsl:param name="list"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<!-- get everything in front of the first delimiter -->
<Number>
<xsl:value-of select="substring-before($list,$delimiter)"/>
</Number>
<xsl:call-template name="tokenizeString">
<!-- store anything left in another variable -->
<xsl:with-param name="list" select="substring-after($list,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<Number>
<xsl:value-of select="$list"/>
</Number>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
电流输出:
<table xmlns:fo="http://www.w3.org/1999/XSL/Format" name="Results">
<Results>
<Values>
<Value>1</Value>
<Number>10</Number>
<Number>100</Number>
<Number>1000</Number>
</Values>
<Values>
<Value>2</Value>
<Number>10</Number>
<Number>100</Number>
<Number>1000</Number>
</Values>
<Values>
<Value>3</Value>
<Number>10</Number>
<Number>100</Number>
<Number>1000</Number>
</Values>
</Results>
</table>
但是输出不正确。我无法得到,在应用标记化模板后,我如何以正确的顺序返回结果,但不是每个值节点的所有结果。
很清楚如何使用 XSLT 2.0 实现相同的内容,因为我可以执行以下操作:
<xsl:variable name"Foo" select="tokenize($string,$delimter)[$n]">
更新:使用节点集的解决方案对我来说很好,可以使用 XSLT1 表示标记化函数。但我得到了另一种情况,这是初始要求的补充。这种情况是只有一个数字对应于一组值:
<Table>
<Foo>
<Numbers>10,100,1000</Numbers>
<Values>
<Value>1</Value>
<Value>2</Value>
<Value>3</Value>
</Values>
</Foo>
<Foo>
<Numbers>10</Numbers>
<Values>
<Value>4</Value>
<Value>5</Value>
<Value>6</Value>
</Values>
</Foo>
</Table>
预期产出:
<Table>
<Results>
<Values>
<Value>1</Value>
<Number>10</Number>
</Values>
<Values>
<Value>2</Value>
<Number>100</Number>
</Values>
<Values>
<Value>3</Value>
<Number>1000</Number>
</Values>
</Results>
<Results>
<Values>
<Value>4</Value>
<Number>10</Number>
</Values>
<Values>
<Value>5</Value>
<Number>10</Number>
</Values>
<Values>
<Value>6</Value>
<Number>10</Number>
</Values>
</Results>
</Table>
我的愿景是为 michael-hor257k 添加以下条件:
<Number>
<xsl:choose>
<xsl:when test="$numbers-set[$i] != ''">
<xsl:value-of select="$numbers-set[$i]"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$numbers-set[1]"/>
</xsl:otherwise>
</xsl:choose>
</Number>
这看起来是否合理,或者我可以避免硬编码并将这种情况放在模板中?
试试这个方式:
XSLT 1.0
<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:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/Table">
<xsl:variable name="Numbers">
<xsl:call-template name="tokenizeString">
<xsl:with-param name="list" select="Numbers"/>
<xsl:with-param name="delimiter" select="','"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="numbers-set" select="exsl:node-set($Numbers)/Number" />
<table name="Results">
<Results>
<xsl:for-each select="Values/Value">
<xsl:variable name="i" select="position()" />
<Values>
<Value>
<xsl:value-of select="."/>
</Value>
<Number>
<xsl:value-of select="$numbers-set[$i]"/>
</Number>
</Values>
</xsl:for-each>
</Results>
</table>
</xsl:template>
<xsl:template name="tokenizeString">
<xsl:param name="list"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<Number>
<xsl:value-of select="substring-before($list,$delimiter)"/>
</Number>
<xsl:call-template name="tokenizeString">
<xsl:with-param name="list" select="substring-after($list,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<Number>
<xsl:value-of select="$list"/>
</Number>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
或者更短一点:
<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:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/Table">
<xsl:variable name="numbers">
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="Numbers"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="numbers-set" select="exsl:node-set($numbers)/Number" />
<xsl:copy>
<Results>
<xsl:for-each select="Values/Value">
<xsl:variable name="i" select="position()" />
<Values>
<xsl:copy-of select="."/>
<xsl:copy-of select="$numbers-set[$i]"/>
</Values>
</xsl:for-each>
</Results>
</xsl:copy>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="text"/>
<xsl:param name="delimiter" select="','"/>
<xsl:variable name="token" select="substring-before(concat($text, $delimiter), $delimiter)" />
<xsl:if test="$token">
<Number>
<xsl:value-of select="$token"/>
</Number>
</xsl:if>
<xsl:if test="contains($text, $delimiter)">
<!-- recursive call -->
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after($text, $delimiter)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
FWIW,这是一个不需要node-set()
扩展函数的纯 XSLT 1.0 解决方案:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" indent="yes"/>
<xsl:template match="/Table">
<xsl:copy>
<Results>
<xsl:call-template name="process">
<xsl:with-param name="values" select="Values/Value"/>
<xsl:with-param name="numbers" select="Numbers"/>
</xsl:call-template>
</Results>
</xsl:copy>
</xsl:template>
<xsl:template name="process">
<xsl:param name="values" select="dummy-node"/>
<xsl:param name="numbers"/>
<xsl:param name="i" select="1"/>
<!-- output -->
<Values>
<xsl:copy-of select="$values[1]"/>
<Number>
<xsl:value-of select="substring-before(concat($numbers, ','), ',')"/>
</Number>
</Values>
<!-- recursive call -->
<xsl:if test="count($values) > 1">
<xsl:call-template name="process">
<xsl:with-param name="values" select="$values[position() > 1]"/>
<xsl:with-param name="numbers" select="substring-after($numbers, ',')"/>
<xsl:with-param name="i" select="$i + 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
通常,主要问题是 XSLT 1.0 中的模板返回结果树片段,要对其应用 XPath,您首先需要将其转换为节点集,这通常是调用 XSLT 1.0 处理器提供的exsl:node-set($result-tree-fragment)
或类似扩展函数来完成的。
所以假设你有例如
<xsl:variable name="tokens-rtf">
<xsl:call-template name="tokenize">...</xsl:call-template>
</xsl:variable>
模板返回的位置,例如
<Values>
<Value>1</Value>
<Number>10</Number>
</Values>
<Values>
<Value>2</Value>
<Number>100</Number>
</Values>
<Values>
<Value>3</Value>
<Number>1000</Number>
</Values>
你可以做
<xsl:variable name="tokens" select="exsl:node-set($tokens-rtf)/Values" xmlns:exsl="http://exslt.org/common"/>
现在你有一个节点集,你可以应用任何 XPath,比如你想要做的位置谓词,例如 <xsl:variable name"Foo" select="$tokens[$n]"/>
.
此解决方案是迄今为止唯一不使用扩展函数的解决方案,并且不假设将按任何预定义的顺序检索数字:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="Value">
<Values>
<xsl:copy-of select="."/>
<Number>
<xsl:apply-templates select="/*/Numbers" mode="call">
<xsl:with-param name="pPos" select="."/>
</xsl:apply-templates>
</Number>
</Values>
</xsl:template>
<xsl:template match="Numbers" name="getNth" mode="call">
<xsl:param name="pNums" select="concat(., ',')"/>
<xsl:param name="pPos"/>
<xsl:param name="pResult"/>
<xsl:if test="not($pPos > 0)">
<xsl:value-of select="$pResult"/>
</xsl:if>
<xsl:if test="$pNums and $pPos > 0">
<xsl:call-template name="getNth">
<xsl:with-param name="pNums" select="substring-after($pNums, ',')"/>
<xsl:with-param name="pResult" select="substring-before($pNums, ',')"/>
<xsl:with-param name="pPos" select="$pPos -1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="/*">
<xsl:copy>
<Results><xsl:apply-templates/></Results>
</xsl:copy>
</xsl:template>
<xsl:template match="text()"/>
</xsl:stylesheet>
在提供的 XML 文档上应用此转换时:
<Table>
<Numbers>10,100,1000</Numbers>
<Values>
<Value>1</Value>
<Value>2</Value>
<Value>3</Value>
</Values>
</Table>
生成所需的正确结果:
<Table>
<Results>
<Values>
<Value>1</Value>
<Number>10</Number>
</Values>
<Values>
<Value>2</Value>
<Number>100</Number>
</Values>
<Values>
<Value>3</Value>
<Number>1000</Number>
</Values>
</Results>
</Table>
当对此 XML 文档应用相同的转换时(请注意值的更改顺序(:
<Table>
<Numbers>10,100,1000</Numbers>
<Values>
<Value>3</Value>
<Value>1</Value>
<Value>2</Value>
</Values>
</Table>
再次正确生成此 XML 文档的结果:
<Table>
<Results>
<Values>
<Value>3</Value>
<Number>1000</Number>
</Values>
<Values>
<Value>1</Value>
<Number>10</Number>
</Values>
<Values>
<Value>2</Value>
<Number>100</Number>
</Values>
</Results>
</Table>