Выбор уникальных записей в XSLT/XPath

Мне нужно выбрать только уникальные записи из XML-документа в контексте цикла <xsl:for-each>. Visual Studio ограничивает меня использованием XSL 1.0.

    <availList>
        <item>
          <schDate>2010-06-24</schDate>              
          <schFrmTime>10:00:00</schFrmTime>
          <schToTime>13:00:00</schToTime>
          <variousOtherElements></variousOtherElements>
        </item>
        <item>
          <schDate>2010-06-24</schDate>              
          <schFrmTime>10:00:00</schFrmTime>
          <schToTime>13:00:00</schToTime>
          <variousOtherElements></variousOtherElements>
        </item>
        <item>
          <schDate>2010-06-25</schDate>              
          <schFrmTime>10:00:00</schFrmTime>
          <schToTime>12:00:00</schToTime>
          <variousOtherElements></variousOtherElements>
        </item>
        <item>
          <schDate>2010-06-26</schDate>              
          <schFrmTime>13:00:00</schFrmTime>
          <schToTime>14:00:00</schToTime>
          <variousOtherElements></variousOtherElements>
        </item>
        <item>
          <schDate>2010-06-26</schDate>              
          <schFrmTime>10:00:00</schFrmTime>
          <schToTime>12:00:00</schToTime>
          <variousOtherElements></variousOtherElements>
        </item>
    </availList>

Уникальность должна основываться на значении трех дочерних элементов: schDate, schFrmTime и schToTime. Если два элемента item имеют одинаковые значения для всех трех дочерних элементов, они дублируются. В приведенном выше XML элементы один и два повторяются. Остальные уникальны. Как указано выше, каждый элемент содержит другие элементы, которые мы не хотим включать в сравнение. «Уникальность» должна быть фактором этих трех элементов, и только их.

Я попытался сделать это с помощью следующего:

availList/item[not(schDate = preceding:: schDate and schFrmTime = preceding:: schFrmTime and schToTime = preceding:: schToTime)]

Идея заключается в том, чтобы выбрать записи, в которых нет предшествующих элементов с одинаковыми schDate, schFrmTime и schToTime. Однако в его выводе отсутствует последний элемент. Это связано с тем, что мой XPath фактически исключает элементы, в которых все значения дочерних элементов совпадают во всем предыдущем документе. Ни один item не соответствует всем дочерним элементам последнего элемента, но поскольку значение каждого элемента индивидуально присутствует в другом элементе, последний элемент исключается.

Я мог бы получить правильный результат, сравнив все дочерние значения в виде объединенной строки с теми же составными значениями для каждого предыдущего элемента. Кто-нибудь знает, как я мог бы это сделать?


person Daniel Situnayake    schedule 10.06.2010    source источник
comment
Хороший вопрос (+1). См. мой ответ для решений XPath и XSLT.   -  person Dimitre Novatchev    schedule 10.06.2010
comment
Метод, использующий key(), обычно называют мюнхским методом: /grouping/muenchian.html   -  person Josh Davis    schedule 11.06.2010


Ответы (2)


И. Как одно выражение XPath:

/*/item[normalize-space() and not(. = preceding-sibling::item)]

II. Более эффективная (XSLT) реализация с использованием ключей:

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

 <xsl:key name="kItemByVal" match="item" use="."/>

 <xsl:template match="/">
  <xsl:copy-of select=
   "*/item[generate-id() = generate-id(key('kItemByVal', .))]
   "/>
 </xsl:template>
</xsl:stylesheet>

И I, и II при применении к предоставленному XML-документу правильно выбирают/скопируют следующие узлы:

<item><schDate>2010-06-24</schDate><schFrmTime>10:00:00</schFrmTime><schToTime>13:00:00</schToTime></item>
<item><schDate>2010-06-25</schDate><schFrmTime>10:00:00</schFrmTime><schToTime>12:00:00</schToTime></item>
<item><schDate>2010-06-26</schDate><schFrmTime>13:00:00</schFrmTime><schToTime>14:00:00</schToTime></item>
<item><schDate>2010-06-26</schDate><schFrmTime>10:00:00</schFrmTime><schToTime>12:00:00</schToTime></item>

Обновление: если у <item> есть другие дочерние элементы, это преобразование:

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>

    <xsl:key name="kItemBy3Children" match="item"
     use="concat(schDate, '+', schFrmTime, '+', schToTime)"/>

 <xsl:template match="/">
       <xsl:copy-of select=
        "*/item[generate-id()
              = generate-id(key('kItemBy3Children',
                                concat(schDate,
                                       '+', schFrmTime,
                                       '+', schToTime)
                               )
                            )
               ]
        "/>
 </xsl:template>
</xsl:stylesheet>

дает желаемый результат.

person Dimitre Novatchev    schedule 10.06.2010
comment
Дмитрий, Большое спасибо за ответ. Однако я боюсь, что это не сработает в моем случае - я извиняюсь за то, что не очень ясно выразился, когда писал свой вопрос (впоследствии я его отредактировал). Проблема в том, что на самом деле мои элементы item также содержат различные другие подэлементы, которые не должны учитываться независимо от того, выбраны ли элементы или нет. На самом деле я не ищу «настоящую» уникальность, я ищу уникальность только в определенных значениях дочерних элементов. Я уверен, что ваш ответ будет ценным для других. Дэн - person Daniel Situnayake; 11.06.2010
comment
@ Daniel-I-S: я обновил свой ответ решением измененной проблемы. - person Dimitre Novatchev; 11.06.2010

Техника, которую я видел, состоит в том, чтобы сделать это в два прохода: отсортировать элементы по всем трем ключевым полям, а затем сравнить каждый элемент с его предыдущим элементом (а не со всеми предыдущими элементами).

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

Я видел эту технику в более ранней редакции книга Майкла Кея о XSLT. Вы можете найти его в каком-то из его примеров кода.

person Don Kirkby    schedule 10.06.2010