XSLT define integer variable for use with table cells - xslt-2.0

I have a simple XSLT variable setup like this:
<xsl:variable name="testId" select="1"/>
Then I try to setup the same variable conditionally like this:
<xsl:variable name="testId">
<xsl:choose>
<xsl:when test="$var='true'"><xsl:value-of select="1"/></xsl:when>
<xsl:when test="$var='false'"><xsl:value-of select="2"/></xsl:when>
</xsl:choose>
</xsl:variable>
I use this variable to address some specific table cells like this td[$testId] (i.e. td[1], td[2]).
I don't know what I am doing wrong but with static declaration it works just fine, while dynamic declaration always returns an empty value (just td) without numbers. What is wrong with the second option?

You can use <xsl:variable name="testId" select="if ($var) then 1 else 2"/> or, if that $var is really bound to a string and not a boolean value then <xsl:variable name="testId" select="if ($var = 'true') then 1 else 2"/>.
Your approach is flawed as it uses xsl:value-of which always creates a text node with the value(s) selected in the select expression and because your use of <xsl:variable name="varName">... content ...</xsl:variable> creates a document fragment node containing the content created inside.
To avoid creating a document node you can use the as attribute with a sequence type e.g. <xsl:variable name="testId" as="xs:integer"><xsl:choose><xsl:when test="$var"><xsl:sequence select="1"/></xsl:when><xsl:otherwise><xsl:sequence select="2"/></xsl:otherwise></xsl:choose></xsl:variable> gives you a variable with an integer value.
The use of select with an if () then .. else .. XPath expression seems much more succinct.

Related

xslt2: sequence of attribute nodes

This is not really a question but an astonishing xslt2 experience that I like to share.
Take the snippet (subtract one set from another)
<xsl:variable name="v" as="node()*">
<e a="a"/>
<e a="b"/>
<e a="c"/>
<e a="d"/>
</xsl:variable>
<xsl:message select="$v/#a[not(.=('b','c'))]"/>
<ee>
<xsl:sequence select="$v/#a[not(.=('b','c'))]"/>
</ee>
What should I expect to get?
I expected a d at the console and
<ee>a d</ee>
at the output.
What I got is
<?attribute name="a" value="a"?><?attribute name="a" value="d"?>
at the console and
<ee a="d"/>
at the output. I should have known to take $v/#a as a sequence of attribute nodes to predict the output.
In order to get what I wanted, I had to convert the sequence of attributes to a sequence of strings like:
<xsl:variable name="w" select="$v/#a[not(.=('b','c'))]" as="xs:string*"/>
Questions:
Is there any use of sequences of attributes (or is it just an interesting effect of the node set concept)?
If so, would I be able to enter statically a sequence of attributes like I am able to enter a sequence of strings: ('a','b','c','d')
Is there any inline syntax to convert a sequence of attributes to a sequence of strings? (In order to achieve the same result omitting the variable w)
It seems to be an elegant way for creating attributes using xsl:sequence. Or would that be a misuse of xslt2, not covered by the standard?
As for "Is there any inline syntax to convert a sequence of attributes to a sequence of strings", you can simply add a step $v/#a[not(.=('b','c'))]/string(). Or use a for $a in $v/#a[not(.=('b','c'))] return string($a) and of course in XPath 3 $v/#a[not(.=('b','c'))]!string().
I am not sure what the question about the "use of sequences of attributes" is about, in particular as it then mentions the XPath 1 concept of node sets. If you want to write a function or template to return some original attribute nodes from an input then xsl:sequence allows that. Of course, inside a sequence constructor like the contents of an element, if you look at 10) in https://www.w3.org/TR/xslt20/#constructing-complex-content, in the end a copy of the attribute is created.
As for creating a sequence of attributes, you can't do that in XPath which can't create new nodes, you can however do that in XSLT:
<xsl:variable name="att-sequence" as="attribute()*">
<xsl:attribute name="a" select="1"/>
<xsl:attribute name="b" select="2"/>
<xsl:attribute name="c" select="3"/>
</xsl:variable>
then you can use it elsewhere, as in
<xsl:template match="/*">
<xsl:copy>
<element>
<xsl:sequence select="$att-sequence"/>
</element>
<element>
<xsl:value-of select="$att-sequence"/>
</element>
</xsl:copy>
</xsl:template>
and will get
<example>
<element a="1" b="2" c="3"/>
<element>1 2 3</element>
</example>
http://xsltfiddle.liberty-development.net/jyyiVhg
XQuery has a more compact syntax and in contrast to XPath allows expressions to create new nodes:
let $att-sequence as attribute()* := (attribute a {1}, attribute b {2}, attribute c {3})
return
<example>
<element>{$att-sequence}</element>
<element>{data($att-sequence)}</element>
</example>
http://xqueryfiddle.liberty-development.net/948Fn56

XSLT: Using a key with a result tree fragment?

Following on from my earlier question, the p elements I want to apply the answer to are actually in a result tree fragment.
How do I make the key function:
<xsl:key name="kRByLevelAndParent" match="p"
use="generate-id(preceding-sibling::p
[not(#ilvl >= current()/#ilvl)][1])"/>
match against p elements in a result tree fragment?
In that answer the key is used via apply-templates:
<xsl:template match="/*">
<list>
<item>
<xsl:apply-templates select="key('kRByLevelAndParent', '')[1]" mode="start">
<xsl:with-param name="pParentLevel" select="$pStartLevel"/>
<xsl:with-param name="pSiblings" select="key('kRByLevelAndParent', '')"/>
</xsl:apply-templates>
</item>
</list>
</xsl:template>
I'd like to pass my result tree fragment as a parameter, and have the key match p elements in that.
Is this the right way to think about it?
There are no result tree fragments in XSLT 2.0 and later, you simply have temporary trees. As for keys, they apply to each document and the key function simply has a third argument to pass in the root node or subtree to search so assuming you have your temporary tree $var you can use key('keyname', key-value-expression, $var) to find elements in $var.

XSLT: How do I get the value of a variable by concatenating a string and another variable?

I am trying to get the value of a variable by concatenating a string and another variable. But the result is a string with the variable name, not the value. So the following code fails since its trying to evaluate a string against a number. Also where the value should be, there is only the name of the variable.
The aim is to make a scrset for images ranging from 300px to maximum 4200. But stopping the srcset before it reaches the maxWidth value. So if an image has a maxWidth of 2000, then the iteration would stop after outputting 1800.
This is the code I have so far:
<xsl:variable name="count" select="14"/>
<xsl:variable name="maxWidth" select="2200"/> <!-- this value will be dynamic depending on each image (taken from an attribute on the image) -->
<xsl:variable name="loopIndex1" select="300"/>
<xsl:variable name="loopIndex2" select="600"/>
<xsl:variable name="loopIndex3" select="900"/>
<xsl:variable name="loopIndex4" select="1200"/>
<xsl:variable name="loopIndex5" select="1500"/>
<xsl:variable name="loopIndex6" select="1800"/>
<xsl:variable name="loopIndex7" select="2100"/>
<xsl:variable name="loopIndex8" select="2400"/>
<xsl:variable name="loopIndex9" select="2700"/>
<xsl:variable name="loopIndex10" select="3000"/>
<xsl:variable name="loopIndex11" select="3300"/>
<xsl:variable name="loopIndex12" select="3600"/>
<xsl:variable name="loopIndex13" select="3900"/>
<xsl:variable name="loopIndex14" select="4200"/>
<xsl:attribute name="srcset">
<xsl:for-each select="1 to $count">
<xsl:variable name="index" select="position()"/>
<xsl:variable name="source">
<xsl:value-of select="concat('loopIndex', $index)"/>
</xsl:variable>
<xsl:if test="$source < $maxWidth">
http://imagescalerserver.com/?url=http://test.com/1108932.jpg&w=<xsl:value-of select="concat($source, ' ')" /> <xsl:value-of select="$source" />w,
</xsl:if>
</xsl:for-each>
</xsl:attribute>
If I remove the test just to get some output, the output would be:
srcset="
http://imagescalerserver.com/?url=http://test.com/1108932.jpg&w=loopIndex1 loopIndex1w,
http://imagescalerserver.com/?url=http://test.com/1108932.jpg&w=loopIndex2 loopIndex2w,
etc
"
The wanted result is:
srcset="
http://imagescalerserver.com/?url=http://test.com/1108932.jpg&w=300 300w,
http://imagescalerserver.com/?url=http://test.com/1108932.jpg&w=600 600w,
etc
"
I also need to not have the comma after the last item. Meaning if http://imagescalerserver.com/?url=http://test.com/1108932.jpg&w=600 600w, was the last output then the comma at the end would not be there, like this:
http://imagescalerserver.com/?url=http://test.com/1108932.jpg&w=600 600w
Ideally I would like to not have to make the loopIndex variables, but rather just increment the value by 300 for a total of 14 iterations, but since variables cant be changed this is the best I've managed. If there is a better way, I'd appreciate to hear about it.
Declare a single variable <xsl:variable name="loopIndex" select="300, 600, 900, ..., 4200"/> (you need to spell out the ... in your code) and then you can set
<xsl:variable name="source" select="$loopIndex[current()]"/>
inside of the for-each.

Explicitly Typed variables in XSL

I'm using XSL's convenience functions for comparisons, gt, lt, ge, le, eq.
I understand these functions won't promote a string to a numerical value when performing comparisons, however I need that cast to be made, and I don't want to clutter my code with lines like
<xsl:when test="xs:integer($variable) lt 250" >
I'd rather make that cast like this (hypothetical of course)
<xsl:variable name="variable" type="xs:integer">
So, is there a means of explicitly casting variable as an numerical type when it is declared/created ?
<xsl:when test="xs:integer($variable) lt 250" >
I'd rather make that cast like this (hypothetical of course)
<xsl:variable name="variable" type="xs:integer">
Use the as attribute -- its purpose is exactly to specify the type of a variable, parameter, template or a function:
<xsl:variable name="variable" as="xs:integer"
select="some-integer-type-expression">

Dynamic 'matches' statement in XSLT

I'm trying to create an xslt function that dynamically 'matches' for an element. In the function, I will pass two parameters - item()* and a comma delimited string. I tokenize the comma delimited string in a <xsl:for-each> select statement and then do the following:
select="concat('$di:meta[matches(#domain,''', current(), ''')][1]')"
Instead of the select statement 'executing' the xquery, it is just returning the string.
How can I get it to execute the xquery?
Thanks in advance!
The problem is that you are wrapping too much of the expression in the concat() function. When that evaluates, it returns a string that would be the XPath expression, rather than evaluating the XPath expression that uses the dynamic string for the REGEX match expression.
You want to use:
<xsl:value-of select="$di:meta[matches(#domain
,concat('.*('
,current()
,').*')
,'i')][1]" />
Although, since you are now evaluating each term separately,rather than having each of those terms in a single regex pattern and selecting the first one, it will now return the first result from each match, rather than the first one from the sequence of matched items. That may or may not be what you want.
If you want the first item from the sequence of matched items, you could do something like this:
<!--Create a variable and assign a sequence of matched items -->
<xsl:variable name="matchedMetaSequence" as="node()*">
<!--Iterate over the sequence of names that we want to match on -->
<xsl:for-each select="tokenize($csvString,',')">
<!--Build the sequence(list) of matched items,
snagging the first one that matches each value -->
<xsl:sequence select="$di:meta[matches(#domain
,concat('.*('
,current()
,').*')
,'i')][1]" />
</xsl:for-each>
</xsl:variable>
<!--Return the first item in the sequence from matching on
the list of domain regex fragments -->
<xsl:value-of select="$matchedMetaSequence[1]" />
You could also put this into a custom function like this:
<xsl:function name="di:findMeta">
<xsl:param name="meta" as="element()*" />
<xsl:param name="names" as="xs:string" />
<xsl:for-each select="tokenize(normalize-space($names),',')">
<xsl:sequence select="$meta[matches(#domain
,concat('.*('
,current()
,').*')
,'i')][1]" />
</xsl:for-each>
</xsl:function>
and then use it like this:
<xsl:value-of select="di:findMeta($di:meta,'foo,bar,baz')[1]"/>

Resources