Reputation: 13867
I've a XML structure like this:
<root>
<element>
<name>Foo</name>
<subelement>
<key>1.1</key>
<value>Lorem ipsum</value>
</subelement>
<subelement>
<key>1.2</key>
<value>Lorem ipsum dolor</value>
</subelement>
</element>
<element>
<name>Bar</name>
<subelement>
<key>7.3.4</key>
<value>Seven three four</value>
</subelement>
<subelement>
<key>7.3.8</key>
<value>Seven three eight</value>
</subelement>
<subelement>
<key>7.1</key>
<value>Seven one</value>
</subelement>
</element>
</root>
What I try to achieve is to remove all <subelement>
s except the one with the "highest" key.
I can't seem to find any way to compare the <key>
s of the <subelements>
s within a certain <element>
.
The resulting XML would look like:
<root>
<element>
<name>Foo</name>
<subelement>
<key>1.2</key>
<value>Lorem ipsum dolor</value>
</subelement>
</element>
<element>
<name>Bar</name>
<subelement>
<key>7.3.8</key>
<value>Seven three eight</value>
</subelement>
</element>
</root>
Any hints are very welcome.
Upvotes: 3
Views: 361
Reputation: 243479
This XSLT 2.0 transformation works for any number of "key components" in a key and for any possible positive integer value of any key component:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"subelement
[not(key eq my:max(../subelement/key))
or
key = preceding-sibling::subelement/key
]"/>
<xsl:function name="my:max" as="xs:string">
<xsl:param name="pValues" as="xs:string+"/>
<xsl:sequence select=
"if(not(distinct-values($pValues)[2]))
then $pValues[1]
else
for $vMax1 in
max(for $s in $pValues
return
xs:integer(substring-before(concat($s,'.'),'.'))
),
$vcntMax1Values in
count($pValues[starts-with(., string($vMax1))])
return
if($vcntMax1Values eq 1)
then $pValues[starts-with(., string($vMax1))]
[1]
else
for $submax in
(my:max(for $val in
$pValues[starts-with(., string($vMax1))]
[contains(., '.')],
$subval in substring-after($val, '.')
return
$subval
)
)
return
concat($vMax1, '.', $submax)
"/>
</xsl:function>
</xsl:stylesheet>
When this transformation is applied on the following XML document (the provided one, extended to be made more interesting):
<root>
<element>
<name>Foo</name>
<subelement>
<key>1.1</key>
<value>Lorem ipsum</value>
</subelement>
<subelement>
<key>1.2</key>
<value>Lorem ipsum dolor</value>
</subelement>
</element>
<element>
<name>Bar</name>
<subelement>
<key>7.3.4</key>
<value>Seven three four</value>
</subelement>
<subelement>
<key>7.3.8</key>
<value>Seven three eight</value>
</subelement>
<subelement>
<key>7.3.8.1</key>
<value>Seven three eight one</value>
</subelement>
<subelement>
<key>7.3.8.1</key>
<value>Seven three eight one</value>
</subelement>
<subelement>
<key>7.1</key>
<value>Seven one</value>
</subelement>
<subelement>
<key>10.1</key>
<value>Ten one</value>
</subelement>
<subelement>
<key>10.1</key>
<value>Ten one</value>
</subelement>
</element>
</root>
the wanted, correct result is produced:
<root>
<element>
<name>Foo</name>
<subelement>
<key>1.2</key>
<value>Lorem ipsum dolor</value>
</subelement>
</element>
<element>
<name>Bar</name>
<subelement>
<key>10.1</key>
<value>Ten one</value>
</subelement>
</element>
</root>
Explanation:
At the core of this generic solution is the function my:max()
which given a non-empty sequence of "structured values" (a structured value is a sequence of positive integers, joined with the '.' character), produces one (of the many possible) maximum value.
This function is recursive. It does the following:
If all the passed values are identical, then the first of them is returned.
Else, finds the maximum value of the first components.
If only one of the values has a first component with the found maximum value, return this value.
Else, find the maximum (recursively) of the "tails" of all values that have the maximum first component.
Finally, join together the maximum first component with the maximum of the "tails", found in the previous step -- and return this value.
Upvotes: 2
Reputation: 891
The following version 2.0 XSLT
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="element">
<xsl:variable name="sorted">
<xsl:for-each select="subelement">
<xsl:sort select="key"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="highest" select="$sorted/subelement[count($sorted/subelement)]/key"/>
<element>
<xsl:copy-of select="name"/>
<xsl:copy-of select="subelement[key=$highest]"/>
</element>
</xsl:template>
</xsl:stylesheet>
When applied to the sample input XML produces the required output XML
<?xml version="1.0" encoding="UTF-8"?>
<root>
<element>
<name>Foo</name>
<subelement>
<key>1.2</key>
<value>Lorem ipsum dolor</value>
</subelement>
</element>
<element>
<name>Bar</name>
<subelement>
<key>7.3.8</key>
<value>Seven three eight</value>
</subelement>
</element>
</root>
Upvotes: -1