How can I remove specific subelements in XSLT?
I have an 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 am trying to achieve is to remove everything <subelement>
except what has the "highest" key. I can't seem to find a way to compare <key>
<subelements>
within a specific one <element>
.
The resulting XML will look like this:
<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 greatly appreciated.
source to share
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 to the following XML document (provided, extended to make it 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>
required, the correct result is obtained :
<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
This general solution is based on a functionmy:max()
that specifies a non-empty sequence of "structured values" (a structured value is a sequence of positive integers,. '), Yields one (of the maximum possible) maximum value.
This function is recursive. It does the following :
-
If all passed values are identical, the first one is returned.
-
Else, finds the maximum value for the first components.
-
If only one of the values has the first component with the maximum value found, return that value.
-
Find the maximum (recursively) tails of all values that have the maximum first component again.
-
Finally, connect the maximum first component to the maximum number of tails found in the previous step and return that value.
source to share
Next 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 a sample XML input, 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>
source to share