2013-06-21 4 views
2

Я хочу, чтобы иметь возможность выбрать первый элемент XML из набора, который соответствует значениям атрибута. Для того, чтобы объяснить больше, предположим, что мы имеем следующий XML:Выберите только первый элемент из списка, соответствующего значению атрибута

<events> 
    <event date="2013-06-17"> 
     <person first="Joe" last="Bloggs" /> 
     <person first="John" last="Smith" /> 
    </event> 
    <event date="2013-01-29"> 
     <person first="Jane" last="Smith" /> 
     <person first="John" last="Smith" /> 
    </event> 
    <event date="2012-09-03"> 
     <person first="Joe" last="Bloggs" /> 
     <person first="John" last="Doe" /> 
    </event> 
    <event date="2012-04-05"> 
     <person first="Jane" last="Smith" /> 
     <person first="John" last="Smith" /> 
     <person first="John" last="Doe" /> 
    </event> 
<event> 

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

<person first="Joe" last="Bloggs" /> 
<person first="John" last="Doe" /> 
<person first="Jane" last="Smith" /> 
<person first="John" last="Smith" /> 

Есть целый ряд решений, все из которых являются вариации на эту тему:

<xsl:for-each select="//person"> 
    <xsl:if test="not(preceding::person[ @first = current()/@first and @last = current()/@last ])"> 
     <xsl:apply-templates select="." /> 
    </xsl:if> 
</xsl:for-each> 

Тем не менее, мне кажется, что я должен быть в состоянии включить тест из xsl:if в качестве предиката при выборе из xsl:for-each, например.

<xsl:apply-templates select="//person[ not(preceding::person[ @first = current()/@first and @last = current()/@last ]) ]" /> 

Конечно, хотя, функция current() не нравится, но я просто интересно, если кто-нибудь знает, как это может быть сделано в одном операторе XPath?

+2

Можете ли вы использовать XSLT 2.0 или XSTL 1.0? В XSLT 2.0 вы можете использовать элемент ** xsl: for-each-group **. В XSLT 1.0 вы использовали бы технологию, известную как Muenchian Grouping. –

+0

Я могу использовать XSL 2.0, но, хотя для каждой группы работает немного аккуратно, чем то, что я сейчас делаю, она не решает проблему: как указать выбор как единую инструкцию XPath, которая может быть использована, например, в вызове apply-templates. – rumplestiltzkin

ответ

4

Вот XSLT 2.0 подход с использованием for-each-group с group-by:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 

<xsl:output indent="yes"/> 

<xsl:template match="events"> 
    <xsl:for-each-group select="event/person" group-by="concat(@first, '|', @last)"> 
    <xsl:copy-of select="."/> 
    </xsl:for-each-group> 
</xsl:template> 

</xsl:stylesheet> 

Он превращает

<events> 
    <event date="2013-06-17"> 
     <person first="Joe" last="Bloggs" /> 
     <person first="John" last="Smith" /> 
    </event> 
    <event date="2013-01-29"> 
     <person first="Jane" last="Smith" /> 
     <person first="John" last="Smith" /> 
    </event> 
    <event date="2012-09-03"> 
     <person first="Joe" last="Bloggs" /> 
     <person first="John" last="Doe" /> 
    </event> 
    <event date="2012-04-05"> 
     <person first="Jane" last="Smith" /> 
     <person first="John" last="Smith" /> 
     <person first="John" last="Doe" /> 
    </event> 
</events> 

в

<person first="Joe" last="Bloggs"/> 
<person first="John" last="Smith"/> 
<person first="Jane" last="Smith"/> 
<person first="John" last="Doe"/> 

С XSLT 1.0 вы можете использовать

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 

<xsl:output indent="yes"/> 

<xsl:key name="by-name" match="event/person" use="concat(@first, '|', @last)"/> 

<xsl:template match="events"> 
    <xsl:for-each select="event/person[generate-id() = generate-id(key('by-name', concat(@first, '|', @last))[1])]"> 
    <xsl:copy-of select="."/> 
    </xsl:for-each> 
</xsl:template> 

</xsl:stylesheet> 

См. http://www.jenitennison.com/xslt/grouping/muenchian.xml для получения пояснения.

Muenchian группировка может быть упрощено до

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 

<xsl:output indent="yes"/> 

<xsl:key name="by-name" match="event/person" use="concat(@first, '|', @last)"/> 

<xsl:template match="events"> 
    <xsl:copy-of select="event/person[generate-id() = generate-id(key('by-name', concat(@first, '|', @last))[1])]"/> 
</xsl:template> 

</xsl:stylesheet> 

Конечно, вместо копирования в результирующее дерево можно, а сделать apply-templates и трансформировать первый элемент в каждой группе таким образом:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 

<xsl:output indent="yes"/> 

<xsl:key name="by-name" match="event/person" use="concat(@first, '|', @last)"/> 

<xsl:template match="events"> 
    <xsl:apply-templates select="event/person[generate-id() = generate-id(key('by-name', concat(@first, '|', @last))[1])]"/> 
</xsl:template> 

<xsl:template match="person"> 
    <foo>...</foo> 
</xsl:template> 

</xsl:stylesheet> 

С XSLT 2.0 и for-each-group вам нужно будет использовать переменную, хранящую элементы, например

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 

<xsl:output indent="yes"/> 

<xsl:template match="events"> 
    <xsl:variable name="first-person-in-groups" as="element(person)*"> 
    <xsl:for-each-group select="event/person" group-by="concat(@first, '|', @last)"> 
     <xsl:copy-of select="."/> 
    </xsl:for-each-group> 
    </xsl:variable> 
    <xsl:apply-templates select="$first-person-in-groups"/> 
</xsl:template> 

<xsl:template match="person"> 
    <foo>...</foo> 
</xsl:template> 

</xsl:stylesheet> 

таким образом у вас есть последовательность person элементов вы можете apply-templates на.

+0

Этот ответ имеет ту же структуру, что и решение, которое я уже принял, хотя и немного аккуратнее, но не затрагивает фактический вопрос, который я задал: может ли это быть сделано как оператор _single_ XPath, и если да, то как? Я думаю, из этого следует, что это невозможно. – rumplestiltzkin

+0

Мой XSLT 1.0 solutuion - это переписывание XSLT 2.0 (где вам нужна 'for-each-group'), но я покажу вам, как в XSLT 1.0 и 2.0 с определенным ключом вы можете использовать одно выражение для фильтрации первого в каждом группа. –

+0

@rumplestiltzkin, я отредактировал ответ, чтобы показать, как 'apply-templates' на наборе узлов соответственно последовательность уникальных элементов' person'. Однако это сочетание XSLT и XPath, а не одно выражение XPath. –