代码之家  ›  专栏  ›  技术社区  ›  Jay

XSL:计算以前的唯一同级

  •  5
  • Jay  · 技术社区  · 17 年前

    好的,我想应用一个XSL样式表来计算之前唯一的“角色”节点,并输出以下输出格式@name,即当前节点之前唯一角色节点的数量。我已经浪费了好几个小时在一件应该很容易实现的事情上。我尝试了几种方法来实现这一点,包括Muenchian方法、if/with变量(不能增加变量)、将模板应用于模板等,但都没有效果。

    我有以下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 回复  |  直到 17 年前
        1
  •  5
  •   Evan Lenz    17 年前

    使用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的定义替换为如下内容:

    <!-- 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方法加快处理速度(即使这样,我也会进行测试以确保它能为您带来任何好处),否则您最好还是使用上面更简单的版本。

    最后,在XSLT2.0中,这要容易得多。简单地将$roles迄今为止的唯一定义替换为以下内容:

    <!-- Return a list of distinct string values, with duplicates removed -->
    <xsl:variable name="roles-so-far-unique"
                  select="distinct-values($roles-so-far/@name)"/>
    

    我希望这有助于你确定在你提到的各种尝试中你错在哪里。

        2
  •  3
  •   Tomalak    17 年前

    <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
    

    中XPath表达式的说明 <xsl:value-of>

    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
    ]
    )
    

    这就是明钦方法。基于一个节点集不能包含同一节点两次的事实,如果两个节点的并集是同一节点,则它们的节点计数为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] .

    链接谓词时,顺序很重要。前者首先检查父节点是否相等,然后从缩减集中获取第一个唯一节点。这就是我们想要的。

    后者从集合中获取第一个节点(整个文档中具有给定名称的所有角色节点),获取第一个节点,然后根据父级相等性保留或放弃它。这是错误的。

        3
  •  0
  •   Steef    17 年前

    递归通常能很好地处理这样的问题。

    <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>