S
Scott Sauyet
I found myself needing to find my way recursively through a document
in XSLT, finding the dependencies one element had on others, and
including only those in an initial set, their dependencies, the
dependencies of those dependencies and so on. I managed to do it, but
I'm not really happy with the method, and I'm wondering if anyone can
suggest something better.
My technique involves manipulating a string containing the names,
calling a template that adds the names of the direct dependencies to a
temporary string and then, if there are no additional names, returning
that list, or, if there are additional ones, returning a recursive
call to the template using the new list. It's pretty simple Recursion
101 code, but there's a level of indirection I don't like here. I
shouldn't be using a string of names; I would rather be using node-
sets. But whenever I tried that, I ran into issues of variables that
held things that looked like but weren't really node-sets.
Any suggestions for how I could do this without resorting to using the
names, would be appreciated.
Here's a simplified sample input document:
<root>
<x name="a">
<y dep="b"/>
<y dep="c"/>
</x>
<x name="b">
<y dep="g"/>
</x>
<x name="c">
<y dep="a"/>
</x>
<x name="e">
<y dep="b"/>
</x>
<x name="f">
<y dep="c"/>
<y dep="e"/>
</x>
<x name="g">
<y dep="h"/>
</x>
<x name="h">
<y dep="i"/>
</x>
<x name="i"/>
<x name="j">
<y dep="i"/>
</x>
</root>
Passed the parameter, "a, b", the stylesheet should note that 'a'
depends on 'b' and' c', 'b' depends on 'g', which depends on 'h',
which depends on 'i', which has no further dependencies. And 'c'
depends on 'a'. That last is important because it demonstrates that
this is not necessarily a tree structure. There are cycles.
The output should be something like:
<root>
<x name="a">
<y dep="b"/>
<y dep="c"/>
</x>
<x name="b">
<y dep="g"/>
</x>
<x name="c">
<y dep="a"/>
</x>
<x name="g">
<y dep="h"/>
</x>
<x name="h">
<y dep="i"/>
</x>
<x name="i"/>
</root>
The XSLT I use for this is pretty straightforward:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl
utput method="xml" indent="yes" omit-xml-declaration="no" />
<xsl
aram name="base" select="'a b'"/>
<xsl:variable name="start">
<xsl:call-template name="normalize">
<xsl:with-param name="data" select="$base"/>
</xsl:call-template>
</xsl:variable>
<xsl:template match="/">
<root>
<xsl:variable name="names">
<xsl:call-template name="choose-names">
<xsl:with-param name="names" select="$start"/>
</xsl:call-template>
</xsl:variable>
<xsl:for-each select="//x">
<xsl:if test="contains($names, @name)">
<xsl:text>
</xsl:text><xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</root>
</xsl:template>
<xsl:template name="normalize">
<xsl
aram name="data" />
<xsl:value-of select="concat(normalize-space($data), ' ')" />
</xsl:template>
<xsl:template name="choose-names">
<xsl
aram name="names"/>
<xsl:variable name="temp">
<xsl:for-each select="//x">
<xsl:variable name="name" select="@name"/>
<xsl:if test="contains($names, $name) or
//x[contains($names, @name)]/y[@dep = $name]">
<xsl:value-of select="@name"/><xsl:text> </
xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:choose>
<xsl:when test="$names = $temp">
<xsl:value-of select="$temp"/>
</xsl:when>
<xsl
therwise>
<xsl:call-template name="choose-names">
<xsl:with-param name="names" select="$temp" />
</xsl:call-template>
</xsl
therwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I know there are holes in the string processing here. I'm sure I
could tighten it up by using a separator other than space and then
doing some escaping if the name contained the separator, but since I'd
rather not be using the names anyway...
So, does anyone have suggestions for how to do this without resorting
to using strings (except to get the original set from the input
parameter?)
Thank you very much,
-- Scott Sauyet
in XSLT, finding the dependencies one element had on others, and
including only those in an initial set, their dependencies, the
dependencies of those dependencies and so on. I managed to do it, but
I'm not really happy with the method, and I'm wondering if anyone can
suggest something better.
My technique involves manipulating a string containing the names,
calling a template that adds the names of the direct dependencies to a
temporary string and then, if there are no additional names, returning
that list, or, if there are additional ones, returning a recursive
call to the template using the new list. It's pretty simple Recursion
101 code, but there's a level of indirection I don't like here. I
shouldn't be using a string of names; I would rather be using node-
sets. But whenever I tried that, I ran into issues of variables that
held things that looked like but weren't really node-sets.
Any suggestions for how I could do this without resorting to using the
names, would be appreciated.
Here's a simplified sample input document:
<root>
<x name="a">
<y dep="b"/>
<y dep="c"/>
</x>
<x name="b">
<y dep="g"/>
</x>
<x name="c">
<y dep="a"/>
</x>
<x name="e">
<y dep="b"/>
</x>
<x name="f">
<y dep="c"/>
<y dep="e"/>
</x>
<x name="g">
<y dep="h"/>
</x>
<x name="h">
<y dep="i"/>
</x>
<x name="i"/>
<x name="j">
<y dep="i"/>
</x>
</root>
Passed the parameter, "a, b", the stylesheet should note that 'a'
depends on 'b' and' c', 'b' depends on 'g', which depends on 'h',
which depends on 'i', which has no further dependencies. And 'c'
depends on 'a'. That last is important because it demonstrates that
this is not necessarily a tree structure. There are cycles.
The output should be something like:
<root>
<x name="a">
<y dep="b"/>
<y dep="c"/>
</x>
<x name="b">
<y dep="g"/>
</x>
<x name="c">
<y dep="a"/>
</x>
<x name="g">
<y dep="h"/>
</x>
<x name="h">
<y dep="i"/>
</x>
<x name="i"/>
</root>
The XSLT I use for this is pretty straightforward:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl
<xsl
<xsl:variable name="start">
<xsl:call-template name="normalize">
<xsl:with-param name="data" select="$base"/>
</xsl:call-template>
</xsl:variable>
<xsl:template match="/">
<root>
<xsl:variable name="names">
<xsl:call-template name="choose-names">
<xsl:with-param name="names" select="$start"/>
</xsl:call-template>
</xsl:variable>
<xsl:for-each select="//x">
<xsl:if test="contains($names, @name)">
<xsl:text>
</xsl:text><xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</root>
</xsl:template>
<xsl:template name="normalize">
<xsl
<xsl:value-of select="concat(normalize-space($data), ' ')" />
</xsl:template>
<xsl:template name="choose-names">
<xsl
<xsl:variable name="temp">
<xsl:for-each select="//x">
<xsl:variable name="name" select="@name"/>
<xsl:if test="contains($names, $name) or
//x[contains($names, @name)]/y[@dep = $name]">
<xsl:value-of select="@name"/><xsl:text> </
xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:choose>
<xsl:when test="$names = $temp">
<xsl:value-of select="$temp"/>
</xsl:when>
<xsl
<xsl:call-template name="choose-names">
<xsl:with-param name="names" select="$temp" />
</xsl:call-template>
</xsl
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I know there are holes in the string processing here. I'm sure I
could tighten it up by using a separator other than space and then
doing some escaping if the name contained the separator, but since I'd
rather not be using the names anyway...
So, does anyone have suggestions for how to do this without resorting
to using strings (except to get the original set from the input
parameter?)
Thank you very much,
-- Scott Sauyet