Выбор уникальных записей в XSLT / XPath - PullRequest
5 голосов
/ 10 июня 2010

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

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

Ответы [ 2 ]

4 голосов
/ 10 июня 2010

I.В качестве одного выражения 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>

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

2 голосов
/ 10 июня 2010

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

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

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

...