在XSLT / XPath中选择唯一记录

时间:2010-06-10 17:36:02

标签: xml xpath transform xslt

我必须在<xsl:for-each>循环的上下文中从XML文档中仅选择唯一记录。我被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>

唯一性必须基于三个子元素的值:schDateschFrmTimeschToTime。如果两个item元素对于所有三个子元素具有相同的值,则它们是重复的。在上面的XML中,第一项和第二项是重复的。其余的都是独特的。如上所述,每个项目都包含我们不希望包含在比较中的其他元素。 “独特性”应该是这三个要素的一个因素,而且仅仅是那些因素。

我试图通过以下方式实现这一目标:

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

这背后的想法是选择没有前面元素的记录,其中schDateschFrmTimeschToTime相同。但是,其输出缺少最后一项。这是因为我的XPath实际上排除了所有子元素值在整个前一个文档中匹配的项目。没有单个item匹配最后一个项目的所有子元素 - 但由于每个元素的值都单独存在于另一个项目中,因此最后一个项目将被排除。

我可以通过将所有子值作为连接字符串比较每个前面项的相同连接值来获得正确的结果。有人知道我能做到这一点吗?

2 个答案:

答案 0 :(得分:4)

<强>予。作为单个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>

产生想要的结果

答案 1 :(得分:2)

我见过的技术是两次通过:按所有三个关键字段对项目进行排序,然后将每个项目与之前的项目(而不是所有前面的项目)进行比较。

运行两个单独的转换是否实用?它使问题更容易。

我在旧版Michael Kay's XSLT book中看到了这种技术。您可以在他的一些示例代码中找到它。