Ключ XSLT возвращает значение только один раз

Я думаю, что мне не хватает чего-то очевидного здесь, но здесь идет. У меня есть приведенный ниже xml, и мне нужно сгруппировать узлы KEY соответствующих экземпляров вместе. Это определяется атрибутом соответствия и может содержать более одного номера элемента. Может быть любое количество узлов ITEM и любое количество узлов KEY. Кроме того, нет ограничений на глубину узлов ITEM. И совпадающие экземпляры не обязательно должны находиться под одним и тем же родителем. Я также ограничен XSLT 1.0 и парсером Microsoft.

<?xml version="1.0" encoding="utf-8" ?>
<ITEM number='1'>
  <ITEM number='2'>
    <ITEM number='3' match='5,11'>
      <KEY name='key1' value='x' />
      <KEY name='key2' value='y' />
      <KEY name='key3' value='z' />
      <ITEM number ='4' />
    </ITEM>
    <ITEM number='5' match='3,11'>
      <KEY name='key1' value='x' />
      <KEY name='key2' value='y' />
      <KEY name='key3' value='z' />
    </ITEM>
    <ITEM number='6' match='10'>
      <KEY name='key1' value='x' />
      <KEY name='key2' value='y' />
      <KEY name='key4' value='a' />
    </ITEM>
    <ITEM number='7' />
    <ITEM number='8'>
      <KEY name='key1' value='x' />
    </ITEM>
  </ITEM>
  <ITEM number='9'>
    <ITEM number='10' match='6'>
      <KEY name='key1' value='x' />
      <KEY name='key3' value='z' />
      <KEY name='key5' value='b' />
    </ITEM>
  </ITEM>
    <ITEM number='11' match='3,5'>
      <KEY name='key2' value='y' />
      <KEY name='key3' value='z' />
    </ITEM>
</ITEM>

Мой ожидаемый результат будет выглядеть примерно так...

<?xml version="1.0" encoding="utf-8" ?>
<Result>
  <Group number="1" />
  <Group number="2" />
  <Group number="3,5,11">
    <KEY name='key1' value='x' />
    <KEY name='key2' value='y' />
    <KEY name='key3' value='z' />
  </Group>
  <Group number="4" />
  <Group number="6,10">
    <KEY name='key1' value='x' />
    <KEY name='key2' value='y' />
    <KEY name='key3' value='z' />
    <KEY name='key4' value='a' />
    <KEY name='key5' value='b' />
  </Group>
  <Group number="7" />
  <Group number="8">
    <KEY name='key1' value='x' />
  </Group>
  <Group number="9" />
</Result>

То, что я действительно получаю, это...

<?xml version="1.0" encoding="utf-8"?>
<Result>
  <Group number="1" />
  <Group number="2" />
  <Group number="3,5,11">
    <KEY name="key1" value="x" />
    <KEY name="key2" value="y" />
    <KEY name="key3" value="z" />
  </Group>
  <Group number="4" />
  <Group number="6,10">
    <KEY name="key4" value="a" />
    <KEY name="key5" value="b" />
  </Group>
  <Group number="7" />
  <Group number="8" />
  <Group number="9" />
</Result>

Я использую ключ, и похоже, что когда я получаю доступ к этому конкретному значению из функции ключа, я не могу получить к нему доступ снова. Группа номер 6,10 должна содержать все 5 ключей, но отсутствуют первые 3, которые уже присутствуют в группе номер 3,5. Аналогично для группы №8 она должна содержать 1 ключ. Я использовал рекурсию, чтобы пропустить совпадающие экземпляры, но я не думаю, что там есть какая-то проблема, похоже, это связано с ключевыми функциями. Я прикрепил свой xslt ниже, пожалуйста, посмотрите и скажите мне, что я делаю неправильно. Любые советы по улучшению производительности также приветствуются :)

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
  <xsl:output method="xml" indent="yes"/>

  <xsl:key name="kKeyByName" match="KEY" use="@name" />

  <xsl:template name="ProcessItem">
    <!--pItemsList - node set containing items that need to be processed-->
    <xsl:param name="pItemsList" />
    <!--pProcessedList - string containing processed item numbers in the format |1|2|3|-->
    <xsl:param name="pProcessedList" />

    <xsl:variable name="vCurrItem" select="$pItemsList[1]" />
    <!--Recursion exit condition - check if we have a valid Item-->
    <xsl:if test="$vCurrItem">
      <xsl:variable name="vNum" select="$vCurrItem/@number" />
      <!--Skip processed instances-->
      <xsl:if test="not(contains($pProcessedList, concat('|', $vNum, '|')))">
        <xsl:element name="Group">
          <!--If the item is matched with another item, only the distinct keys of the 2 should be displayed-->
          <xsl:choose>
            <xsl:when test="$vCurrItem/@match">
              <xsl:attribute name="number">
                <xsl:value-of select="concat($vNum, ',', $vCurrItem/@match)" />
              </xsl:attribute>
              <xsl:for-each select="(//ITEM[@number=$vNum or @match=$vNum]/KEY)[generate-id(.)=generate-id(key('kKeyByName', @name)[1])]">
                <xsl:apply-templates select="." />
              </xsl:for-each>
            </xsl:when>
            <xsl:otherwise>
              <xsl:attribute name="number">
                <xsl:value-of select="$vNum" />
              </xsl:attribute>
              <xsl:apply-templates select="KEY" />
            </xsl:otherwise>
          </xsl:choose>
        </xsl:element>
      </xsl:if>

      <!--Append processed instances to list to pass on in recursive function-->
      <xsl:variable name="vNewList">
        <xsl:value-of select="$pProcessedList" />
        <xsl:value-of select="concat($vNum, '|')" />
        <xsl:if test="$vCurrItem/@match">
          <xsl:value-of select="concat($vCurrItem/@match, '|')" />
        </xsl:if>
      </xsl:variable>

      <!--Call template recursively to process the rest of the instances-->
      <xsl:call-template name="ProcessItem">
        <xsl:with-param name="pItemsList" select="$pItemsList[position() > 1]" />
        <xsl:with-param name="pProcessedList" select="$vNewList" />
      </xsl:call-template>
    </xsl:if>
  </xsl:template>

  <xsl:template match="KEY">
    <xsl:copy>
      <xsl:copy-of select="@*|node()" />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="/">
    <xsl:element name="Result">
      <xsl:call-template name="ProcessItem">
        <xsl:with-param name="pItemsList" select="//ITEM" />
        <xsl:with-param name="pProcessedList" select="'|'" />
      </xsl:call-template>
    </xsl:element>
  </xsl:template>
</xsl:stylesheet>

person user1701183    schedule 24.05.2013    source источник


Ответы (1)


ЕСЛИ есть только одно совпадение или нет для каждого элемента, вы можете попробовать следующий xslt:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="xml" indent="yes" />
    <xsl:strip-space elements="*"/>

    <xsl:key name="kItemNr" match="ITEM" use="@number" />
    <xsl:key name="kNumberKey" match="KEY" use="concat(../@number, '|', @name )" />

    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>

        </xsl:template>

    <xsl:template match="ITEM">
        <xsl:if test="not(preceding::ITEM[@number = current()/@match])" >
            <Group>
                <xsl:attribute name="number">
                    <xsl:value-of select="@number"/>
                    <xsl:if test="@match" >
                        <xsl:text>,</xsl:text>
                        <xsl:value-of select="@match"/>
                    </xsl:if>
                </xsl:attribute>
                <xsl:variable name="itemNr" select="@number"/>
                <xsl:apply-templates select="KEY |  key('kItemNr',@match )/KEY[ 
                                     not (key('kNumberKey', concat($itemNr, '|', @name) ) )] ">
                    <xsl:sort select="@name"/>
                </xsl:apply-templates>

            </Group>
        </xsl:if>
    </xsl:template>
    <xsl:template match="/" >
        <Result>
            <xsl:for-each select="//ITEM[count(. |  key('kItemNr',number )  ) = 1 ]" >
                <xsl:apply-templates select="." />
            </xsl:for-each>
        </Result>
    </xsl:template>

</xsl:stylesheet>

Что приведет к следующему выводу:

<?xml version="1.0"?>
<Result>
  <Group number="1"/>
  <Group number="2"/>
  <Group number="3,5">
    <KEY name="key1" value="x"/>
    <KEY name="key2" value="y"/>
    <KEY name="key3" value="z"/>
  </Group>
  <Group number="4"/>
  <Group number="6,10">
    <KEY name="key1" value="x"/>
    <KEY name="key2" value="y"/>
    <KEY name="key3" value="z"/>
    <KEY name="key4" value="a"/>
    <KEY name="key5" value="b"/>
  </Group>
  <Group number="7"/>
  <Group number="8">
    <KEY name="key1" value="x"/>
  </Group>
  <Group number="9"/>
</Result>

Обновление из-за измененного запроса:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="xml" indent="yes"/>

    <xsl:key name="kItemNr" match="ITEM" use="@number" />

    <xsl:template match="@*|node()">
        <xsl:copy >
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

    <xsl:template match="ITEM">
        <xsl:variable name="matchStr" select=" concat(',', current()/@match, ',')"/>
        <xsl:if test="not(preceding::ITEM[ contains($matchStr, concat(',', @number, ',') )])" >
            <Group>
                <xsl:attribute name="number">
                    <xsl:value-of select="@number"/>
                    <xsl:if test="@match" >
                        <xsl:text>,</xsl:text>
                        <xsl:value-of select="@match"/>
                    </xsl:if>
                </xsl:attribute>

                <xsl:apply-templates select="(KEY | 
                                     //ITEM[ 
                                        contains( $matchStr, concat(',', @number, ',') )
                                    ]/KEY[
                                         not((preceding::ITEM[
                                             contains( $matchStr, concat(',', @number, ',') )
                                            ] | current() )/KEY/@name = @name)
                                     ]) ">
                    <xsl:sort select="@name"/>
                </xsl:apply-templates>

            </Group>

        </xsl:if>
    </xsl:template>
    <xsl:template match="/" >
        <Result>
            <xsl:for-each select="//ITEM[count(. |  key('kItemNr',number )  ) = 1 ]" >
                <xsl:apply-templates select="." />
            </xsl:for-each>
        </Result>

    </xsl:template>

</xsl:stylesheet>

Это может быть довольно медленным для больших входных данных, но в любом случае.

person hr_117    schedule 24.05.2013
comment
Привет, мне нравится, что вам удалось удалить мою зависимость от рекурсии, и ваше решение отлично подходит для вопроса :) Но вы были правы, я упустил из виду условие, согласно которому атрибут match может содержать более одного номера элемента. Я обновил вопрос, чтобы включить это условие. - person user1701183; 24.05.2013
comment
Выглядит хорошо, большое спасибо за это! :) Извините за поздний ответ, зарылся под другие задачи. Есть ли у вас идеи, как избежать поиска по всему документу? - person user1701183; 07.06.2013