XSL: подсчет предыдущих уникальных братьев и сестер - PullRequest
5 голосов
/ 03 июня 2009

ОК, я хочу применить таблицу стилей XSL, которая подсчитывает предыдущие уникальные узлы "ROLE" и выдает следующий выходной формат @name числа уникальных узлов ROLE до текущих узлов. Я потратил несколько часов на то, что должно быть легко реализовать. Я пытался реализовать это несколькими способами, включая метод Мюнхена, если / с переменными (невозможно увеличить переменную), применять шаблоны к шаблонам и т. Д. Безрезультатно.

У меня есть следующий 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 ]

5 голосов
/ 03 июня 2009

Это можно легко решить с помощью 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>

Вот альтернативная реализация, использующая метод Мюнхена. Сначала объявите ключ:

<xsl:key name="roles" match="ROLE" use="@name"/>

Затем замените определение $ роли, пока что уникальное на что-то вроде этого:

<!-- 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])]"/>

Этот код, конечно, более сложный. Если у вас нет большого набора данных, требующего ускорения обработки с использованием метода Мюнхена (даже тогда я бы протестировал, чтобы убедиться, что он что-то покупает), вы могли бы также придерживаться более простой версии выше.

Наконец, в XSLT 2.0 это намного проще. Простое замените уникальное определение $ role следующим:

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

Надеюсь, это помогло вам определить, где вы ошиблись в различных упомянутых вами попытках.

3 голосов
/ 03 июня 2009

Это легко решается с помощью <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].

Порядок важен при объединении предикатов. Первый сначала проверяет равенство родительских узлов, а затем берет первый уникальный узел из сокращенного набора. Это то, что мы хотим.

Последний берет первый узел из набора (все узлы ROLE с заданным именем по всему документу), берет первый и затем сохраняет или отбрасывает его на основе родительского равенства. Это неправильно.

0 голосов
/ 03 июня 2009

Рекурсия обычно работает довольно хорошо с такими проблемами.

<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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...