Mike Kay, David Carlisle and Dimitre Novatchev
> I have the following XML-file (see below) and I need to find the
> deepest node of 'title'.
> The resultfile should only contain the titles 'Text 02' and 'Text 03',
> because they are in this case the deepest nodes. The next XML-file I
> receive may contain more nested levels of items and whatever the level
> of nesting I only need to find the deepest titles.
> I know I have to do something with recurse, but I don't know how to
> start.
>
> <?xml version="1.0" encoding="UTF-8"?> <document>
> <item>
> <item>
> <title>Text 01</title>
> </item>
> <item>
> <item>
> <title>Text 02</title>
> </item>
> </item>
> </item>
> <item>
> <item>
> <item>
> <title>Text 03</title>
> </item>
> </item>
> </item>
> <item>
> <item>
> <title>Text 04</title>
> </item>
> </item>
> </document>
>
> Resultfile should contain:
>
> <?xml version="1.0" encoding="UTF-8"?> <document>
> <item>
> <item>
> <item>
> <title>Text 02</title>
> </item>
> </item>
> </item>
> <item>
> <item>
> <item>
> <title>Text 03</title>
> </item>
> </item>
> </item>
> </document>
>
In 1.0 you can usually tackle this kind of problem by sorting nodes according to their depth (that is, count(ancestor::*)) and taking the first or last in sorted sequence. It's complicated here because you want all the nodes having that maximum depth. That suggests two passes, one to get the max depth and the second to select nodes with that depth <xsl:variable name="max-depth">
<xsl:for-each select="//title">
<xsl:sort select="count(ancestor::*)" data-type="number"/>
<xsl:if test="position()=last()">
<xsl:copy-of select="count(ancestor::*)"/>
</
</
<xsl:copy-of select="//title[count(ancestor::*) = $max-depth]"/>
and DC adds
This is a grouping problem (where you only want one group) <xsl:key name="t" select="title" use="count(ancestor::*)"/> ... <xsl:for-each select="//title">
<xsl:sort data-type="number" select="count(ancestor::*)">
<xsl:if test="position()=last()">
<xsl:apply-templates select="key('t',count(ancestor::*))"/>
</xsl:if>
</xsl:for-each>
Dimitre adds
Using FXSL, one will write something as the following:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
exclude-result-prefixes="f"
>
<xsl:import href="C:/CVS-DDN/fxsl-xslt2/f/func-maxDepth.xsl"/>
<xsl:output omit-xml-declaration="yes"/>
<xsl:template match="/">
<xsl:sequence select=
"//node()[count(ancestor::node()) = f:maxDepth(/)]/.."/>
</xsl:template>
</xsl:stylesheet>
and applying this transformation on the originally specified xml source document produces the wanted result:
<title>Text 02</title><title>Text 03</title>
Here's the code of func-maxDepth.xsl:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:f="http://fxsl.sf.net/"
xmlns:MyMaxDepth="MyMaxDepth"
exclude-result-prefixes="xs f MyMaxDepth"
>
<xsl:import href="func-map.xsl"/>
<xsl:variable name="vfunMaxDepth"
select="document('')/*/MyMaxDepth:*[1]"/>
<xsl:function name="f:maxDepth" as="xs:integer">
<xsl:param name="pNode" as="node()"/>
<xsl:value-of select=
"if (not($pNode/node())) then 0
else
max(f:map($vfunMaxDepth, $pNode/node())) + 1"/>
</xsl:function>
<MyMaxDepth:MyMaxDepth/>
<xsl:template match="MyMaxDepth:*" mode="f:FXSL">
<xsl:param name="arg1" as="node()"/>
<xsl:sequence select="f:maxDepth($arg1)"/>
</xsl:template>
</xsl:stylesheet>
|