2010-06-10 2 views
5

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

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

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

+0

Хороший вопрос (+1). См. Мой ответ для XPath и XSLT-решений. –

+1

Метод, использующий key(), обычно называется методом Muenchian: http://www.jenitennison.com/xslt/grouping/muenchian.html –

ответ

4

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> 

Update : В случае, если <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> 

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

+0

Dimitre, Большое спасибо за ваш ответ. Я боюсь, что это не сработает для моего случая, но я извиняюсь, что не очень ясно, когда писал свой вопрос (впоследствии я его отредактировал). Проблема в том, что на самом деле мои элементы «item» также содержат различные другие подэлементы, которые не должны учитываться в отношении выбора элементов или нет. Я действительно не ищу «реальной» уникальности, я ищу уникальность только в определенных значениях дочерних элементов. Я уверен, что ваш ответ будет ценным для других. Dan –

+1

@ Daniel-I-S: Я обновил свой ответ с решением измененной проблемы. –

+2

Это отличный ответ; Большое спасибо. –

2

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

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

Я видел технику в старом издании Michael Kay's XSLT book. Вы можете найти его в некоторых своих образцах кода.

Смежные вопросы