如何获取XML指定子元素对应父元素索引并遍历后续元素?
XSLT:通过子元素文本定位父元素并获取后续动态元素内容
问题背景
给定如下XML结构:
<start> <Item> <ItemName>x</ItemName> <ItemValue>x val</ItemValue> </Item> <Item> <ItemName>y</ItemName> <ItemValue>y val</ItemValue> </Item> <Item> <ItemName>z</ItemName> <ItemValue>z val</ItemValue> </Item> <Item> <ItemName>y</ItemName> <ItemValue>y val</ItemValue> </Item> <Item> <ItemName>Head</ItemName> <ItemValue>Head val</ItemValue> </Item> <Item> <ItemName>Head1</ItemName> <ItemValue>Head1 Val</ItemValue> </Item> <Item> <ItemName>Head2</ItemName> <ItemValue>Head2 Val</ItemValue> </Item> </start>
需求:
- 定位到
/start/Item[ItemName='Head']元素 - 获取该元素的位置(即它之前的
/start/Item元素数量) - 利用这个计数,获取后续动态命名的
Head1、Head2等元素的内容
尝试过程
- 最初通过
<xsl:if test="/start/Item[ItemName='Head'] "/>定位目标元素,但不知如何完成后续操作 - 尝试以下代码无法打印计数:
<fo:block> <xsl:template match="start/Item[ItemName='Head']"> Count <xsl:value-of select="count(preceding-sibling::Item)"/> </xsl:template> </fo:block>
- 修改后代码已生效,但希望知道是否有更简洁的实现方式,以及代码是否存在冗余:
</xsl:template> <xsl:variable name="var"> <xsl:apply-templates select="start/Item[ItemName='Head']"/> </xsl:variable> <fo:block margin-top="9.50mm"> <xsl:value-of select="start/Item[number($var)+1]/ItemValue"/> </fo:block> </xsl:template> <xsl:template match="start/ItemItemName='Head']"> <xsl:value-of select="count(preceding-sibling::Item)"/> </xsl:template>
解答
现有代码的问题
首先注意到模板匹配规则存在笔误:start/ItemItemName='Head']缺少了[,正确写法应为start/Item[ItemName='Head']。
更简洁的实现方式
不需要通过模板和变量间接获取计数,直接用XPath即可完成所有需求,大幅简化代码:
1. 获取Head元素的位置计数
直接用以下XPath计算Head元素之前的Item数量:
<xsl:variable name="head-position" select="count(/start/Item[ItemName='Head']/preceding-sibling::Item)"/>
这个变量直接存储了Head元素之前的Item总数,无需额外模板。
2. 获取后续动态元素内容
如果要基于计数获取第N个后续元素(比如Head之后第一个元素):
<fo:block margin-top="9.50mm"> <!-- 获取Head之后第一个元素的ItemValue --> <xsl:value-of select="/start/Item[position() = $head-position + 2]/ItemValue"/> </fo:block>
注:XPath的position()从1开始计数,Head元素的位置是$head-position + 1,所以它之后第一个元素的位置是$head-position + 2
如果要获取Head之后所有动态元素(比如Head1、Head2等),可以直接用following-sibling轴,无需依赖计数:
<xsl:for-each select="/start/Item[ItemName='Head']/following-sibling::Item"> <fo:block margin-top="9.50mm"> <xsl:value-of select="ItemValue"/> </fo:block> </xsl:for-each>
冗余代码分析
你当前的代码存在两处冗余:
- 通过模板+
xsl:apply-templates来获取计数,完全可以直接用XPath表达式赋值给变量,无需模板调用 - 变量
var存储的是文本形式的计数,还需要用number()转换,直接用数值型变量更高效
完整简化示例
<xsl:template match="/"> <!-- 直接获取Head元素的前置Item计数 --> <xsl:variable name="head-preceding-count" select="count(/start/Item[ItemName='Head']/preceding-sibling::Item)"/> <!-- 获取Head之后第一个元素的内容 --> <fo:block margin-top="9.50mm"> <xsl:value-of select="/start/Item[$head-preceding-count + 2]/ItemValue"/> </fo:block> <!-- 遍历获取Head之后所有元素的内容 --> <xsl:for-each select="/start/Item[ItemName='Head']/following-sibling::Item"> <fo:block margin-top="9.50mm"> <xsl:value-of select="ItemValue"/> </fo:block> </xsl:for-each> </xsl:template>
内容的提问来源于stack exchange,提问作者Rito Sarkar
相关产品推荐
相关产品推荐

