I working on a site which some if/or statements in XSL and being a little unfamilar with the language i'm not certain how to accomplish:
if [condion one is met] or [condition two is met] then do [action] otherwise do [alternative action]
can anyone offer some examples?
Thanks in advance!
Conditionals in XSLT are either an unary "if":
<xsl:if test="some Boolean condition">
<!-- "if" stuff (there is no "else" here) -->
</xsl:if>
or more like the switch statement of other languages:
<xsl:choose>
<xsl:when test="some Boolean condition">
<!-- "if" stuff -->
</xsl:when>
<xsl:otherwise>
<!-- "else" stuff -->
</xsl:otherwise>
</xsl:choose>
where there is room for as many <xsl:when>s as you like.
Every XPath expression can be evaluated as a Boolean according to a set of rules. These (for the most part) boil down to "if there is something -> true" / "if there is nothing -> false"
the empty string is false
0 is false (so is NaN)
the empty node set is false
the result of false() is false
every other literal value is true (most notably: 'false' is true and '0' is true)
the result of expressions is evaluated with said rules (no surprise here)
Edit: There is of course a more advanced (and more idiomatic) method to control program flow, and that's template matching:
<xsl:template match="node[contains(., 'some text')]">
<!-- output X -->
</xsl:template>
<xsl:template match="node[not(contains(., 'some text'))]">
<!-- output Y -->
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select=".//node" />
</xsl:template>
Writing templates that match specific nodes and using <xsl:apply-templates> to make the XSLT processor choose the appropriate ones is superior to writing complex <xsl:if> or <xsl:choose> constructs.
The above sample is equivalent to the imperative style:
<xsl:template match="/">
<xsl:for-each select=".//node">
<xsl:choose>
<xsl:when test="contains(., 'some text')">
<!-- output X -->
</xsl:when>
<xsl:when test="not(contains(., 'some text'))">
<!-- output Y -->
</xsl:when>
<xsl:choose>
<xsl:for-each>
</xsl:template>
XSLT beginners tend to pick the latter form for its familiarity, but examining template matching instead of using conditionals is worthwhile. (also see.)
XSL has an <xsl:if>, but you're probably looking more for a <xsl:choose> / <xsl:when> / <xsl:otherwise> sequence. Some examples here (near the bottom). Maybe:
<xsl:choose>
<xsl:when test="[conditionOne] or [conditionTwo]">
<!-- do [action] -->
</xsl:when>
<xsl:otherwise>
<!-- do [alternative action] -->
</xsl:otherwise>
</xsl:choose>
The general if statement syntax is
<xsl:if test="expression">
...some output if the expression is true...
</xsl:if>
Not sure if XSL has the else condition but you should be able to test if true then test if false or the other way around.
In this case you would have to use a xsl:choose. It's like using if/else with a final else.
<xsl:choose>
<xsl:when test="condition one or condition two">
<!-- action -->
</xsl:when>
<xsl:otherwise>
<!-- alternative action -->
</xsl:otherwise>
</xsl:choose>
Related
I am using Umbraco 4.5 (yes, I know I should upgrade to 7 now!)
I have an XSLT transform which builds up a list of products which match user filters.
I am making an XSL:variable which is a collection of products from the CMS database.
Each product has several Yes/No properties (radio buttons). Some of these haven't been populated however.
As a result, the following code breaks occasionally if the dataset includes products which don't have one of the options populated with an answer.
The error I get when it transforms the XSLT is "Value was either too large or too small for an Int32". I assume this is the value being passed into the GetPreValueAsString method.
How do I check to see if ./option1 is empty and if so, use a specific integer, otherwise use ./option1
<xsl:variable name="nodes"
select="umbraco.library:GetXmlNodeById(1098)/*
[#isDoc and string(umbracoNaviHide) != '1' and
($option1= '' or $option1=umbraco.library:GetPreValueAsString(./option1)) and
($option2= '' or $option2=umbraco.library:GetPreValueAsString(./option2)) and
($option3= '' or $option3=umbraco.library:GetPreValueAsString(./option3)) and
($option4= '' or $option4=umbraco.library:GetPreValueAsString(./option4))
]" />
Note: you tagged your question as XSLT 2.0, but Umbraco does not use XSLT 2.0, it is (presently) stuck with XSLT 1.0.
$option1= '' or $option1=umbraco.library:GetPreValueAsString(./option1)
There can be multiple causes for your error. A processor is not required to process the or-expression left-to-right or right-to-left, and it is even allowed to always evaluate both expressions, even if the first is true (this is comparable with bit-wise operators (unordered) in other languages, whereas boolean operators (ordered) in those languages typically use early breakout).
Another error can be that your option value in the context node is not empty and is not an integer or empty, in which case your code will always return an error.
You could expand your expression by testing ./optionX, but then you still have the problem of order of evaluation.
That said, how can you resolve it and prevent the error from arising? In XSLT 1.0, this is a bit clumsy (i.e., you cannot define functions and cannot use sequences), but here's one way to do it:
<xsl:variable name="pre-default-option">
<default>1</default>
<default>2</default>
<default>3</default>
<default>4</default>
</xsl:variable>
<xsl:variable name="default-option"
select="exslt:node-set($pre-default-option)" />
<xsl:variable name="pre-selected-option">
<option><xsl:value-of select="$option1" /></option>
<option><xsl:value-of select="$option2" /></option>
<option><xsl:value-of select="$option3" /></option>
<option><xsl:value-of select="$option4" /></option>
</xsl:variable>
<xsl:variable name="selected-option" select="exslt:node-set($pre-selected-option)" />
<xsl:variable name="pre-process-nodes">
<xsl:variable name="selection">
<xsl:apply-templates
select="umbraco.library:GetXmlNodeById(1098)/*"
mode="pre">
<xsl:with-param name="opt-no" select="1" />
</xsl:apply-templates>
</xsl:variable>
<!-- your original code uses 'and', so is only true if all
conditions are met, hence there must be four found nodes,
otherwise it is false (i.e., this node set will be empty) -->
<xsl:if test="count($selection) = 4">
<xsl:copy-of select="$selection" />
</xsl:if>
</xsl:variable>
<!-- your original variable, should now contain correct set, no errors -->
<xsl:variable name="nodes" select="exslt:node-set($pre-process-nodes)"/>
<xsl:template match="*[#isDoc and string(umbracoNaviHide) != '1']" mode="pre">
<xsl:param name="opt-no" />
<xsl:variable name="option"
select="$selected-option[. = string($opt-no)]" />
<!-- gets the child node 'option1', 'option2' etc -->
<xsl:variable
name="pre-ctx-option"
select="*[local-name() = concat('option', $opt-no)]" />
<xsl:variable name="ctx-option">
<xsl:choose>
<!-- empty option param always allowed -->
<xsl:when test="$option = ''">
<xsl:value-of select="$option"/>
</xsl:when>
<!-- if NaN or 0, this will return false -->
<xsl:when test="number($pre-ctx-option)">
<xsl:value-of select="$default-option[$opt-no]"/>
</xsl:when>
<!-- valid number (though you could add a range check as well) -->
<xsl:otherwise>
<xsl:value-of select="umbraco.library:GetPreValueAsString($pre-ctx-option)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- prevent eternal recursion -->
<xsl:if test="4 >= $opt-no">
<xsl:apply-templates select="self::*" mode="pre">
<xsl:with-param name="opt-no" select="$opt-no + 1" />
</xsl:apply-templates>
<!-- the predicate is now ctx-independent and just true/false
this copies nothing if the conditions are not met -->
<xsl:copy-of select="self::*[$option = $ctx-option]" />
</xsl:if>
</xsl:template>
<xsl:template match="*" mode="pre" />
Note (1): I have written the above code by hand, tested only for syntax errors, I couldn't test it because you didn't provide an input document to test it against. If you find errors, by all means, edit my response so that it becomes correct.
Note (2): the above code generalizes working with the numbered parameters. By generalizing it, the code becomes a bit more complicated, but it becomes easier to maintain and to extend, and less error-prone for copy/paste errors.
XML:
<?xml version="1.0" encoding="utf-8"?>
<NewDataSet>
<inc_incident>
<inc_interventionprocedure>
<ProcedureID>CPR</ProcedureID>
</inc_interventionprocedure>
<inc_interventionprocedure>
<ProcedureID>Airway-Endotracheal Intubation</ProcedureID>
</inc_interventionprocedure>
<inc_interventionprocedure>
<ProcedureID>Capnography</ProcedureID>
</inc_interventionprocedure>
</inc_incident>
</NewDataSet>
XSL:
<xsl:if test="starts-with(inc_interventionprocedure/ProcedureID, "Airway")">
<fo:inline>X</fo:inline>
</xsl:if>
<xsl:if test="not(starts-with(inc_interventionprocedure/ProcedureID, "Airway"))">
<fo:inline>X</fo:inline>
</xsl:if>
I would like to show, if any of the nodes starts with "Airway", in the column "YES" with an "X" and if there is none in the column "NO" mark "X". With this xsl:if test both column is marked with Xs.
Result with the xsl:if test shows:
YES NO
Airway Established X X
The solution provided by Tim C should work with a minor change as below.
<xsl:choose>
<xsl:when test="inc_interventionprocedure/ProcedureID[starts-with(., 'Airway')]">
<fo:inline>X</fo:inline>
<fo:inline> </fo:inline>
</xsl:when>
<xsl:otherwise>
<fo:inline> </fo:inline>
<fo:inline>X</fo:inline>
</xsl:otherwise>
</xsl:choose>
Your current test is not well-formed as you have quotation marks embedded inside quotation marks. But the main issue is that starts-with takes a string as a first parameter, not a node-set. In XSLT 1.0, it would use the value of the first node. In XSLT 2.0, you would get an error.
Your expression should really look like this
<xsl:if test="inc_interventionprocedure/ProcedureID[starts-with(., 'Airway')]">
Ideally, you would use an xsl:choose here to avoid writing the whole expression again inside a not
Try this:
<xsl:choose>
<xsl:when test="inc_interventionprocedure/ProcedureID[starts-with(., 'Airway')]">
<fo:inline>YES</fo:inline>
</xsl:when>
<xsl:otherwise>
<fo:inline>NO</fo:inline>
</xsl:otherwise>
</xsl:choose>
This assumes you are positioned on a inc_incident element.
I have a parameter in my XSLT which usually is a proper set of nodes that I apply templates on.
<apply-templates select="$conferences" />
However, sometimes something goes wrong and it comes in as a string. In this case I just want to skip applying templates. But what is the proper way of checking this? I could check that it's not a string of course, but how can I check that the parameter is... "templatable"?
<if test=" ? ">
<apply-templates select="$conferences" />
</if>
Since you're in XSLT 2.0 you can simply do
<xsl:if test="$conferences instance of node()*">
You can do:
<apply-templates select="$conferences/*" />
Which will only apply if there is an XML in it. Strings will not be applied.
If you want to do a condition up front, do something like:
<xsl:choose>
<xsl:when test="count($conferences/*) > 0"> <!-- it is XML -->
<xsl:apply-templates select="$conferences/*" />
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="$conferences" /> <!-- it is not XML -->
</xsl:otherwise>
</xsl:choose>
Tags can be outputted by either directly typing
<div>
<span>complex...</span>
</div>
or using <xsl:element>,
<xsl:element name="div">
<span>complex...</span>
</xsl:element>
My question is how to do this: when x, output <div>, when y, output <a>, when z, output no tag?
One of course can make three templates, or even write ugly code as
<xsl:when ...x >
<![CDATA[ <div> ]]>
</xsl:when>
<span>complex...</span>
<xsl:when ...x >
<![CDATA[ </div> ]]>
</xsl:when>
but is there a way to conditionally provide the value of the name attribute of xsl:element?
I tried this, failed:
<xsl:variable name="a" select="'div'"/>
<xsl:element name="$a">
...
[edited] Forgot to say, XSLT1.0 only
Here's another way to look at it:
<xsl:variable name="content">
<span>complex...</span>
</xsl:variable>
<xsl:choose>
<xsl:when ... x>
<div>
<xsl:copy-of select="$content"/>
</div>
</xsl:when>
<xsl:when ... y>
<a>
<xsl:copy-of select="$content"/>
</a>
</xsl:when>
<xsl:when ... z>
<xsl:copy-of select="$content"/>
</xsl:when>
</xsl:choose>
The name attribute is not expecting a full-fledge XPath expression but simply a string. So, instead of using name="$a" you only have to evaluate the Xpath expression into a string by bracing it with curly braces:
<xsl:element name="{$a}">
As for the conditional creation of the surrounding tag you could do something like this:
<xsl:variable name="tag_name">
<xsl:choose>
<xsl:when test="x">
<xsl:text>div</xsl:text>
</xsl:when>
<xsl:when test="y">
<xsl:text>a</xsl:text>
</xsl:when>
</xsl:choose>
<!-- possibly other checks for different tag names -->
<xsl:variable>
<xsl:choose>
<xsl:when test="$tag_name != ''">
<xsl:element name="$tag_name">
<!-- whatever has to be put into a tagged block (A) -->
</xsl:element>
</xsl:when>
<xsl:otherwise>
<!-- whatever has to be put into a untagged block (B) -->
</xsl:otherwise>
</xsl:choose>
If A and B are equal you could put that into a template.
XSLT doesn't output tags: it outputs nodes to a result tree. Your suggestion of using constructs like <![CDATA[ </div> ]]> is therefore way off the mark: you can't add half a node to a tree.
However, there's no difficulty with conditional generation of elements nodes. If you want to create an element but compute its name conditionally, then in XSLT 2.0 you can do
<xsl:element name="{if (test) then 'a' else 'b'}">
or if you're stuck with 1.0, the much more verbose
<xsl:variable name="elname">
<xsl:choose>
<xsl:when test="test">a</xsl:when>
<xsl:otherwise>b</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="{$elname}"/>
If you want to output an element or nothing depending on a condition, just do
<xsl:if test="test2">
<e/>
</xsl:if>
Is there a way to open a tag and not close it? For example:
<xsl:for-each select=".">
<span>
</xsl:for-each>
This is my code: http://pastebin.com/1Xh49YN0 . As you can see i need to open on a when tag and close it on another when tag (row 43 and 63).
This piece of code is not valid because XSLT is not well formed, but is there a way to do a similar thing? Thank you
Move the content between the two existing xsl:choose elements to a new template
In the xsl:when, open and close your span. Inside the span, call this new template.
Add an xsl:otherwise to the xsl:choose, in this, call the template, without adding a span.
As a general point, try to use xsl:apply-templates a bit more often, rather than xsl:for-each, it should make it easier to understand what is going on.
You can't - XSLT isn't about generating a text file or a sequence of characters, it's about transforming one document tree into another. That the tree eventually gets serialized into a textual format is incidental.
This is why, for example, you can't choose between and in the output file - they're both represent exactly the same document tree.
You can almost always achieve what is intended by refactoring into separate templates that call each other.
You can use disable-output-escaping, but it's generally considered a bit of a hack, and I understand it's deprecated in XSLT 2.
Untested, obviously, but if I understood your original code correctly, this should be pretty close.
<xsl:choose>
<xsl:when test="$pos">
<xsl:for-each select="$s">
<xsl:choose>
<!-- inizio contesto -->
<xsl:when test="$pos[.=position()+$context_number]">
<xsl:text>INIZIO CONTESTO</xsl:text>
</xsl:when>
<!-- fine contesto -->
<xsl:when test="$pos[.=position()-$context_number]">
<xsl:text>FINE CONTESTO</xsl:text>
</xsl:when>
<!-- parola -->
<xsl:when test="$pos[.=position()]">
<span class="word"><xsl:value-of select="."/></span>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
<xsl:if test="position() != last()">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<!-- stampo tutta la riga -->
<xsl:value-of select="$s"/>
</xsl:otherwise>
</xsl:choose>
Hint: <xsl:when test="(. - $current_pos) eq 0"> is equivalent to <xsl:when test=".=$current_pos">. ;-)