仅选择与属性值匹配的列表中的第一个元素

时间:2013-06-21 07:00:33

标签: xml xslt xpath

我希望能够从一组中选择与属性值匹配的第一个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语句中完成此操作?

1 个答案:

答案 0 :(得分:4)

以下是使用for-each-groupgroup-by

的XSLT 2.0方法
<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元素。