XSL Call Template String param as group-by element value - xslt

Lets say I have the following xsl:template element:
<xsl:template name="acct-row-l3">
<xsl:param name="category" as="element()"/>
<xsl:for-each-group select="$category" group-by="concat(ACCT_3, '|', ACCT3_DESC)">
. . .
</xsl:for-each-group>
</xsl:template>
and I want to change it to allow me to pass in an account level so that the group-by="concat(ACCT_3, '|', ACCT3_DESC)" portion of the template can by dynamic. something like this:
group-by="concat($acct, '|', $acct-desc)"
Is there are a way to do this?

In XSLT 3 with function expressions (e.g. as supported in Saxon 10 all editions or Saxon 9.8 or 9.9 EE and PE) the cleanest approach might be to parameterize the group-by as a function:
<xsl:param name="group-by" as="function(item()) as item()*"/>
and
<xsl:for-each-group select="..." group-by="$group-by(.)">...</xsl:for-each-group>
where you can then pass in e.g <xsl:with-param name="group-by" select="function($c) { concat($c/foo, '|', $c/bar) }"/> or <xsl:with-param name="group-by" select="function($c) { concat($c/acc1, '|', $c/desc1) }"/>.

Related

Can I not access my xsl:variable this way when I'm outputting plain text

I'm doing an xslt transform that generates a sql statement for me. The way I'm using below is not working. Is there a way?
<xsl:template match="foo">
<xsl:variable name="var1" select="#att_val1" />
select $var1.* from $var1
</xsl:template>
I know it will work if I do this:
<xsl:template match="foo">
select <xsl:value-of select="#att_val1" />.* from <xsl:value-of select="#att_val1" />
</xsl:template>
In XSLT 1.0, variable references are recognized in XPath expressions, but not in general template text. To evaluate an XPath expression and output the result as a text node in the result tree, use xsl:value-of, as you already know how to do. Example:
<xsl:template match="foo">
<xsl:variable name="var1" select="#att_val1" />
select <xsl:value-of select="$var1"/>.* from <xsl:value-of select="$var1"/>
</xsl:template>
Alternatively, you could build the whole select command in one xsl:value-of with use of the concat() function.
Unless you move to XSLT 3.0 (https://www.w3.org/TR/xslt-30/#text-value-templates) where you can do e.g. <xsl:template match="foo" expand-text="yes">select {#att_val1}.* from {#att_val1}</xsl:template> you will have to use your second option or perhaps a <xsl:template match="foo"><xsl:value-of select="concat('select ', #att_val1, '.* from ', #att_val1)"/></xsl:template>, but there is certainly no way in XSLT 1.0 to avoid the use of xsl:value-of completely.

XSLT How to do a classic for x to y loop?

I need to do a classic for i=0 to N loop, how can it be done in xstl 1.0?
thanks.
<xsl:for-each select="¿¿¿$i=0..5???">
<fo:block>
<xsl:value-of select="$i"/>
</fo:block>
</xsl:for-each>
To give an example, I have
<foo>
<bar>Hey!</bar>
</foo>
And want an output of
Hey!
Hey!
XSLT is a functional programming language and as such it is very different to any procedural languages you already know.
Although for loops are possible in XSLT, they do not make use of the inherent strengths of XSLT (and functional programming in general).
for loops are routinely misused to address problems that are best solved with a functional approach instead (that is, matching templates). In other words, a loop is not really a "classic" in XSLT.
So, you might have to double back, identify the problem you are facing instead of discussion your solution. Then, the XSLT community might be able to suggest a solution that is more functional in nature. It might be that you've fallen victim to the XY problem.
Now, among the things XSLT is inherently good at is recursion. Often, problems that are solved with loops in procedural languages are solved with recursive templates in XSLT.
<xsl:template name="recursive-template">
<xsl:param name="var" select="5"/>
<xsl:choose>
<xsl:when test="$var > 0">
<xsl:value-of select="$var"/>
<xsl:call-template name="recursive-template">
<xsl:with-param name="var" select="$var - 1"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise/>
</xsl:choose>
</xsl:template>
To summarize, I suggest you look at "classic" recursion instead of "classic" for loops. You find more information about exactly this topic in an IBM article here.
EDIT as a response to your edited question. If your problem really boils down to outputting text content twice:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/foo">
<xsl:apply-templates select="bar"/>
<xsl:apply-templates select="bar"/>
</xsl:template>
</xsl:stylesheet>
This is not feasible of course for a dynamic number of iterations.
XSLT 2.0 has <xsl:for-each select="0 to 5"> but in XSLT 1.0 you can only for-each over node sets, not sequences of atomic values. The easiest way I've found to get around this is to use some kind of sufficiently generic selector expression that will match at least as many nodes as you want iterations, e.g.
<xsl:for-each select="/descendant::node()[position() < 7]">
<fo:block>
<xsl:value-of select="position() - 1"/>
</fo:block>
</xsl:for-each>
or if you don't necessarily know there will be at least 6 nodes in the input document then you can use document('') to treat the stylesheet itself as another input document.
<xsl:for-each select="document('')/descendant::node()[position() < 7]">
In both cases the for-each will change the context node, so you'll need to save the outer context in a variable if you need to access it inside the for-each body
<xsl:variable name="dot" select="." />
Use a named template with parameters $i and $n; call the template with parameters {$i = 0, $N = 5}; have the template call itself recursively with parameters {$i + 1, $N} until $i > $N.
Example:
<xsl:template match="/">
<output>
<!-- stuff before -->
<xsl:call-template name="block-generator">
<xsl:with-param name="N" select="5"/>
</xsl:call-template>
<!-- stuff after -->
</output>
</xsl:template>
<xsl:template name="block-generator">
<xsl:param name="N"/>
<xsl:param name="i" select="0"/>
<xsl:if test="$N >= $i">
<!-- generate a block -->
<fo:block>
<xsl:value-of select="$i"/>
</fo:block>
<!-- recursive call -->
<xsl:call-template name="block-generator">
<xsl:with-param name="N" select="$N"/>
<xsl:with-param name="i" select="$i + 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>

parsing string in xslt

I have following xml
<xml>
<xref>
is determined “in prescribed manner”
</xref>
</xml>
I want to see if we can process xslt 2 and return the following result
<xml>
<xref>
is
</xref>
<xref>
determined
</xref>
<xref>
“in prescribed manner”
</xref>
</xml>
I tried few options like replace the space and entities and then using for-each loop but not able to work it out. May be we can use tokenize function of xslt 2.0 but don't know how to use it. Any hint will be helpful.
# JimGarrison: Sorry, I couldn't resist. :-) This XSLT is definitely not elegant but it does (I assume) most of the job:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />
<xsl:variable name="left_quote" select="'<'"/>
<xsl:variable name="right_quote" select="'>'"/>
<xsl:template name="protected_tokenize">
<xsl:param name="string"/>
<xsl:variable name="pattern" select="concat('^([^', $left_quote, ']+)(', $left_quote, '[^', $right_quote, ']*', $right_quote,')?(.*)')"/>
<xsl:analyze-string select="$string" regex="{$pattern}">
<xsl:matching-substring>
<!-- Handle the prefix of the string up to the first opening quote by "normal" tokenizing. -->
<xsl:variable name="prefix" select="concat(' ', normalize-space(regex-group(1)))"/>
<xsl:for-each select="tokenize(normalize-space($prefix), ' ')">
<xref>
<xsl:value-of select="."/>
</xref>
</xsl:for-each>
<!-- Handle the text between the quotes by simply passing it through. -->
<xsl:variable name="protected_token" select="normalize-space(regex-group(2))"/>
<xsl:if test="$protected_token != ''">
<xref>
<xsl:value-of select="$protected_token"/>
</xref>
</xsl:if>
<!-- Handle the suffix of the string. This part may contained protected tokens again. So we do it recursively. -->
<xsl:variable name="suffix" select="normalize-space(regex-group(3))"/>
<xsl:if test="$suffix != ''">
<xsl:call-template name="protected_tokenize">
<xsl:with-param name="string" select="$suffix"/>
</xsl:call-template>
</xsl:if>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:template>
<xsl:template match="*|#*">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="xref">
<xsl:call-template name="protected_tokenize">
<xsl:with-param name="string" select="text()"/>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
Notes:
There is the general assumption that white space only serves as a token delimiter and need not be preserved.
“ and rdquo; seem to be invalid in XML although they are valid in HTML. In the XSLT there are variables defined holding the quote characters. They will have to be adapted once you find the right XML representation. You can also eliminate the variables and put the characters right into the regular expression pattern. It will be significantly simplified by this.
<xsl:analyze-string> does not allow a regular expression which may evaluate into an empty string. This comes as a little problem since either the prefix and/or the proteced token and/or the suffix may be empty. I take care of this by artificially adding a space at the beginning of the pattern which allows me to search for the prefix using + (at least one occurence) instead of * (zero or more occurences).

Name function in XPATH

I have the following elements in my xmi file:
<element1 id= 3 >
<element2 id= 3>
I want to transform them into something like:
<element1 id= 3 name =element2>
<element2 id= 3>
I am using xslt to transform:
<xsl:if test="#id = //*[#id]/#id">
<xsl:sequence
select="fn:createAtt('name',X)" />
</xsl:if>
I want to compare the id of two elements and in case they match then i want to save the name of second element (element2) into the name attribute of first element.
The comparison works ok. The problem is how to read the name of the second element ? I tried to use the name() function but am not able to read exactly that name that matches the comparison.
I would do it like this: first define a key as
<xsl:key name="el-by-id" match="*" use="#id"/>
then I would write a template
<xsl:template match="*[#id]">
<xsl:variable name="same-id" use="key('el-by-id', #id) except ."/>
<xsl:copy>
<xsl:copy-of select="#*"/>
<xsl:if test="$same-id">
<xsl:attribute name="name" select="node-name($same-id[1])"/>
</xsl:if>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
That way you can efficiently reference the elements using a key, then if one element of the same id is found the attribute named name is created. I used the XSLT/XPath 2.0 node-name function, depending on your exact requirements you might want to use <xsl:attribute name="name" select="name($same-id[1])"/> instead.

How do I render a comma delimited list using xsl:for-each

I am rendering a list of tickers to html via xslt and I would like for the list to be comma deliimited. Assuming I was going to use xsl:for-each...
<xsl:for-each select="/Tickers/Ticker">
<xsl:value-of select="TickerSymbol"/>,
</xsl:for-each>
What is the best way to get rid of the trailing comma? Is there something better than xsl:for-each?
<xsl:for-each select="/Tickers/Ticker">
<xsl:if test="position() > 1">, </xsl:if>
<xsl:value-of select="TickerSymbol"/>
</xsl:for-each>
In XSLT 2.0 you could do it (without a for-each) using the string-join function:
<xsl:value-of select="string-join(/Tickers/Ticker, ',')"/>
In XSLT 1.0, another alternative to using xsl:for-each would be to use xsl:apply-templates
<xsl:template match="/">
<!-- Output first element without a preceding comma -->
<xsl:apply-templates select="/Tickers/Ticker[position()=1]" />
<!-- Output subsequent elements with a preceding comma -->
<xsl:apply-templates select="/Tickers/Ticker[position()>1]">
<xsl:with-param name="separator">,</xsl:with-param>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="Ticker">
<xsl:param name="separator" />
<xsl:value-of select="$separator" /><xsl:value-of select="TickerSymbol" />
</xsl:template>
I know you said xsl 2.0 is not an option and it has been a long time since the question was asked, but for all those searching for a posibility to do what you wanted to achieve:
There is an easier way in xsl 2.0 or higher
<xsl:value-of separator=", " select="/Tickers/Ticker/TickerSymbol" />
This will read your /Tickers/Ticker elements and insert ', ' as separator where needed
If there is an easier way to do this I am looking forward for advice
Regards Kevin