XSL:if test or XSL:choose - xslt

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.

Related

Populate a variable with a subtree

in a version="2.0" stylesheet:
the following code produces the correct output
<xsl:variable name="obj">
<xsl:choose>
<xsl:when test="t:ReferencedObjectType='Asset'">
<xsl:value-of select="/t:Flow/t:FHeader/t:Producer/t:Repository" />
</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<xsl:value-of select="$obj"/>
but this one does not
<xsl:variable name="obj">
<xsl:choose>
<xsl:when test="t:ReferencedObjectType='Asset'">
<xsl:value-of select="/t:Flow/t:FHeader/t:Producer" />
</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<xsl:value-of select="$obj/t:Repository"/>
How can I get the second code to run as expected ?
If needed, is there a solution in v3 ?
this code does not run either
<xsl:variable name="obj">
<xsl:choose>
<xsl:when test="t:ReferencedObjectType='Asset'">
<xsl:copy-of select="/t:Flow/t:FHeader/t:Producer" />
</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<xsl:value-of select="$obj/t:Repository"/>
relevant xml input
<Flow>
<FHeader>
<Producer>
<Repository>tests.com</Repository>
</Producer>
</FHeader>
</Flow>
You can simply select <xsl:variable name="obj" select="/t:Flow/t:FHeader/t:Producer/t:Repository[current()/t:ReferencedObjectType='Asset']"/>. Or, as Tim already commented, use xsl:copy-of, also taking into account that you then later on need e.g. $obj/t:Producer/t:Repository to select the right level.
Or learn about the as attribute and use e.g. <xsl:variable name="obj" as="element()*">...<xsl:copy-of select="/t:Flow/t:FHeader/t:Producer"/> ...</xsl:variable>, then you later on can use e.g. $obj/t:Repository.
There is also xsl:sequence to select input nodes instead of copying them, in particular with xsl:variable if you use the as attribute. This might consume less memory.
Furthermore XPath 2 and later have if (condition-expression) then expression else expression conditional expressions at the expression level so you might not need XSLT with xsl:choose/xsl:when but could use the <xsl:variable name="obj" select="if (t:ReferencedObjectType='Asset']) then /t:Flow/t:FHeader/t:Producer else if (...) then ... else ()"/>, that way you would select e.g. an input t:Producer element anyway and if you use the variable you can directly select the t:Repository child.

how to default a value for non existing node in xslt

I have following xml element in the source and would like to map to an element in the target.
Whenever there is no node in the source, I want to default it to a string "None"
--------------------------Source XML----------------------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<Instructions InstructionType="Gen">Some Message</Instructions>
<Instructions InstructionType="Test">Some Other Message</Instructions>
---------------------------Transformation XSL--------------------------------------
<xsl:if test='/ns1:OrderResponse/ns1:OrderResponseBody/ns1:OrderResponseProperties/ns1:Instructions/#InstructionType = "Gen"'>
<xsl:choose>
<xsl:when test='/ns1:OrderResponse/ns1:OrderResponseBody/ns1:OrderResponseProperties/ns1:Instructions[#InstructionType = "Gen"] != ""'>
<ns0:siGen>
<xsl:value-of select='substring(/ns1:OrderResponse/ns1:OrderResponseBody/ns1:OrderResponseProperties/ns1:Instructions[#InstructionType = "Gen"],1.0,199.0)'/>
</ns0:siGen>
</xsl:when>
<xsl:when test="not(/ns1:OrderResponse/ns1:OrderResponseBody/ns1:OrderResponseProperties/ns1:Instructions[#InstructionType = 'Gen'])">
<ns0:siGen>
<xsl:text disable-output-escaping="no">None</xsl:text>
</ns0:siGen>
</xsl:when>
<xsl:otherwise>
<ns0:siGen>
<xsl:text disable-output-escaping="no">None</xsl:text>
</ns0:siGen>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
---------------------------------------------Issue-------------------------------------------------------------
When the source xml doesn't have the the node at all as shown below (commented), I cannot default the value "None" to the target element "ns0:siGen"
<!--Instructions InstructionType="Gen">Some Message</Instructions-->
<Instructions InstructionType="Test">Some Other Message</Instructions>
I dont understand why the below condition is not working:
<xsl:when test="not(/ns1:OrderResponse/ns1:OrderResponseBody/ns1:OrderResponseProperties/ns1:Instructions[#InstructionType = 'Gen'])">
Please advise.
Thanks
Yogi
The very first line in your XSLT sample says this...
<xsl:if
test='/ns1:OrderResponse/ns1:OrderResponseBody/ns1:OrderResponseProperties/ns1:Instructions/#InstructionType = "Gen"'>
i.e. You are testing if there is an Instructions element which has an InstructionType equal to "Gen". So, obviously if you comment this Instructions element, the statement will be false, and so your xsl:choose inside the statement will not be executed.
To simplify things, consider putting the instructions element in a variable
<xsl:variable name="gen"
select="ns1:OrderResponse/ns1:OrderResponseBody/ns1:OrderResponseProperties/ns1:Instructions[#InstructionType = 'Gen']" />
Then you can have a simple xsl:choose to test this
<ns0:siGen>
<xsl:choose>
<xsl:when test="$gen != ''"><xsl:value-of select="$gen" /></xsl:when>
<xsl:otherwise>None</xsl:otherwise>
</xsl:choose>
<ns0:siGen>

build node-set variable from result tree fragment using <xsl:choose>

Is it possible to create a node-set variable from an rtf using xsl:choose (for use in MSXML engine)?
I have the following construct:
<xsl:choose>
<xsl:when test="function-available('msxsl:node-set')">
<xsl:variable name="colorList" select="msxsl:node-set($std:colorList)"/>
<xsl:for-each select="$colorList/color">
tr.testid<xsl:value-of select="#testid"/> {
color:<xsl:value-of select="."/>;
}
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="colorList" select="$std:colorList"/>
<xsl:for-each select="$colorList/color">
tr.testid<xsl:value-of select="#testid"/> {
color:<xsl:value-of select="."/>;
}
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
std:colorList being the tree fragment of course.
The above works fine, and is OK because the code is the same for the two alternatives but is not that large.
But for larger code fragments I wonder whether it is possible to avoid duplicating code by first declaring the variable based on the rtf, and then perform the code; something like
<xsl:variable name="colorList">
<xsl:choose>
<xsl:when test="function-available('msxsl:node-set')">
<xsl:copy-of select="msxsl:node-set($std:colorList)"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$std:colorList"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:for-each select="$colorList/color">
tr.testid<xsl:value-of select="#testid"/> {
color:<xsl:value-of select="."/>;
}
</xsl:for-each>
But this does not work properly: MSXML complains about colorList not being a node-set, so it cannot be used in the xsl:for-each.
XSL transformation failed due to following error:
Expression must evaluate to a node-set.
-->$colorList<--/color
Note that in the working example, this error did not occur because of "copying" std:colorList into the colorList variable. Apparently it is an xsl parsing error, not a runtime one.
Should I use something else than xsl:copy-of? Or is there another way to achieve the same?
In case you wonder, std:colorList contents are as follows:
<std:colorList>
<color testid="111">#FF0000</color>
<color testid="999">#FFFF00</color>
</std:colorList>
Unfortunately in XSLT 1.0, when xsl:variable has contained instructions rather than a select attribute, the result is always an RTF. So your careful attempts to convert the RTF to a node-set come to nothing, because it's converted straight back again.
I'm afraid there's no clean workaround (other than moving to XSLT 2.0, of course). I would suggest structuring the code like this:
<xsl:choose>
<xsl:when test="function-available('msxsl:node-set')">
<xsl:apply-templates select="msxsl:node-set($std:colorList)/color" mode="z"/>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="$std:colorList/color" mode="z"/>
</xsl:otherwise>
</xsl:choose>
<xsl:template match="color" mode="z">
tr.testid<xsl:value-of select="#testid"/> {
color:<xsl:value-of select="."/>;
}
</xsl:template>
Just for the record, I add the final solution below. It is slightly different from what Michael proposed, adding a copy of the RTF to a variable before applying the template.
This is because otherwise MSXML still errors during xsl parsing (apparently it checks the apply-templates select value and concludes it is not correct when it is an RTF instead of a node-set. And, as Michael said, xsl:variable select attribute does just that: converting an RTF to a node-set.
<xsl:choose>
<xsl:when test="function-available('msxsl:node-set')">
<xsl:apply-templates select="msxsl:node-set($std:colorList)/color" mode="addTRclassToCSS"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="colorList" select="$std:colorList"/>
<xsl:apply-templates select="$colorList" mode="addTRclassToCSS"/>
</xsl:otherwise>
</xsl:choose>
<xsl:template match="color" mode="addTRclassToCSS">
tr.testid<xsl:value-of select="#testid"/> {
color:<xsl:value-of select="."/>;
}
</xsl:template>

XSLT: contains() for multiple strings

I have a variable in XSLT called variable_name which I am trying to set to 1, if the Product in question has attributes with name A or B or both A & B.
<xsl:variable name="variable_name">
<xsl:for-each select="product/attributes">
<xsl:if test="#attributename='A' or #attributename='B'">
<xsl:value-of select="1"/>
</xsl:if>
</xsl:for-each>
</xsl:variable>
Is there any way to match multiple strings using the if statement, as mine just matches if A is present or B is present. If both A & B are present, it does not set the variable to 1. Any help on this would be appreciated as I am a newbie in XSLT.
You can use xsl:choose statement, it's something like switch in common programming languages:
Example:
<xsl:variable name="variable_name">
<xsl:for-each select="product/attributes">
<xsl:choose>
<xsl:when test="#attributename='A'">
1
</xsl:when>
<xsl:when test=" #attributename='B'">
1
</xsl:when>
<!--... add other options here-->
<xsl:otherwise>1</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
This will set new variable with name variable_name with the value of attribute product/attributes.
For more info ... http://www.w3schools.comwww.w3schools.com/xsl/el_choose.asp
EDIT: And another way (a little dirty) by OP's request:
<xsl:variable name="variable_name">
<xsl:for-each select="product/attributes">
<xsl:if test="contains(text(), 'A') or contains(text(), 'B')">
1
</xsl:if>
</xsl:for-each>
</xsl:variable>
It will be helpful if you provide the xml you're writing your xslt against.
This might not help...
Is it 'legal' to have two XML element attributes with the same name (eg. <element x="1" x="2" />)?
Is this what you are trying to process? Try parsing your XML file through xmllint or something like it to see if it is valid.
xmllint --valid the-xml-file.xml
My guess is that you will get a 'attribute redefined' error.

XSLT:How to deal with testing the value of an element?

I have an xml file in which there is tag namely, <Gender/> It carries either 'M' or 'F' as data, now my work is to test the value and write <Gender_Tag>Male</Gender_Tag> or <Gender_Tag>Female</Gender_Tag> according to the values M or F respectively .. I tried this code .. It used to work in other circumstances..
All relative paths expressed in a template are evaluated against the current node. Your template match Gender elements, so Gender='M' returns true if there is any Gender's child named 'Gender' with the value 'M'. I guess this is not the case...
Use the dot to express the current node (here a Gender element):
<xsl:template match="root/details/Gender">
<Gender_Tag>
<xsl:choose>
<xsl:when test=".='M'">
<xsl:text>Male</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>Female</xsl:text>
</xsl:otherwise>
</xsl:choose>
</Gender_Tag>
</xsl:template>
EDIT: You may use two templates too
<xsl:template match="root/details/Gender[.='M']">
<Gender_Tag>Male</Gender_Tag>
</xsl:template>
<xsl:template match="root/details/Gender[.='F']">
<Gender_Tag>Female</Gender_Tag>
</xsl:template>
<xsl:template match="root/details/Gender">
<xsl:choose>
<xsl:when test="normalize-space(text())='M'">
<Gender_Tag>Male</Gender_Tag>
</xsl:when>
<xsl:otherwise>
<Gender_Tag>Female</Gender_Tag>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
My example differs in two points from Scoregraphic's:
It uses xsl:choose to ensure, that only one Gender_Tag element is created (that also means, that if the text is not 'M', it is always a Female)
Use of normalize-space() strips white space around the text content of the element.
Untested, but may work...
<xsl:template match="root/details/Gender">
<xsl:if test="text()='M'">
<Gender_Tag>Male</Gender_Tag>
</xsl:if>
<xsl:if test="text()='F'">
<Gender_Tag>Female</Gender_Tag>
</xsl:if>
</xsl:template>
Without seeing XML its hard to be certain, but I think your sample XSLT should be:
<xsl:template match="root/details/Gender">
<xsl:if test=".='M'">
<Gender_Tag><xsl:text>Male</xsl:text></Gender_Tag>
</xsl:if>
<xsl:if test=".='F'">
<Gender_Tag><xsl:text>Female</xsl:text></Gender_Tag>
</xsl:if>
</xsl:template>
Use of choose as per another answer would be better (though I think it should be two explicit when clauses rather than a when and an otherwise)