使用XSLT在标记值上添加索引:`position()`返回预期值的两倍

时间:2017-06-16 17:46:03

标签: xml xslt xslt-1.0

我有一个输入XML:

<root>
<child>somevalue</child>
<child>othervalue</child>
</root>

需要这个输出:

<root>
<child>item1_somevalue</child>
<child>item2_othervalue</child>
</root>

我正在使用此模板:

<xsl:template match="*[local-name()='root' and namespace-uri()='']/*[local-name()='child' and namespace-uri()='']">
<xsl:element name="{local-name()}">
<xsl:text>item</xsl:text>
<xsl:value-of select="position()"/>
<xsl:text>_</xsl:text>
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>

得到这个结果:

<child>item2_somevalue</child>
<child>item4_othervalue</child>

为什么position()返回预期值的两倍?

我现在不担心根标签。

必须使用XSLT 1.0。

2 个答案:

答案 0 :(得分:3)

解释发生了什么

position()函数有时会返回意外结果。乍一看,听起来应该返回context元素相对于其父容器的位置。因此,对于我们的示例输入XML:

<root>
    <child>somevalue</child>
    <child>othervalue</child>
</root>

...我们希望看到第一个position()元素的child1,第二个position()child元素将是2

但是当我们针对该代码运行示例XSL时,我们得到以下输出:

<child>item2_somevalue</child>
<child>item4_othervalue</child>

关键是我们需要特别注意措辞。 position()元素不仅计算元素,它计算节点 - 并且包含文本节点。因此,在上面的示例输入XML中,第一个child元素实际上是root下的第二个节点,因为它前面有一个文本节点(换行符和领先的空白)。并且,出于同样的原因,第二个child元素实际上是root下的第四个节点。所以position()正在准确回归。

修复行为

有几种方法可以解决这个问题。一个是明确计算child个元素,就像jpmo22的帖子那样。另一种更灵活的方法是告诉XSL处理器忽略仅空白文本节点。我们可以通过在XSLT代码中添加顶级元素来实现:

<xsl:strip-space elements="*"/>

我们可以根据需要自定义元素列表,方法是将*替换为元素名称列表。

如果我们只是在示例XSL代码中的strip-space之前添加template指令,我们就可以得到预期的结果:

<child>item1_somevalue</child>
<child>item2_othervalue</child>

其他考虑因素

  • match语句中的XPath是错综复杂的。

    <xsl:template match="*[local-name()='root' and namespace-uri()='']/*[local-name()='child' and namespace-uri()='']">
    

    如果元素名称没有前缀,local-namename会返回相同的结果。我们的示例输入XML没有前缀,因此没有理由使用local-name。实际上,我们可以简单地使用所需的元素名称而不是*。此外,我们的示例输入XML根本没有名称空间声明,因此没有理由使用namespace-uri

    建议的简化:

    <xsl:template match="root/child">
    
  • 使用element也有点费解。

    <xsl:element name="{local-name()}">
    

    我们已经知道我们正在使用child个元素,因为这是我们在此模板中match编辑的内容。我们可以更简单地在文字元素声明中使用元素名称,如下所示:

    <child>
    

    如果我们想要对代码进行泛化,我们也可以复制上下文元素:

    <xsl:copy>
    

答案 1 :(得分:0)

我找到了解决方案。我将position()更改为count(preceding::*[local-name()='child']) + 1

XSLT:

<xsl:template match="*[local-name()='child']">
<xsl:element name="{local-name()}">
<xsl:text>item</xsl:text>
<xsl:value-of select="count(preceding::*[local-name()='child']) + 1"/>
<xsl:text>_</xsl:text>
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>

给出正确的结果:

<child>item1_somevalue</child>
<child>item2_othervalue</child>