Having function like this below:
<xsl:function name="fn:get-hierachy">
<xsl:param name="hierarchy" required="yes" as="node()"/>
<xsl:param name="separator0" required="no" as="xs:string"/>
<xsl:value-of select="$hierarchy/*" separator="$separator0"/>
</xsl:function>
I'm getting 'separator0' as a delimiter for output eg.
<xsl:value-of select="fn:get-hierarchy($place, ' > ')"/> result in:
Earth$separator0Africa$separator0Egypt
I'm passing my custom delimiter as a second function argument = ' > ' but it's being ignored and variable name is used instead.
Desired output:
Earth > Africa > Egypt
Is it possible to pass separator argument value as a parameter?
For the separator attribute, you need to use an attribute value template <xsl:value-of select="$hierarchy/*" separator="{$separator0}"/>.
Related
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.
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
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.
I'm using xpath2's index-of value to return the index of current() within a sorted sequence of nodes. Using SAXON, the sorted sequence of nodes are unique, yet index-of returns a sequence of two values.
This does not happen all the time, just very occasionally, but not for any reason I can find. Can someone please explain what is going on?
I have worked up a minimal example based on an example of data that routines gives this odd behavior.
The source data is:
<data>
<student userID="1" userName="user1"/>
<session startedOn="01/16/2012 15:01:18">
</session>
<session startedOn="11/16/2011 13:31:33">
</session>
</data>
My xsl document puts the session nodes into a sorted sequence $orderd at the very top of the root template:
<xsl:template match="/">
<xsl:variable name="nodes" as="node()*" select="/data/session"></xsl:variable>
<xsl:variable name="orderd" as="node()*">
<xsl:for-each select="$nodes">
<xsl:sort select="xs:dateTime(xs:dateTime(concat(substring(normalize-space(#startedOn),7,4),'-',substring(normalize-space(#startedOn),1,2),'-',substring(normalize-space(#startedOn),4,2),'T',substring(normalize-space(#startedOn),12,8)))
)" order="ascending"/>
<xsl:sequence select="."/>
</xsl:for-each>
</xsl:variable>
Since the nodes were already ordered by #startOn but in the opposite order, the sequence $orderd should be the same as document-ordered sequence $nodes, except in reverse order.
When I create output using a for-each statement, I find that somehow the two nodes are seen as identical when tested using index-of.
The code below is used to output data (and comes immediately after the chunk above):
<output>
<xsl:for-each select="$nodes">
<xsl:sort select="position()" order="descending"></xsl:sort>
<xsl:variable name="index" select="index-of($orderd,current())" as="xs:integer*"></xsl:variable>
<xsl:variable name="pos" select="position()"></xsl:variable>
<session reverse-documentOrder="{$pos}" sortedOrder="{$index}"/>
</xsl:for-each>
</output>
As the output (shown below) indicates, the index-of function is returning the sequence (1,2), meaning that it sees both nodes as identical. I have checked the expression used to sort the values, and it produces distinct and well-formed date-Time strings.
<output>
<session reverse=documentOrder="1"
sortedOrder="1 2"/>
<session reverse-documentOrder="2"
sortedOrder="1 2"/>
</output>
Not relying on the generate-id() function, which is XSLT function, but not XPath function, one can write a simple index-of() function that operates on node identity:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vNum3" select="/*/*[3]"/>
<xsl:variable name="vSeq" select="/*/*[1], /*/*[3], /*/*[3]"/>
<xsl:template match="/">
<xsl:sequence select="my:index-of($vSeq, $vNum3)"/>
</xsl:template>
<xsl:function name="my:index-of" as="xs:integer*">
<xsl:param name="pSeq" as="node()*"/>
<xsl:param name="pNode" as="node()"/>
<xsl:for-each select="$pSeq">
<xsl:if test=". is $pNode">
<xsl:sequence select="position()"/>
</xsl:if>
</xsl:for-each>
</xsl:function>
</xsl:stylesheet>
when this transformation is applied on the following XML document:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
the wanted, correct result is returned:
2 3
Explanation: Use of the is operator.
The documentation http://www.w3.org/TR/xpath-functions/#func-index-of of index-of says "The items in the sequence $seqParam are compared with $srchParam under the rules for the eq operator. Values of type xs:untypedAtomic are compared as if they were of type xs:string.". So you are trying to compare untyped element nodes and that means they are compared as strings and both session elements have the same white space only string contents. That way both are compared as equal.
I am not sure what to suggest as I am not sure what you want to achieve but I hope the above explains the result you get.
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]"/>