XSL:计算以前唯一的兄弟姐妹

时间:2009-06-02 20:17:32

标签: xml xslt xpath

好的,我想应用一个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

获取唯一先前节点的计数是我的问题。任何帮助将不胜感激

3 个答案:

答案 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>&#xA;</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() &lt; 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() &lt;= $my-pos]" />
            </xsl:call-template>
            <xsl:text>&#10;</xsl:text>
        </xsl:for-each>
    </xsl:template>

</xsl:stylesheet>