好的,我想应用一个XSL样式表来计算以前唯一的“ROLE”节点,并在当前节点之前向@name输出以下输出格式的@name唯一ROLE节点的数量。我浪费了几个小时才能实现一件容易的事情。我试图用几种方法实现它,包括Muenchian方法,if / with variables(不能增加变量),将模板应用到模板等都无济于事。
我有以下XML:
<ROLEACTIONINFO>
<ROLE name="TESTER" />
<ROLE name="PARENT1"/>
<ROLE name="PARENT1"/>
<ROLE name="PARENT1"/>
<ROLE name="PARENT2"/>
<ROLE name="PARENT2"/>
<ROLE name="PARENT3"/>
<ROLE name="PARENT4"/>
<ROLE name="TESTROLE"/>
</ROLEACTIONINFO>
输出示例:
TESTER 1
PARENT1 2
PARENT1 2
PARENT1 2
PARENT2 3
PARENT2 3
PARENT3 4
PARENT4 5
TESTROLE 6
获取唯一先前节点的计数是我的问题。任何帮助将不胜感激
答案 0 :(得分:5)
使用XPath可以很容易地解决这个问题。以下是您要查找的表达式:count((.|preceding-sibling::ROLE)[not(@name = preceding-sibling::ROLE/@name)])
这可以分解为使其更具可读性,正如我在以下XSLT 1.0样式表中所做的那样:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<!-- don't copy whitespace -->
<xsl:template match="text()"/>
<xsl:template match="ROLE">
<xsl:variable name="roles-so-far" select=". | preceding-sibling::ROLE"/>
<!-- Only select the first instance of each ROLE name -->
<xsl:variable name="roles-so-far-unique"
select="$roles-so-far[not(@name = preceding-sibling::ROLE/@name)]"/>
<xsl:apply-templates select="@name"/>
<xsl:text> </xsl:text>
<xsl:value-of select="count($roles-so-far-unique)"/>
<xsl:text>
</xsl:text> <!-- linefeed -->
</xsl:template>
</xsl:stylesheet>
这是使用Muenchian方法的替代实现。首先,声明一个键:
<xsl:key name="roles" match="ROLE" use="@name"/>
然后,用以下内容替换$ roles-so-far-unique的定义:
<!-- Among all the ROLEs having one of the names so far,
select only the first one for each name -->
<xsl:variable name="roles-so-far-unique"
select="../ROLE[@name = $roles-so-far/@name]
[generate-id(.) = generate-id(key('roles',@name)[1])]"/>
这段代码当然更复杂。除非你有一个大数据集要求你使用Muenchian方法加速处理(即使那时我会测试以确保它能为你买任何东西),你也可以坚持使用上面更简单的版本。
最后,在XSLT 2.0中,它更容易。简单地用以下内容替换$ roles-so-far-unique定义:
<!-- Return a list of distinct string values, with duplicates removed -->
<xsl:variable name="roles-so-far-unique"
select="distinct-values($roles-so-far/@name)"/>
我希望这可以帮助您确定在您提到的各种尝试中出错的地方。
答案 1 :(得分:3)
使用<xsl:key>
:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:output method="text" />
<xsl:key name="kRole" match="ROLE" use="@name" />
<xsl:template match="ROLE">
<xsl:value-of select="concat(@name, ' ')" />
<xsl:value-of select="count(
(. | preceding-sibling::ROLE)[
count(. | key('kRole', @name)[1]) = 1
])" />
</xsl:template>
</xsl:stylesheet>
输出符合要求:
TESTER 1
PARENT1 2
PARENT1 2
PARENT1 2
PARENT2 3
PARENT2 3
PARENT3 4
PARENT4 5
TESTROLE 6
<xsl:value-of>
中的XPath表达式的说明:
count( # count the nodes:
(. | preceding-sibling::ROLE) # union of this node and its predecessors
[ # where...
count( # the count of the union of...
. | # this node and
key('kRole', @name)[1] # the first node with the same @name
) = 1 # is 1
]
)
这是Muenchian方法。基于节点集不能包含两次相同节点的事实,如果两个节点的节点是同一节点,则它们的节点数为1。这样我们就只从(. | preceding-sibling::ROLE)
中选择唯一的节点。
如果文档中有多个<ROLEACTIONINFO>
元素,则会缺少父级检查。这也很容易实现:
<xsl:template match="ROLE">
<xsl:variable name="parentId" select="generate-id(..)" />
<xsl:value-of select="count(
(. | preceding-sibling::ROLE)[
count(. | key('kRole', @name)[generate-id(..) = $parentId][1]) = 1
])" />
</xsl:template>
请注意[generate-id(..) = $parentId][1]
!= [1][generate-id(..) = $parentId]
。
链接谓词时,顺序很重要。前者首先检查父节点的相等性,然后从缩减集中获取第一个唯一节点。这就是我们想要的。
后者从集合中获取第一个节点(在整个文档中具有给定名称的所有ROLE节点),获取第一个节点,然后根据父级相等性保留或丢弃它。这是错误的。
答案 2 :(得分:0)
递归通常适用于这样的问题。
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" media-type="text/plain" />
<xsl:template name="count-previous-but-not-with-my-name">
<xsl:param name="nodes" />
<xsl:param name="count" select="0" />
<xsl:choose>
<xsl:when test="count($nodes) = 0">
<xsl:value-of select="$count" />
</xsl:when>
<xsl:otherwise>
<xsl:variable name="last-name" select="$nodes[last()]/@name" />
<xsl:variable name="nodes-before-me-without-my-name" select="$nodes[position() < last() and @name != $last-name]" />
<xsl:call-template name="count-previous-but-not-with-my-name">
<xsl:with-param name="nodes" select="$nodes-before-me-without-my-name" />
<xsl:with-param name="count" select="$count + 1" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="/">
<xsl:for-each select="//ROLEACTIONINFO/ROLE">
<xsl:variable name="role" select="current()" />
<xsl:variable name="my-pos" select="position()" />
<xsl:value-of select="current()/@name" /><xsl:text> </xsl:text>
<xsl:call-template name="count-previous-but-not-with-my-name">
<xsl:with-param name="nodes" select="$role/../ROLE[position() <= $my-pos]" />
</xsl:call-template>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>