Alexis said:
Can you start from the end and move towards the begining.?
Yes Alexis, in <xsl:template> rules you can do what's called
head recursion (where the 'work' is done after the recursive
call, that is, as the stack unwinds) or tail recursion (do
the 'work' first and then make a recursive call, which is
what the example I've shown demonstrated).
I used tail recursion because I don't believe I could find
a head recursive solution that's as efficient. An extra
processing step is required to 'reverse' the @-delimited
particles in your sample XML to affect a head recursive
solution. E.g.,
JOHN SMITH@17 BELLINGHAM DR@OWINGS MILLS@MD/21117@USA
needs to become,
USA@MD/21117@OWINGS MILLS@17 BELLINGHAM DR@JOHN SMITH
However, I understand you're asking for the approach so
that "optional" elements at the end can be omitted, and
it can be done more elegantly than testing for the num-
ber of particles or determining if the first particle
is a name or an address.
Here's a revised example,
- - - SplitsvilleTheSequel.xsl
<xsl:stylesheet version="1.0" xmlns:xsl="
http://www.w3.org/1999/XSL/Transform">
<xsl
aram name="scheme">country,region,city,street,name</xsl
aram>
<xsl:template name="split">
<xsl
aram name="scheme" />
<xsl
aram name="encodedArg" />
<xsl:if test="substring-before($encodedArg,'@')"> <!-- Line 9 -->
<xsl:call-template name="split">
<xsl:with-param name="scheme" select="substring-after($scheme,',')" />
<xsl:with-param name="encodedArg" select="substring-after($encodedArg,'@')" />
</xsl:call-template>
<xsl:element name="{substring-before($scheme,',')}">
<xsl:value-of select="substring-before($encodedArg,'@')" />
</xsl:element>
</xsl:if>
</xsl:template>
<xsl:template name="reverse">
<xsl
aram name="unreversedArgs" />
<xsl:if test="substring-before($unreversedArgs,'@')">
<xsl:call-template name="reverse">
<xsl:with-param name="unreversedArgs" select="substring-after($unreversedArgs,'@')" />
</xsl:call-template>
<xsl:value-of select="concat(substring-before($unreversedArgs,'@'),'@')" />
</xsl:if>
</xsl:template>
<xsl:template match="FullAddress">
<xsl:call-template name="split">
<xsl:with-param name="scheme" select="concat($scheme,',')" />
<xsl:with-param name="encodedArg">
<xsl:call-template name="reverse">
<xsl:with-param name="unreversedArgs" select="concat(text(),'@')" />
</xsl:call-template>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template match="/">
<address>
<xsl:apply-templates select="FullAddress" />
</address>
</xsl:template>
</xsl:stylesheet>
- - -
The reverse template reverses the particles between '@'
delimiters. (XPath 2.0 helps make this sort of operation
more direct, with it's new reverse() function, BTW.)
The outcome of reverse is what the split template receives
as it's $encodedArg. Notice that I reversed the $scheme
variable manually (you could also run a reverse template
on these, using a ',' delimiter instead of '@' which in
the general case could be a second parameter to reverse,
but I left out this for efficiency).
In the split template, all I did to make it head recursive
instead of tail recursive was swap the <xsl:call-template>
with the <xsl:element> that renders into the resulting
node set. This means we go all the way to the last particle
in $encodedArg and $scheme (which I want rendered first)
and then as the recursion unwinds that renders the first
element, and so on back up the stack.
I'll draw your attention to one other note about line 9,
<xsl:if test="substring-before($encodedArg,'@')">
has the effect of not emitting an optional element when
that text particle (name) was left unspecified. If,
however, you change this to,
<xsl:if test="substring-before($scheme,',')">
then you can emit an empty element for optional pieces
of information that were left unspecified.
Derek Harmon