Reputation: 5
I have the current XML:
<DocumentElement>
<Customer>
<CustomerId>2315</CustomerId>
<Date>2011-04-28 14:14:00</Date>
<VersionNumber>1</VersionNumber>
<GUID>2E05DE20-02A0-425D-944D-65E5E744FF8A</GUID>
</Customer>
<Customer>
<CustomerId>2316</CustomerId>
<Date>2011-04-28 15:03:00</Date>
<VersionNumber>2</VersionNumber>
<GUID>2E05DE20-02A0-425D-944D-65E5E744FF8A</GUID>
</Customer>
<Customer>
<CustomerId>2317</CustomerId>
<Date>2011-04-28 15:03:00</Date>
<VersionNumber>1</VersionNumber>
<GUID>9995DE20-02A0-425D-944D-65E5E744FF8A</GUID>
</Customer>
</DocumentElement>
What I am trying to do is to filter out one element of each GUID with the highest version number, i.e, transform the document above to look like:
<DocumentElement>
<Customer>
<CustomerId>2316</CustomerId>
<Date>2011-04-28 15:03:00</Date>
<VersionNumber>2</VersionNumber>
<GUID>2E05DE20-02A0-425D-944D-65E5E744FF8A</GUID>
</Customer>
<Customer>
<CustomerId>2317</CustomerId>
<Date>2011-04-28 15:03:00</Date>
<VersionNumber>1</VersionNumber>
<GUID>9995DE20-02A0-425D-944D-65E5E744FF8A</GUID>
</Customer>
</DocumentElement>
Anyone who can point me in the right direction on where to start?
Thanks in advance.
Upvotes: 0
Views: 727
Reputation: 10337
I would start with declaring a key on GUID, this enables to easily process distinct GUIDs; then, per GUID, sort the corresponding Customer elements on their VersionNumber and simply copy the first (xslt-1.0):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:key name="byGUID" match="Customer" use="GUID"/>
<xsl:template match="/*">
<xsl:copy>
<!-- process first Customer for each distinct GUID -->
<xsl:apply-templates select="//Customer[generate-id()=generate-id(key('byGUID',GUID))]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<!-- sort and process all Customers with the same GUID -->
<xsl:apply-templates select="key('byGUID',GUID)" mode="copyFirst">
<xsl:sort select="VersionNumber" order="descending"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="*" mode="copyFirst">
<!-- only copy the first (highest VersionNumber) -->
<xsl:if test="position()=1">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Using this intersection of nodesets is another way to process the first Customer for each distinct GUID:
<xsl:apply-templates select="//Customer[count(.|key('byGUID',GUID)[1])=1]"/>
Upvotes: 1
Reputation: 13574
As an exercise, I've tried to solve this using XSLT 2.0 and XPath 2.0:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="no" />
<xsl:template match="/DocumentElement">
<xsl:copy>
<xsl:for-each-group select="Customer" group-by="GUID">
<xsl:copy-of select="current-group()
[VersionNumber=max(current-group()/VersionNumber)]" />
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Major differences:
with XSLT 2.0 you don't need to define an xsl:key
as in this other answer, but you can use xsl:for-each-group ... group-by="GUID"
with XPath 2.0 you have the fn:max(...)
function
Upvotes: 3