Jeni Tennison
> In a fragment from an XML document where the
> contents of the <ErrorText> nodes has a value of "%1
> %2 is not a valid date format.".
>
> My task, (you guessed it) is to replace each %1, %2 etc.. with a
> string built from the correspondong ErrorParameter (formatted nicely
> of course) . Being relatively new to XSLT, I can't think of a way to
> accomplish this in a generic manner. Has anyone got any ideas? I
> can't think of an approach that will actually work!
It's a little tedious because XPath string-handling isn't the best
(roll on regexps), but it's certainly possible. To work through the
string, you need a recursive template; it needs to take the string in
which you're replacing parameter references, and a list of parameters
that you want to replace the references with: <xsl:template name="substituteParameters">
<xsl:param name="string" />
<xsl:param name="parameters" select="/.." />
...
</xsl:template>
You need to work through the string a bit at a time. If the string
doesn't contain any % characters, then you know that you can just
output the string with no changes, so that's the basic test on which
the template hinges: <xsl:template name="substituteParameters">
<xsl:param name="string" />
<xsl:param name="parameters" select="/.." />
<xsl:choose>
<xsl:when test="contains($string, '%')">
...
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="." />
</xsl:otherwise>
</xsl:choose>
</xsl:template> Now, given that you have a string with a % in it, you want to output: - the string up to the % | - the parameter in the position indicated by the number after the % | - the result of calling this same template on the rest of the string
(after the number) |
The first bit's easy - you can use the substring-before() function to
get the bit before the %: <xsl:template name="substituteParameters">
<xsl:param name="string" />
<xsl:param name="parameters" select="/.." />
<xsl:choose>
<xsl:when test="contains($string, '%')">
<xsl:value-of select="substring-before($string, '%')" />
...
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="." />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
The second bit's a little more complicated - you need to get the
number after the %. If you can guarantee that there aren't going to be
more than 9 parameters, then you only need to get the first character
after the %, so I'll assume that's the case (let me know if it's not).
So you can use the following to get the first character after the %: substring(substring-after($string, '%'), 1, 1) That gives you (a string representation of) a number, which you can
use to index into the parameters passed as the value of the
$parameters parameter: $parameters[position() =
substring(substring-after($string, '%'), 1, 1)]
or:
$parameters[number(substring(substring-after($string, '%'), 1, 1))] I'd apply templates to this parameter - you can have a template
matching ErrorParameter elements that does the relevant pretty
formatting: <xsl:template name="substituteParameters">
<xsl:param name="string" />
<xsl:param name="parameters" select="/.." />
<xsl:choose>
<xsl:when test="contains($string, '%')">
<xsl:value-of select="substring-before($string, '%')" />
<xsl:apply-templates
select="$parameters[number(substring(
substring-after($string, '%'),
1, 1))]" />
...
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="." />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Finally, the recursive call to the template needs to pass in the same
set of parameters whilst amending the string to being the 'rest' of
the substring after the '%': <xsl:template name="substituteParameters">
<xsl:param name="string" />
<xsl:param name="parameters" select="/.." />
<xsl:choose>
<xsl:when test="contains($string, '%')">
<xsl:value-of select="substring-before($string, '%')" />
<xsl:apply-templates
select="$parameters[number(substring(
substring-after($string, '%'),
1, 1))]" />
<xsl:call-template name="substituteParameters">
<xsl:with-param name="string"
select="substring(substring-after($string, '%'), 2)" />
<xsl:with-param name="parameters" select="$parameters" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="." />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
To call it, you'd do something like the following (assuming that
SynchError is the current node): <xsl:call-template name="substituteParameters">
<xsl:with-param name="string" select="ErrorText" />
<xsl:with-param name="parameters" select="ErrorParameter" />
</xsl:call-template>
Having said all of that, you would be a lot better off if you could
change the XML structure so that you used elements to indicate where
the parameters should be inserted. Something like: <ErrorText>
<Insert param="1" /> <Insert param="2" /> is not a valid date
format.
</ErrorText>
That way, you could just apply templates to the content of the
ErrorText element and use templates matching Insert elements to insert
the value of the relevant parameter. |