2009-06-02 2 views
5

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

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

Получение количества уникальных узлов предшествующих моя проблема. Любая помощь будет оценена

+2

Добро пожаловать в Переполнение стека. Хороший первый вопрос! :) Я видел много людей, которые продолжают испортить формат до тех пор, пока не достигнут 1000-летней репутации, хорошо видеть, кто с самого начала справляется с этим. :) – Tomalak

+0

Спасибо за помощь, приведенные ниже примеры сделали трюк! – Jay

ответ

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

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

<!-- 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 это намного проще. Просто замените $ ролей так далеко уникальным определение со следующим:

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

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

+0

+1 от меня. Выражение Muenchian XPath должно быть «$ role-so-far [generate-id (.) = Generate-id (key (« role », @ name) [1])]». Что вы пытаетесь сделать с помощью «../ROLE[@name = $ role-so-far/@ name]»? – Tomalak

+0

Набор узлов, подлежащий фильтрации, является только подмножеством узлов, индексированных по ключу xsl:. Ваше упрощение работает из-за того, как определяется $ role-so-far. Но если я изменил определение $ role-so-far (например, чтобы он перечислял все @name * после *, а не раньше), это было бы неправильно. Первый проиндексированный узел для данного значения не обязательно будет находиться в подмножестве. Тем не менее, это только одна локальная переменная, следующая за другой, поэтому я думаю, что такая связь прекрасна. Я мог бы подумать иначе, если бы он был определен в другом месте (глобально). Я одобряю ваше упрощение, но я оставлю это, чтобы эти комментарии имели смысл. –

+0

Обычно неважно, реализуете ли вы Muenchian Method с помощью [1] или [last()]. * * Намерение * состоит в том, чтобы получить один результат для каждого значения, а не для получения первого. Мне не понравилось, что [1] внезапно и молчаливо преднамеренно; Я хотел, чтобы он оставался частью реализации. :-) –

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

Объяснение выражения 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 
] 
) 

Это 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 с заданным именем во всем документе), принимает первый, а затем сохраняет или отбрасывает его на основе родительского равенства. Это не верно.

+0

Хороший ответ, хотя вы, похоже, не знаете, предпочитаете ли вы метод generate-id() или count (. | $ Ns) = 1 для определения идентификатора узла, используя оба в одном выражении. :-) Я лично предпочитаю сгенерировать-id(), поскольку он более описателен, оставив count (. | $ Ns), когда он незаменим (для получения пересечения двух наборов узлов), то есть count (. | $ Ns) = count ($ ns) –

+0

Мне нравятся оба подхода, хотя обычно я использую метод generate-id() по тем же причинам, что и вы. Я хотел опубликовать альтернативу вашему решению, поэтому я пошел с count() вместе с объяснением здесь. :-) Для «родительской проверки» я снова выбрал generate-id(), потому что это просто более выразительно ... Что-то вроде «count (. | Key ('kRole', @name) [count (.. | $ parent) = 1] [1]) = 1 "довольно беспорядок. – Tomalak

+0

Итак, позвольте мне сказать спасибо всем за объяснения, я многому научился. Тем не менее, я более чем симулировал свой пример, и я все еще немного смущен. Я хотел бы опубликовать folowup с более подробным примером, если это будет опубликовано как новый вопрос (так как был задан исходный вопрос, на который я спросил) или каким-то образом связал этот оригинальный вопрос? – Jay