XLST Run For-Each to check node value and update variable - xslt

I'm trying to run the equivalent of an overall pass/fail.
I want it to check node-x, test if node-x is > or < 2.5, and update a variable based on the results. I want it to run through the whole set to see if any fail...
<xsl:variable name="overallPassFail" select="'true'" />
<xsl:for-each select="MsmtValueNode">
<xsl:variable name="EachValue" select="MsmtValueNode" />
<xsl:if test="$EachValue > 2.5">
<xsl:variable name="overallPassFail" select="'false'" />
</xsl:if>
<xsl:if test="$EachValue < 2.5">
<xsl:variable name="overallPassFail" select="'true'" />
</xsl:if>
</xsl:for-each>
<xsl:choose>
<xsl:when test="($overallPassFail = 'true')">
<div class="status_label_overview status_label_passed_overview">
Passed
</div>
</xsl:when>
<xsl:otherwise>
<div class="status_label_overview status_label_failed_overview">
Failed
</div>
</xsl:otherwise>
</xsl:choose>
It isn't updating the variable "overallPassFail" as I think it would, so looking for some help. Thanks in advance...

Consider the following minimal example:
XML
<root>
<MsmtValueNode>2.5</MsmtValueNode>
<MsmtValueNode>2.4</MsmtValueNode>
<MsmtValueNode>2.6</MsmtValueNode>
</root>
XSLT
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/root">
<div>
<xsl:choose>
<xsl:when test="MsmtValueNode < 2.5">Failed</xsl:when>
<xsl:otherwise>Passed</xsl:otherwise>
</xsl:choose>
</div>
</xsl:template>
</xsl:stylesheet>
Result
<?xml version="1.0" encoding="UTF-8"?>
<div>Failed</div>
Explanation
A comparison of a sequence to a single value is true when there is at least one value in the sequence that has the required magnitude relationship to the single value - see: https://www.w3.org/TR/2010/REC-xpath20-20101214/#id-general-comparisons.

Related

Otherwise-statement in XSLT-choose is not applied/working

I have an XML-document with a type-node whose value is either "1" or "2":
<MyDoc>
<foo>
<bar>
<type>2</type>
</bar>
</foo>
</MyDoc>
I want to set a variable typeBool depending on the value of the type-node, if it is "1" it should be set to false, if it's "2" to true.
With the XSLT-choose-Element it should be possible to test for the current value and set typeBool according to the outcome.
I'm trying to do this with the following construct in XSLT 2.0, but I'm puzzled that the "otherwise"-path is not applied and I get an error that typeBool is not created:
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:variable name="type" select="/MyDoc/foo/bar/type/text()"/>
<xsl:choose>
<xsl:when test="$type = '2'">
<xsl:variable name="typeBool">true</xsl:variable>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="typeBool">false</xsl:variable>
</xsl:otherwise>
</xsl:choose>
<h1><b><xsl:value-of select="$typeBool"/></b></h1>
</xsl:transform>
This is the transformation error I get:
error during xslt transformation:
Source location: line 0, col 0 Description:
No variable with name typeBool exists
As you currently present your problem, an xsl:choose is not needed and it unnecessarily complicates your XSLT code. Your actual problem might be more intricate though.
You can write a template that matches the element you are interested in (for instance, type elements) and then simply select the value of a comparison that will evaluate to either true or false.
XSLT Stylesheet
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:output method="html" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="type">
<h1>
<b>
<xsl:value-of select=". = '2'"/>
</b>
</h1>
</xsl:template>
<xsl:template match="text()"/>
</xsl:transform>
HTML Output
<h1><b>true</b></h1>
Try it online here.
With choose-element
The choose-clause has to be defined inside of the variable-declaration:
<xsl:variable name="type">
<xsl:value-of select="/MyDoc/foo/bar/type/text()"/>
</xsl:variable>
<xsl:variable name="typeBool">
<xsl:choose>
<xsl:when test="$type = '2'">true</xsl:when>
<xsl:otherwise>false</xsl:otherwise>
</xsl:choose>
</xsl:variable>
The conditional also looks cleaner this way.
With XSLT 2.0
#MichaelKay pointed out that in XSLT 2.0 a xpath-conditional can be used, which is even simpler:
<xsl:variable name="type">
<xsl:value-of select="/MyDoc/foo/bar/type/text()"/>
</xsl:variable>
<h1>
<b>
<xsl:value-of select="select="if($type=2) then 'true' else 'false'"/>
</b>
</h1>

XSL - Sum of computed amounts

I just need some help on basic feature of XSL.
I would like to display a sum of amounts previously computed. But I do not know how to do it.
For information the XSL must work with XSLT 1.0, technical limitation on my side.
For instance here is my xml.
<A>
<amount>10</amount>
<rate>4</rate>
</A>
<A>
<amount>-21</amount>
<rate>2</rate>
</A>
<B>
<amount>8</amount>
<rate>1</rate>
</B>
<C>
<amount>7</amount>
<rate>32</rate>
</C>
and I would like to display the sum of each amount multiplied by each associated rate within a Total element.
<Total value="230">
<PositiveTotal>
272
</PositiveTotal>
<NegativeTotal>
-42
</NegativeTotal>
</Total>
I have no idea how to do it.
Thanks in advance
Regards,
One of possible multiple solutions. It will give you an idea, how to solve this.
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="root">
<xsl:variable name="positiveTotal">
<xsl:call-template name="sum">
<xsl:with-param name="items" select="*[not(starts-with(amount,'-') or starts-with(rate, '-'))]"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="negativTotal">
<xsl:call-template name="sum">
<xsl:with-param name="items" select="*[starts-with(amount,'-') or starts-with(rate, '-')]"/>
</xsl:call-template>
</xsl:variable>
<Total value="{$positiveTotal + $negativTotal}">
<PositivTotal>
<xsl:value-of select="format-number($positiveTotal, '0')"/>
</PositivTotal>
<NegativeTotal>
<xsl:value-of select="format-number($negativTotal, '0')"/>
</NegativeTotal>
</Total>
</xsl:template>
<xsl:template name="sum">
<xsl:param name="items" />
<xsl:param name="total" select="0" />
<xsl:choose>
<xsl:when test="not($items)">
<xsl:value-of select="$total"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="sum">
<xsl:with-param name="items" select="$items[position() > 1]" />
<xsl:with-param name="total"
select="$total + ($items[1]/amount * $items[1]/rate)" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
!! Change match="root" to your root-node! Given source-xml is not valid.
There are already many sum-questions! See the Related Box on your right side of screen.
This question has been asked many times and following the links to similar searches on SO should give you lots of ideas.
Computing the sum of computed values in XSLT 2.0 is trivial, but in XSLT 1.0 it isn't easy because there's no such data type in its data model as a set of numbers (sum() only works over a set of nodes). Possible solutions include:
(a) a recursive template call which supplies the running total as a parameter to the template, adds the next value, then calls the template again to process the rest of the list with the new running total
(b) a multiphase transformation where the computed values are placed in XML nodes during one phase, and summed using the sum() function in a second phase
(c) use of the FXSL library which uses xsl:apply-templates to simulate higher-order functions and then provides a fold mechanism which can be specialised to implement summation.
(d) calling out to extension functions in a procedural programming language
(e) upgrading to XSLT 2.0.
I would suggest you do it this way:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/root">
<!-- first pass -->
<xsl:variable name="summands-rtf">
<xsl:for-each select="*">
<value>
<xsl:value-of select="amount * rate" />
</value>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="summands" select="exsl:node-set($summands-rtf)/value" />
<!-- output -->
<Total value="{sum($summands)}">
<PositiveTotal>
<xsl:value-of select="sum($summands[. > 0])" />
</PositiveTotal>
<NegativeTotal>
<xsl:value-of select="sum($summands[. < 0])" />
</NegativeTotal>
</Total>
</xsl:template>
</xsl:stylesheet>
When applied to a well-formed XML input (with a single root element):
XML
<root>
<A>
<amount>10</amount>
<rate>4</rate>
</A>
<A>
<amount>-21</amount>
<rate>2</rate>
</A>
<B>
<amount>8</amount>
<rate>1</rate>
</B>
<C>
<amount>7</amount>
<rate>32</rate>
</C>
</root>
the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<Total value="230">
<PositiveTotal>272</PositiveTotal>
<NegativeTotal>-42</NegativeTotal>
</Total>

XSLT: Replace string with Abbreviations

I would like to know how to replace the string with the abbreviations.
My XML looks like below
<concept reltype="CONTAINS" name="Left Ventricular Major Axis Diastolic Dimension, 4-chamber view" type="NUM">
<code meaning="Left Ventricular Major Axis Diastolic Dimension, 4-chamber view" value="18074-5" schema="LN" />
<measurement value="5.7585187646">
<code value="cm" schema="UCUM" />
</measurement>
<content>
<concept reltype="HAS ACQ CONTEXT" name="Image Mode" type="CODE">
<code meaning="Image Mode" value="G-0373" schema="SRT" />
<code meaning="2D mode" value="G-03A2" schema="SRT" />
</concept>
</content>
</concept>
and I am selecting some value from the xml like,
<xsl:value-of select="concept/measurement/code/#value"/>
Now what I want is, I have to replace cm with centimeter. I have so many words like this. I would like to have a xml for abbreviations and replace from them.
I saw one similar example here.
Using a Map in XSL for expanding abbreviations
But it replaces node text, but I have text as attribute. Also, it would be better for me If I can find and replace when I select text using xsl:valueof select instead of having a separate xsl:template. Please help. I am new to xslt.
I have created XSLT v "1.1". For abbreviations I have created XML file as you have mentioned:
Abbreviation.xml:
<Abbreviations>
<Abbreviation>
<Short>cm</Short>
<Full>centimeter</Full>
</Abbreviation>
<Abbreviation>
<Short>m</Short>
<Full>meter</Full>
</Abbreviation>
</Abbreviations>
XSLT:
<xsl:stylesheet version="1.1" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" method="xml" />
<xsl:param name="AbbreviationDoc" select="document('Abbreviation.xml')"/>
<xsl:template match="/">
<xsl:call-template name="Convert">
<xsl:with-param name="present" select="concept/measurement/code/#value"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="Convert">
<xsl:param name="present"/>
<xsl:choose>
<xsl:when test="$AbbreviationDoc/Abbreviations/Abbreviation[Short = $present]">
<xsl:value-of select="$AbbreviationDoc/Abbreviations/Abbreviation[Short = $present]/Full"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$present"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
INPUT:
as you have given <xsl:value-of select="concept/measurement/code/#value"/>
OUTPUT:
centimeter
You just need to enhance this Abbreviation.xml to keep short and full value of abbreviation and call 'Convert' template with passing current value to get desired output.
Here a little shorter version:
- with abbreviations in xslt file
- make use of apply-templates with mode to make usage shorter.
But with xslt 1.0 node-set extension is required.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="abbreviations_txt">
<abbreviation abbrev="cm" >centimeter</abbreviation>
<abbreviation abbrev="m" >meter</abbreviation>
</xsl:variable>
<xsl:variable name="abbreviations" select="exsl:node-set($abbreviations_txt)" />
<xsl:template match="/">
<xsl:apply-templates select="concept/measurement/code/#value" mode="abbrev_to_text"/>
</xsl:template>
<xsl:template match="* | #*" mode="abbrev_to_text">
<xsl:variable name="abbrev" select="." />
<xsl:variable name="long_text" select="$abbreviations//abbreviation[#abbrev = $abbrev]/text()" />
<xsl:value-of select="$long_text"/>
<xsl:if test="not ($long_text)">
<xsl:value-of select="$abbrev"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

Setting disable-output-escaping="yes" for every xsl:text tag in the xml

say I have the following xml:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/*">
<display>
<xsl:for-each select="logline_t">
<xsl:text disable-output-escaping="yes"><</xsl:text> <xsl:value-of select="./line_1" <xsl:text disable-output-escaping="yes">></xsl:text>
<xsl:text disable-output-escaping="yes"><</xsl:text> <xsl:value-of select="./line_2" <xsl:text disable-output-escaping="yes">></xsl:text>
<xsl:text disable-output-escaping="yes"><</xsl:text> <xsl:value-of select="./line_3" <xsl:text disable-output-escaping="yes">></xsl:text>
</xsl:for-each>
</display>
</xsl:template>
</xsl:stylesheet>
Is there a way to set disable-output-escaping="yes" to all of the xsl:text that appear in the document?
I know there is an option to put
< xsl:output method="text"/ >
and every time something like
& lt;
appears, a < will appear, but the thing is that sometimes in the values of line_1, line_2 or line_3, there is a "$lt;" that I don't want changed (this is, I only need whatever is between to be changed)
This is what I'm trying to accomplish. I have this xml:
<readlog_l>
<logline_t>
<hora>16:01:09</hora>
<texto>Call-ID: 663903<hola>396#127.0.0.1</texto>
</logline_t>
</readlog_l>
And this translation:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/*">
<display>
<screen name="<xsl:value-of select="name(.)"/>">
<xsl:for-each select="logline_t">
< field name="<xsl:for-each select="*"><xsl:value-of select="."/></xsl:for-each>" value="" type="label"/>
</xsl:for-each>
</screen>
</display>
</xsl:template>
</xsl:stylesheet>
I want this to be the output:
<?xml version="1.0"?>
<display>
<screen name="readlog_l">
<field name="16:01:09 Call-ID: 663903<hola>396#127.0.0.1 " value="" type="label">
</screen>
</display>
Note that I need the "<" inside the field name not to be escaped, this is why I can't use output method text.
Also, note that this is an example and the translations are much bigger, so this is why I'm trying to find out how not to write disable-output-escaping for every '<' or '>' I need.
Thanks!
Thanks for clarifying the question. In this case, I'm fairly sure there's no need to disable output escaping. XSLT was designed to accomplish what you're doing:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/*">
<display>
<screen name="{name(.)}">
<xsl:for-each select="logline_t">
<xsl:variable name="nameContent">
<xsl:for-each select="*">
<xsl:if test="position() > 1"><xsl:text> </xsl:text></xsl:if>
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:variable>
<field name="{$nameContent}" value="" type="label" />
</xsl:for-each>
</screen>
</display>
</xsl:template>
</xsl:stylesheet>
I'm a bit unclear on this point:
Note that I need the "<" inside the field name not to be escaped, this is why I can't use output method text.
Which < are you referring to? Is it the < and > around "hola"? If you left those unescaped you would wind up with invalid XML. It also looks like the name attribute in your sample output have a lot of values that aren't in the input XML. Where did those come from?
Given your expected output you don't need d-o-e at all for this. Here is a possible solution that doesn't use d-o-e, and is based on templates rather than for-each:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/*">
<display>
<screen name="{name(.)}">
<xsl:apply-templates select="logline_t"/>
</screen>
</display>
</xsl:template>
<xsl:template match="logline_t">
<field value="" type="label">
<xsl:attribute name="name">
<xsl:apply-templates select="*" mode="fieldvalue"/>
</xsl:attribute>
</field>
</xsl:template>
<xsl:template match="*[last()]" mode="fieldvalue">
<xsl:value-of select="." />
</xsl:template>
<xsl:template match="*" mode="fieldvalue">
<xsl:value-of select="." />
<xsl:text> </xsl:text>
</xsl:template>
</xsl:stylesheet>
If you want to set d-o-e on everything, that suggests you are trying to generate markup "by hand". I don't think that's a particularly good idea (in fact, I think it's a lousy idea), but if it's what you want to do, I would suggest using the text output method instead of the xml output method. That way, no escaping of special characters takes place, and therefore it doesn't need to be disabled.

XSLT get nth node using dynamic number

I need to add CSS classes to each item output by an XSLT template. The class values need to include the position of the node, which is fine, but the position needs to be written in words (classOne, classTwo etc) rather than a digits (class1, class2 etc).
The code I have almost works. It outputs the position correctly as a number, but when I use that position to return the written version of the number it just picks the first one every time, so I always get a class of 'classOne'. If I hard-code the number it works fine.
<xsl:param name="currentPage"/>
<xsl:variable name="numbers" select="my.library:Split('One,Two,Three,Four,Five,Six,Seven,Eight',',')"/>
<xsl:template match="/">
<xsl:apply-templates select="$currentPage/*[starts-with(name(), 'largeImage')]" mode="large" />
</xsl:template>
<xsl:template match="*" mode="large">
<xsl:variable name="index" select="substring(name(), 11)"/>
<div class="class{$numbers/*[$index]}">item</div>
</xsl:template>
Can anyone see how I can get it to convert the $index value into the written equivalent?
Use <div class="class{$numbers/*[position() = $index]}">item</div>. If that does not work then you need to show details of what kind of data your my.library:Split function returns.
No string-splitting and extension functions are necessary to carry out the required processing:
I. XSLT 1.0 Solution
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my" exclude-result-prefixes="my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<my:nums>
<num>one</num>
<num>two</num>
<num>three</num>
<num>four</num>
<num>five</num>
<num>six</num>
<num>seven</num>
<num>eight</num>
<num>nine</num>
<num>ten</num>
</my:nums>
<xsl:variable name="vNums" select="document('')/*/my:nums/*"/>
<xsl:template match="*[starts-with(name(), 'largeImage')]">
<xsl:variable name="vIndex">
<xsl:number count="*[starts-with(name(), 'largeImage')]"
level="any"/>
</xsl:variable>
<div class="class{$vNums[position() = $vIndex]}">item</div>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document (no source XML document was provided!):
<t>
<largeImageA/>
<largeImageB/>
<largeImageC/>
<largeImageD/>
<largeImageE/>
<largeImageF/>
<largeImageG/>
<largeImageH/>
<largeImageI/>
<largeImageJ/>
</t>
produces the wanted, correct result:
<div class="classone">item</div>
<div class="classtwo">item</div>
<div class="classthree">item</div>
<div class="classfour">item</div>
<div class="classfive">item</div>
<div class="classsix">item</div>
<div class="classseven">item</div>
<div class="classeight">item</div>
<div class="classnine">item</div>
<div class="classten">item</div>
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:template match="*[starts-with(name(), 'largeImage')]">
<xsl:variable name="vIndex">
<xsl:number count="*[starts-with(name(), 'largeImage')]"
level="any" format="w"/>
</xsl:variable>
<div class="class{$vIndex}">item</div>
</xsl:template>
</xsl:stylesheet>
when applied on the same XML document (above), the same correct result is produced:
<div class="classone">item</div>
<div class="classtwo">item</div>
<div class="classthree">item</div>
<div class="classfour">item</div>
<div class="classfive">item</div>
<div class="classsix">item</div>
<div class="classseven">item</div>
<div class="classeight">item</div>
<div class="classnine">item</div>
<div class="classten">item</div>
Somewhat more verbose, but you could try something like:
<xsl:template name="int2str">
<xsl:param name="val" required="yes"/>
<xsl:choose>
<xsl:when test="$val= 1">One</xsl:when>
<xsl:when test="$val= 2">Two</xsl:when>
<xsl:when test="$val= 3">Three</xsl:when>
<xsl:when test="$val= 4">Four</xsl:when>
<xsl:when test="$val= 5">Five</xsl:when>
<xsl:when test="$val= 6">Six</xsl:when>
<xsl:when test="$val= 7">Seven</xsl:when>
<xsl:when test="$val= 8">Eight</xsl:when>
</xsl:choose>
</xsl:template>
you can call it with:
<xsl:call-template name="int2str">
<xsl:with-param name="val" select="$index"/>
</xsl:call-template>