using pure XSLT 1.0, how can I conditionally assign the node. I am trying something like this but it's not working.
<xsl:variable name="topcall" select="//topcall"/>
<xsl:variable name="focusedcall" select="//focusedcall" />
<xsl:variable name="firstcall" select="$topcall | $focusedcall"/>
For variable firstcall, I am doing the conditional node selection. if there is a topcall then assign it to firstcall, othersie assign firstcall to the focusedcall.
This should work:
<xsl:variable name="firstcall" select="$topcall[$topcall] |
$focusedcall[not($topcall)]" />
In other words, select $topcall if $topcall nodeset is non-empty; $focusedcall if $topcall nodeset is empty.
Re-Update regarding "it can be 5-6 nodes":
Given that there may be 5-6 alternatives, i.e. 3-4 more besides $topcall and $focusedcall...
The easiest solution is to use <xsl:choose>:
<xsl:variable name="firstcall">
<xsl:choose>
<xsl:when test="$topcall"> <xsl:copy-of select="$topcall" /></xsl:when>
<xsl:when test="$focusedcall"><xsl:copy-of select="$focusedcall" /></xsl:when>
<xsl:when test="$thiscall"> <xsl:copy-of select="$thiscall" /></xsl:when>
<xsl:otherwise> <xsl:copy-of select="$thatcall" /></xsl:otherwise>
</xsl:choose>
</xsl:variable>
However, in XSLT 1.0, this will convert the output of the chosen result to a result tree fragment (RTF: basically, a frozen XML subtree). After that, you won't be able to use any significant XPath expressions on $firstcall to select things from it. If you need to do XPath selections on $firstcall later, e.g. select="$firstcall[1]", you then have a few options...
Put those selections into the <xsl:when> or <xsl:otherwise> so that they happen before the data gets converted to an RTF. Or,
Consider the node-set() extension, which converts an RTF to a nodeset, so you can do normal XPath selections from it. This extension is available in most XSLT processors but not all. Or,
Consider using XSLT 2.0, where RTFs are not an issue at all. In fact, in XPath 2.0 you can put normal if/then/else conditionals inside the XPath expression if you want to.
Implement it in XPath 1.0, using nested predicates like
:
select="$topcall[$topcall] |
($focusedcall[$focusedcall] | $thiscall[not($focusedcall)])[not($topcall)]"
and keep on nesting as deep as necessary. In other words, here I took the XPath expression for 2 alternatives above, and replaced $focusedcall with
($focusedcall[$focusedcall] | $thiscall[not($focusedcall)])
The next iteration, you would replace $thiscall with
($thiscall[$thiscall] | $thatcall[not($thiscall)])
etc.
Of course this becomes hard to read, and error-prone, so I would not choose this option unless the others aren't feasible.
Does <xsl:variable name="firstcall" select="($topcall | $focusedcall)[1]"/> do what you want? That is usually the way to take the first node in document order of different types of nodes.
I. XSLT 1.0 Solution This short (30 lines), simple and parameterized transformation works with any number of node types/names:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pRatedCalls">
<call type="topcall"/>
<call type="focusedcall"/>
<call type="normalcall"/>
</xsl:param>
<xsl:variable name="vRatedCalls" select=
"document('')/*/xsl:param[#name='pRatedCalls']/*"/>
<xsl:variable name="vDoc" select="/"/>
<xsl:variable name="vpresentCallNames">
<xsl:for-each select="$vRatedCalls">
<xsl:value-of select=
"name($vDoc//*[name()=current()/#type][1])"/>
<xsl:text> </xsl:text>
</xsl:for-each>
</xsl:variable>
<xsl:template match="/">
<xsl:copy-of select=
"//*[name()
=
substring-before(normalize-space($vpresentCallNames),' ')]"/>
</xsl:template>
</xsl:stylesheet>
When applied to this XML document (do note the document order doesn't coincide with the specified priorities in the pRatedCalls parameter):
<t>
<normalcall/>
<focusedcall/>
<topcall/>
</t>
produces exactly the wanted, correct result:
<topcall/>
when the same transformation is applied to the following XML document:
<t>
<normalcall/>
<focusedcall/>
</t>
again the wanted and correct result is produced:
<focusedcall/>
Explanation:
The names of the nodes that are to be searched for (as many as needed and in order of priority) are specified by the global (typically externally specified) parameter named $pRatedCalls.
Within the body of the variable $vpresentCallNames we generate a space-separated list of names of elements that are both specified as a value of the type attribute of a call elementin the$pRatedCalls` parameter and also are names of elements in the XML document.
Finally, we determine the first such name in this space-separated list and select all elements in the document, that have this name.
II. XSLT 2.0 solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pRatedCalls" select=
"'topcall', 'focusedcall', 'normalcall'"/>
<xsl:template match="/">
<xsl:sequence select=
"//*
[name()=$pRatedCalls
[. = current()//*/name()]
[1]
]"/>
</xsl:template>
</xsl:stylesheet>
Related
I have a list of conditions, and I want to check if the immediate sibling of an element matches any of those conditions.
If these conditions are simple tag names, this is easy enough.
<xsl:param name="tag-list" select="tokenize('img figure table', '\s+')"/>
<xsl:template match="* | text()">
<xsl:variable name="next-name" select="name(following-sibling::*[1])" />
<xsl:if test="$next-name = $tag-list">
<!-- DoSomething -->
</xsl:if>
</xsl:template>
This template will match any element or text node, and will DoSomething if the immediate following sibling of that node is either <img>, <figure> or <table>.
However, I want to check for more complex conditions. How can I only DoSomething for when the template matches against elements with a sibling with a specific attribute, child or text value? I would prefer to do this with a single <xsl:if>, as this list of sibling conditions could get pretty long.
<xsl:param name="tag-list" select="tokenize('img[#src] figure[text()] table[tbody]', '\s+')"/>
<xsl:template match="* | text()">
<xsl:if test="???">
<!-- DoSomething -->
</xsl:if>
</xsl:template>
Define a function representing your complex condition:
<xsl:function name="my:condition" as="xs:boolean">
<xsl:param name="node" as="node()"/>
... your condition goes here ...
</xsl:function>
and then the test is:
<xsl:if test="following-sibling::*[1][my:condition(.)]">...</xsl:if>
If you want to modularize the condition into separate functions then you can of course do so:
<xsl:function name="my:condition" as="xs:boolean">
<xsl:param name="node" as="node()"/>
<xsl:sequence select="my:first($node) and my:second($node) and..."/>
</xsl:function>
If you want to make the logic a bit more dynamic, so the list of conditions is somehow supplied dynamically, then consider using higher order functions.
If you use XSLT 3 consider whether a static parameter with a shadow attribute allows you to construct the right XPath expression on the fly before stylesheet compilation:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:param name="tag-list" static="yes" select="tokenize('img[#src] figure[text()] table[tbody]', '\s+')"/>
<xsl:template match="* | text()">
<xsl:if _test="{string-join($tag-list ! ('following-sibling::*[1][self::' || . || ']'), ' | ')}">
<xsl:comment>DoSomething</xsl:comment>
</xsl:if>
<xsl:next-match/>
</xsl:template>
<xsl:mode on-no-match="shallow-copy"/>
</xsl:stylesheet>
At https://xsltfiddle.liberty-development.net/3NSTbf3 that transforms
<root>
<section><span>test</span><img src="foo.jpeg"/></section>
<section>test<table><tbody>...</tbody></table><span>test</span><figure>...</figure></section>
</root>
into
<root>
<section><!--DoSomething--><span>test</span><img src="foo.jpeg"/></section>
<section><!--DoSomething-->test<table><tbody>...</tbody></table><!--DoSomething--><span>test</span><figure>...</figure></section>
</root>
The example is meant to show the use of _test as a shadow attribute, I have intentionally used a different template content as it seems easier to show the result of the code with the identity transformation plus a comment being output where the xsl:if kicks in.
I have a variable which I set the value. Thereafter, in a loop, I would like to edit this variable and use it in the next iteration. Is there any way I can edit the value in the variable? Thanks!
<xsl:variable name="numberVariable" select="5">
----loop----
$numberVariable = $numberVariable+2
----End loop----
in a loop, I would like to edit this variable and use it in the next iteration. Is there any way I can edit the value in the variable?
The answer is negative:
XSLT is a functional language and this, among other things means that a variable's value, once defined, is immutable.
One can achieve the same effect in a more safe way, by calling another callable unit of the language (template or function) and passing the wanted new value as an argument.
I. Here is a simple example. The following transformation calculates the factorial of the integer, obtained from the string value of the document (top) element of the source XML document:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/*/text()" name="factorial">
<xsl:param name="pN" select="."/>
<xsl:param name="pAccum" select="1"/>
<xsl:value-of select="substring($pAccum, 1 div not($pN > 1))"/>
<xsl:apply-templates select="self::node()[$pN > 1]">
<xsl:with-param name="pN" select="$pN -1"/>
<xsl:with-param name="pAccum" select="$pAccum * $pN"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following source XML document:
<t>9</t>
the wanted, correct result is produced:
362880
In the code above, we see how the value of the parameter $pN is "decreased" from call to call, until it reaches 1, and the value of the parameter $pAccum is multiplied on each call by the value of the parameter $pN.
Do note, however, that we do not modify any parameter at all -- on each call a new instance of the parameter(s) is created, having the same name(s), but living only in the inner scope (call).
II. Often we can avoid the need for recursion: The following XSLT 1.0 transformation calculates and outputs the cubes of the numbers from 1 to 20:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vDoc" select="document('')"/>
<xsl:template match="/*" name="cubes">
<xsl:param name="pN" select="20"/>
<xsl:for-each select=
"($vDoc//node() | $vDoc//node()/#* | $vDoc//namespace::*)
[not(position() > $pN)]">
<xsl:variable name="vM" select="position()"/>
<xsl:value-of select="$vM*$vM*$vM"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on any source XML document (not used), the wanted, correct result is produced:
1
8
27
64
125
216
343
512
729
1000
1331
1728
2197
2744
3375
4096
4913
5832
6859
8000
Do note how the standard XPath function position() is called to produce the "loop counter" :). This is the well known method of Piez.
Starting from XSLT versions 2.0 and above, no such tricks are necessary. One can simply write the following, using an XPath 2.0 range expression:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vDoc" select="document('')"/>
<xsl:template match="/*" name="cubes">
<xsl:param name="pN" select="20"/>
<xsl:for-each select="1 to $pN">
<xsl:value-of select=". * . *."/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Look into the xsl:number element- you can do something like this:
<xsl:variable name="numberVariable">
<xsl:number/>
</xsl:variable>
<xsl:value-of select="$numberVariable * 2"/>
It's difficult to know exactly what you're doing from your question, but hopefully this can point you in the right direction.
I have the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<XmlTest>
<Pictures attr="Pic1">Picture 1</Pictures>
<Pictures attr="Pic2">Picture 2</Pictures>
<Pictures attr="Pic3">Picture 3</Pictures>
</XmlTest>
While this XSL does what is expected (output the attr of the first picture):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/XmlTest">
<xsl:variable name="FirstPicture" select="Pictures[1]">
</xsl:variable>
<xsl:value-of select="$FirstPicture/#attr"/>
</xsl:template>
</xsl:stylesheet>
It seems to be not possible to do the same inside the variable declaration using xsl:copy-of:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:template match="/XmlTest">
<xsl:variable name="FirstPicture">
<xsl:copy-of select="Pictures[1]"/>
</xsl:variable>
<xsl:value-of select="$FirstPicture/#attr"/>
</xsl:template>
</xsl:stylesheet>
Curious:
If I just select "$FirstPicture" instead of "$FirstPicture/#attr" in the second example, it outputs the text node of Picture 1 as expected...
Before you all suggest me to rewrite the code:
This is just a simplified test, my real aim is to use a named template to select a node into the variable FirstPicture and reuse it for further selections.
I hope someone could help me to understand the behavior or could suggest me a proper way to select a node with code which could be easily reused (the decission which node is the first one is complex in my real application). Thanks.
Edit (thanks to Martin Honnen):
This is my working solution example (which additionally uses a seperate template to select the requested picture node), using the MS XSLT processor:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
version="1.0">
<xsl:template match="/XmlTest">
<xsl:variable name="FirstPictureResultTreeFragment">
<xsl:call-template name="SelectFirstPicture">
<xsl:with-param name="Pictures" select="Pictures" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="FirstPicture" select="msxsl:node-set($FirstPictureResultTreeFragment)/*"/>
<xsl:value-of select="$FirstPicture/#attr"/>
<!-- further operations on the $FirstPicture node -->
</xsl:template>
<xsl:template name="SelectFirstPicture">
<xsl:param name="Pictures"/>
<xsl:copy-of select="$Pictures[1]"/>
</xsl:template>
</xsl:stylesheet>
Not nice, that it is in XSLT 1.0 not possible to output a node directly from a template, but with the extra variable it is at least not impossible.
Well with an XSLT 1.0 processor if you do
<xsl:variable name="FirstPicture">
<xsl:copy-of select="Pictures[1]"/>
</xsl:variable>
the variable is a result tree fragment and all you can do with that in pure XSLT 1.0 is output it with copy-of (or value-of). If you want to apply XPath you first need to convert the result tree fragment into a node set, most XSLT 1.0 processors support an extension function for that so try
<xsl:variable name="FirstPictureRtf">
<xsl:copy-of select="Pictures[1]"/>
</xsl:variable>
<xsl:variable name="FirstPicture" select="exsl:node-set(FirstPictureRtf)/Pictures/#attr">
where you define xmlns:exsl="http://exslt.org/common" in your stylesheet.
Note that you will need to check whether your XSLT 1.0 processor supports the EXSLT extension function or a similar one in another namespace (as for instance the various MSXML versions do).
I have an XSLT like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xalan="http://xml.apache.org/xalan">
<xsl:variable name="fooDocument" select="document('fooDocument.xml')"/>
<xsl:template match="/">
<xsl:apply-templates select="$fooDocument//*"/>
</xsl:template>
<xsl:template match="nodeInFooDocument">
<xsl:variable name="valueFromSource" select="//someSourceElement"/>
</xsl:template>
</xsl:transform>
In the second template, which matches nodes in the fooDocument.xml which is loaded with document(), I want to access nodes in the XML source the transformation is executed upon. This does not work with //someSourceElement, because apparently, XPath executes this path in the context of fooDocument.
A first workaround that comes to mind is this:
...
<!-- global variable -->
<xsl:variable name="root" select="/"/>
...
<!-- in the template -->
<xsl:variable name="valueFromSource" select="$root//someSourceElement"/>
...
But I cannot use this workaround, because actually, my variable is selected like this:
<xsl:variable name="valueFromSource" select="xalan:evaluate($someXPathString)"/>
$someXPathString is not crafted in the XSLT file, but loaded from fooDocument (and contains an absolute path like the one used above). Still, I need to somehow change the XPath context back to the XML source. A very hacky workaround I found is this:
<xsl:for-each select="$root[1]">
<xsl:variable name="valueFromSource" select="xalan:evaluate($someXPathString)"/>
</xsl:for-each>
The (useless) for-each loop changes the context back to the main XML source, thus the XPath evaluates correctly. But obviously, this is not an acceptable solution.
Is there a way to do this right, or can someone suggest a better workaround?
Even if you think your attempt with a for-each select="$root" to change the context document is not acceptable this is the right approach. So use that, there is no other way.
Have you considered doing all the computation that constructs $someXPathString using a series of global variables?
<xsl:variable name="fooDocument" select="document('fooDocument.xml')"/>
<xsl:variable name="temp1"
.. some computation using fooDocument ..
</xsl:variable>
<xsl:variable name="temp2"
.. some computation using temp1 ..
</xsl:variable>
<xsl:variable name="someXPathString"
.. some computation using temp2 ..
</xsl:variable>
<xsl:variable name="root" select="xalan:evaluate($someXPathString)"/>
XSLT 1.0
I will need a variable with the following structure, basically I need to construct a variable which actually is a element. I know it looks silly but I need such thing because of the limitation of other stuff.
<xsl:variable name="options">
<xsl:element name="option">
<xsl:attribute name="value">
<xsl:text>test1</xsl:text>
</xsl:attribute>
<xsl:text>test1</xsl:text>
</xsl:element>
</xsl:variable>
Now the problem is, when I call it later in the template with
<xsl:value-of select="$options"/>
the output html only have test1 instead of what I want
<option>test1</option>
So it means the tag is missing. What's the right syntax to do this? Thanks in advance!
You need to make difference between <xsl:value-of> and <xsl:copy-of>
In XSLT 1.0 the <xsl:value-of> instruction creates a text node that contains the string value of the result of evaluating the XPath expression, specified in the select attribute. By definition, the string value of an element is the concatenation (in document order) of all of its descendent text nodes -- this is how you get the string "test1" output.
By contrast:
<xsl:copy-of>
outputs a copy of every node of the node-set that is selected by the XPath expression specified in the select attribute.
Therefore, in order to copy the complete contents of $options, you need to specify:
<xsl:copy-of select="$options" />
Here is a complete example:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vOptions">
<option value="test1">test1</option>
</xsl:variable>
<xsl:template match="/">
<xsl:copy-of select="$vOptions"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on any XML document (not used), the wanted, correct result is produced:
<option value="test1">test1</option>
try:
<xsl:copy-of select="$options" />