xslt in xml attribute - xslt

I have xml element tyle boleean.
<testelement>0</testelement>
I use xslt to transform value to no/yes depending on 0/1 value and it works great
<xsl:choose>
<xsl:when test="./text()='0'">
<xsl:text>No</xsl:text>
</xsl:when>
<xsl:when test="./text()='1'">
<xsl:text>Yes</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:message terminate="yes">The Yes/No value to be translated did not match expected input</xsl:message>
</xsl:otherwise>
</xsl:choose>
The same I try to do with attribute type boolean. Element has maxOcc unbounded.
<element attribute="0">
...
</element>
<element attribute="1">
...
</element>
In xlts:
<xsl:choose>
<xsl:when test="//#attribute='0'">
<xsl:text>No</xsl:text>
</xsl:when>
<xsl:when test="//#attribute='1'">
<xsl:text>Yes</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:message terminate="no">The Yes/No value to be translated did not match expected input</xsl:message>
</xsl:otherwise>
</xsl:choose>
But after i use this code all values are Yes or all values are No depending what is value in first node element. EG if 0 is in first element all values are No and it doesnd matter that in second is 1.
How to transform it properly?
Thanks

all values are Yes or all values are No
depending what is value in first node element
Yes, of course they are. That's because your test:
<xsl:when test="//#attribute='0'">
selects all the attributes in the XML document, and in XSLT 1.0 (which I assume you're using) only the first one's value will be used.
You need first to be in the context of element, then test that specific element's attribute by:
<xsl:when test="#attribute='0'">

Here's a better way to do it:
<xsl:template match="node()[.='0'] | #*[.='0']" mode="toYesNo"/>No</xsl:template>
<xsl:template match="node()[.='1'] | #*[.='1']" mode="toYesNo"/>Yes</xsl:template>
<xsl:template match="node()|#*" mode="toYesNo"/>
<xsl:message terminate="no">The Yes/No value to be translated did not match expected input</xsl:message>
</xsl:template>
and then you can xsl:apply-templates (with mode="toYesNo") selecting any element, attribute, or text node to get the appropriate conversion.
In XSLT 3.0 you can replace the patterns withm for example, match=".[.='0']" to match any kind of node.
Try to avoid using ./text() because it goes wrong when there are comments in your XML. You can nearly always replace it with ..
And of course you error with the attributes was the leading //. You need to be very clear about the difference between absolute path expressions (which start with / and select from the root of the tree) and relative path expressions (which select from the node you are current processing).

Related

XSLT - Exclude Node If It Contains Invalid Data

I'm trying to check the value of a child node and include it in the transformed XML only if it it's value isn't -1. Below is a sample of the input xml.
<root>
<a>-1</a>
<b>valid</b>
<c>valid</c>
<d>valid</d>
</root>
And the relevant section of the XSLT:
<xsl:choose>
<xsl:when test="/a != -1">
<xsl:value-of select="*"></xsl:value-of>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="*[not(a)]"></xsl:value-of>
</xsl:otherwise>
</xsl:choose>
Elements b through c are permitted to have values of -1, so I need to check the <a> element for that value specifically, and only it should be excluded. Currently I do the -1 check in a choose block, and try to exclude it by using <xsl:value-of select="*[not(a)]"></xsl:value-of>. Any help is appreciated.
If you use the standard recursive descent processing model using xsl:apply-templates, then you just need a do-nothing rule
<xsl:template match="a[.=-1]"/>
to exclude these elements.
If you're using some other processing model then you need to show us your code.

Show the separator once only the 2nd value exist

<handlingInstruction>
<handlingInstructionText>CTAC | MARTINE HOEYLAERTS</handlingInstructionText>
</handlingInstruction>
<handlingInstruction>
<handlingInstructionText>PHON | 02/7225235</handlingInstructionText>
</handlingInstruction>
I have The above given xml structure I concatenate them and use a comma as a separator using below code
> <xsl:value-of
> select="concat(handlingInstruction[1]/handlingInstructionText,
> ',',
> handlingInstruction[2]/handlingInstructionText)"/>
I would like to ask how will I make the comma separator appear only once the 2nd exist the shortest way possible. Thanks in advance
If you don't want to use xsl:for-each, try:
<xsl:template match="/root">
<xsl:apply-templates select="handlingInstruction/handlingInstructionText"/>
</xsl:template>
<xsl:template match="handlingInstructionText">
<xsl:value-of select="."/>
<xsl:if test="position()!=last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:template>
(continued from here: https://stackoverflow.com/a/34679465/3016153)
<xsl:for-each select="handlingInstruction">
<xsl:value-of select="handlingInstructionText"/>
<xsl:if test="position()!=last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
This will iterate over all handlingInstruction elements and output the value of the handlingInstructionText element. It will add to the end of each element, if it is not the last one (which the first one would be if there was only one), a comma.
In your example, you only used two handlingInstruction elements. If you want to only use two with this method, do
<xsl:for-each select="handlingInstruction[position()<3]">
<xsl:value-of select="handlingInstructionText"/>
<xsl:if test="position()!=last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
Note the < there. That is actually a less than sign (<), but we can't use that in xml so we use the entity defined for it.
Here is a second way to do it, which avoids the for-each loop.
If you are using xslt version 2, there is a string-join function which could be used like:
<xsl:value-of select="string-join(//handlingInstruction/handlingInstructionText,',')"/>
The string-join method takes a sequence of strings (which the nodes selected will be converted to by taking their content) and concatenates them with the separator. If there is only one string, a separator will not be added.
Alternatively, xslt 2 also provides a separator attribute on the value-of element. Thus
<xsl:value-of select="//handlingInstruction/handlingInstructionText" separator=","/>
produces the same result.

XSL:if test or XSL:choose

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.

How to get value of a child of node-set?

I am using a variable in my XSLT to select correct node and then I want to use this variable to retrieve its child nodes:
<xsl:variable name="CorrectNode">
<xsl:choose>
<xsl:when test="$Formula='14'">
<xsl:value-of select="f:node14" />
</xsl:when>
<xsl:when test="$Formula='15'">
<xsl:value-of select="f:node15" />
</xsl:when>
</xsl:choose>
</xsl:variable>
<Revenue>
<xsl:value-of select="msxsl:node-set($CorrectNode)/f1:revenueValue" />
</Revenue>
However, it does not output anything. If I have:
<xsl:value-of select="msxsl:node-set($CorrectNode)" />
or
<xsl:copy-of select="msxsl:node-set($CorrectNode)" />
then the values or nodes are outputted but how to access its children?
This snippet sets the variable to a result tree fragment consisting of one text node, whose value is the string value of the appropriate element. The variable does not refer to the element itself, only its string value.
But you don't need the RTF and node-set function here at all, as you can do it with a straight select using appropriate predicates, since the test expressions are not context-dependent (a test like $Formula = '15' gives the same value regardless of whether the current context is the f:node15 element or its parent):
<xsl:variable name="CorrectNode"
select="f:node14[$Formula = '14'] | f:node15[$Formula = '15']" />
This way the variable is a reference to the original element, and you can navigate from there using XPath.

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)