我想在XSLT中使用XPath来选择具有基于属性值的条件的节点。
为了说明我的问题,我有一个简短的XML实例,如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<elementA fID="2013_4_20150722_0" dateTime="2015-07-13T01:04:20+02:00"/>
<elementA fID="2013_4_20150721_0" dateTime="2015-07-13T01:04:20+02:00"/>
<elementA fID="2013_4_20150721_0" dateTime="2015-07-20T14:14:22+02:00"/>
</root>
我想在以下条件下选择所有elementA
个节点:
fID
是唯一的elementA
个节点具有相同的fID
属性值,则只会选择最新dateTime
的节点。因此,在我的示例中,我想选择第一个和第三个elementA
。
如何在XSLT 2.0中使用XPath 2.0实现这一目标?
答案 0 :(得分:2)
这是一个纯粹,单一且高效(无排序)的XPath 2.0表达式,它可以选择想要的元素:
for $fid in distinct-values(/*/*/@fID),
$maxtime in max(/*/*[@fID eq $fid]/@dateTime/xs:dateTime(.))
return
(/*/*[@fID eq $fid and xs:dateTime(@dateTime) eq $maxtime])[1]
这是一个证明,其中XSLT仅用于将表达式的计算结果复制到输出中:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/">
<xsl:sequence select=
"for $fid in distinct-values(/*/*/@fID),
$maxtime in max(/*/*[@fID eq $fid]/@dateTime/xs:dateTime(.))
return
(/*/*[@fID eq $fid and xs:dateTime(@dateTime) eq $maxtime])[1]
"/>
</xsl:template>
</xsl:stylesheet>
在此源XML文档上应用上述转换时:
<root>
<elementA fID="2013_4_20150722_0" dateTime="2015-07-13T01:04:20+02:00"/>
<elementA fID="2013_4_20150721_0" dateTime="2015-07-13T01:04:20+02:00"/>
<elementA fID="2013_4_20150721_0" dateTime="2015-07-20T12:14:22+00:00"/>
<elementA fID="2013_4_20150721_0" dateTime="2015-07-20T14:14:22+02:00"/>
<elementA fID="2013_4_20150721_0" dateTime="2015-07-20T14:14:22+02:00"/>
</root>
产生了想要的正确结果:
<elementA fID="2013_4_20150722_0" dateTime="2015-07-13T01:04:20+02:00"/>
<elementA fID="2013_4_20150721_0" dateTime="2015-07-20T12:14:22+00:00"/>
关于效率的说明:
此XPath表达式仅使用max()
函数,即
O(N)
- 优于使用排序的解决方案的O(N * log(N))。
答案 1 :(得分:1)
我会在XSLT 2.0中进行分组和排序,如果你想在XPath中使用它,你可以编写一个包含该功能的用户定义函数:
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf">
<xsl:output indent="yes"/>
<xsl:function name="mf:group-and-sort" as="element(elementA)*">
<xsl:param name="input" as="element(elementA)*"/>
<xsl:for-each-group select="$input" group-by="@fID">
<xsl:variable name="sorted-group" as="element(elementA)*">
<xsl:perform-sort select="current-group()">
<xsl:sort select="xs:dateTime(@dateTime)" order="descending"/>
</xsl:perform-sort>
</xsl:variable>
<xsl:sequence select="$sorted-group[1]"/>
</xsl:for-each-group>
</xsl:function>
<xsl:template match="root">
<xsl:copy>
<xsl:variable name="max-elementAs" select="mf:group-and-sort(elementA)"/>
<xsl:copy-of select="$max-elementAs"/>
</xsl:copy>
</xsl:template>
</xsl:transform>