Assume I have variables $a, $b, $c and $d which all hold numbers. I would like to get the smallest (largest) value. My typical XSLT 1.0 approach to this is
<xsl:variable name="minimum">
<xsl:for-each select="$a | $b | $c | $d">
<xsl:sort
select="."
data-type="number"
order="ascending" />
<xsl:if test="position()=1"><xsl:value-of select="." /></xsl:if>
</xsl:for-each>
</xsl:variable>
However, my xslt 1.0 processor complains with
runtime error: file stylesheet.xslt line 106 element for-each
The 'select' expression does not evaluate to a node set.
How can I compute the minimum (maximum) of the given values?
Of course, I could use a long series of <xsl:when> statements and check all combinations, but I'd rather like a smaller solution.
If the variables have statically defined values (not dynamically computed), then something like the following can be done with XSLT 1.0:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:variable name="vA" select="3"/>
<xsl:variable name="vB" select="1"/>
<xsl:variable name="vC" select="9"/>
<xsl:variable name="vD" select="5"/>
<xsl:template match="/">
<xsl:for-each select=
"document('')/*/xsl:variable
[contains('|vA|vB|vC|vD|', concat('|', #name, '|'))]
/#select
">
<xsl:sort data-type="number" order="ascending"/>
<xsl:if test="position() = 1">
Smallest: <xsl:value-of select="."/>
</xsl:if>
<xsl:if test="position() = last()">
Largest: <xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on any XML document (not used), the wanted, correct result is produced:
Smallest: 1
Largest: 9
II. Now, suppose the variables are dynamically defined.
We can do something like this (but need the xxx:node-set() extension function):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common">
<xsl:output method="text"/>
<xsl:variable name="vA" select="number(/*/*[3])"/>
<xsl:variable name="vB" select="number(/*/*[1])"/>
<xsl:variable name="vC" select="number(/*/*[9])"/>
<xsl:variable name="vD" select="number(/*/*[5])"/>
<xsl:template match="/">
<xsl:variable name="vrtfStore">
<num><xsl:value-of select="$vA"/></num>
<num><xsl:value-of select="$vB"/></num>
<num><xsl:value-of select="$vC"/></num>
<num><xsl:value-of select="$vD"/></num>
</xsl:variable>
<xsl:for-each select="ext:node-set($vrtfStore)/*">
<xsl:sort data-type="number" order="ascending"/>
<xsl:if test="position() = 1">
Smallest: <xsl:value-of select="."/>
</xsl:if>
<xsl:if test="position() = last()">
Largest: <xsl:value-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the following XML document:
<nums>
<num>01</num>
<num>02</num>
<num>03</num>
<num>04</num>
<num>05</num>
<num>06</num>
<num>07</num>
<num>08</num>
<num>09</num>
<num>10</num>
</nums>
the wanted, correct result is produced:
Smallest: 1
Largest: 9
This XSLT 1.0 solution uses recursive templates to parse a delimited list of values to return the min/max value from the list.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:variable name="a" select="'3'"/>
<xsl:variable name="b" select="'1'"/>
<xsl:variable name="c" select="'9'"/>
<xsl:variable name="d" select="'5'"/>
<xsl:template match="/">
<xsl:text>
Smallest: </xsl:text>
<xsl:call-template name="min">
<xsl:with-param name="values" select="concat($a,',',$b,',',$c,',',$d)"/>
</xsl:call-template>
<xsl:text>
Largest: </xsl:text>
<xsl:call-template name="max">
<xsl:with-param name="values" select="concat($a,',',$b,',',$c,',',$d)"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="min">
<xsl:param name="values" />
<xsl:param name="delimiter" select="','"/>
<xsl:param name="min"/>
<xsl:variable name="currentValue" >
<xsl:choose>
<xsl:when test="contains($values, $delimiter)">
<xsl:value-of select="substring-before($values,$delimiter)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$values"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="minimumValue">
<xsl:choose>
<xsl:when test="$min and $min > $currentValue">
<xsl:value-of select="$currentValue"/>
</xsl:when>
<xsl:when test="$min">
<xsl:value-of select="$min"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$currentValue" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<xsl:when test="substring-after($values,$delimiter)">
<xsl:call-template name="min">
<xsl:with-param name="min" select="$minimumValue" />
<xsl:with-param name="values" select="substring-after($values,$delimiter)" />
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$minimumValue" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="max">
<xsl:param name="values" />
<xsl:param name="delimiter" select="','"/>
<xsl:param name="max"/>
<xsl:variable name="currentValue" >
<xsl:choose>
<xsl:when test="contains($values, $delimiter)">
<xsl:value-of select="substring-before($values,$delimiter)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$values"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="maximumValue">
<xsl:choose>
<xsl:when test="$max and $currentValue > $max">
<xsl:value-of select="$currentValue"/>
</xsl:when>
<xsl:when test="$max">
<xsl:value-of select="$max"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$currentValue" />
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<xsl:when test="substring-after($values,$delimiter)">
<xsl:call-template name="max">
<xsl:with-param name="max" select="$maximumValue" />
<xsl:with-param name="values" select="substring-after($values,$delimiter)" />
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$maximumValue" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
When executed, produces the following output:
Smallest: 1
Largest: 9
I've never had to do this in 1.0 (I use 2.0), but you could do this:
<xsl:variable name="minimum">
<xsl:choose>
<xsl:when test="$b > $a and $c > $a and $d > $a"><xsl:value-of select="$a"/></xsl:when>
<xsl:when test="$a > $b and $c > $b and $d > $b"><xsl:value-of select="$b"/></xsl:when>
<xsl:when test="$b > $c and $a > $c and $d > $c"><xsl:value-of select="$c"/></xsl:when>
<xsl:when test="$b > $d and $c > $d and $a > $d"><xsl:value-of select="$d"/></xsl:when>
</xsl:choose>
</xsl:variable>
There's got to be a better way though.
Related
how to split a node value in XSLT 1.0?
<mark>1,2</mark>
i need to perform some operations in the for loop with each value of the output of split.
<xsl:for-each select="">
</xsl:for-each>
How to do this?
I. XSLT 1.0 solution:
Here is one way to do this in XSLT 1.0 using only the xxx:node-set() extension function:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="mark">
<xsl:variable name="vrtfSplit">
<xsl:apply-templates/>
</xsl:variable>
<xsl:for-each select="ext:node-set($vrtfSplit)/*">
<processedItem>
<xsl:value-of select="10 * ."/>
</processedItem>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()" name="split">
<xsl:param name="pText" select="."/>
<xsl:if test="string-length($pText) >0">
<item>
<xsl:value-of select=
"substring-before(concat($pText, ','), ',')"/>
</item>
<xsl:call-template name="split">
<xsl:with-param name="pText" select=
"substring-after($pText, ',')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied to the following XML document:
<mark>1,2,3,4,5</mark>
The wanted, correct output (each item multiplied by 10) is produced:
<processedItem>10</processedItem>
<processedItem>20</processedItem>
<processedItem>30</processedItem>
<processedItem>40</processedItem>
<processedItem>50</processedItem>
II. XSLT 2.0 solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="mark">
<xsl:for-each select="tokenize(., ',')">
<processedItem>
<xsl:sequence select="10*xs:integer(.)"/>
</processedItem>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The explaination by Dimitre Novatchev is awesome, but we can also do it in much more simpler way without using node-set() function have a look:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="delimiter">
<xsl:text>,</xsl:text>
</xsl:variable>
<xsl:template match="mark">
<xsl:variable name="dataList">
<xsl:value-of select="."/>
</xsl:variable>
<xsl:call-template name="processingTemplate">
<xsl:with-param name="datalist" select="$dataList"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="processingTemplate">
<xsl:param name="datalist"/>
<xsl:choose>
<xsl:when test="contains($datalist,$delimiter) ">
<xsl:element name="processedItem">
<xsl:value-of select="substring-before($datalist,$delimiter) * 10"/>
</xsl:element>
<xsl:call-template name="processingTemplate">
<xsl:with-param name="datalist" select="substring-after($datalist,$delimiter)"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="string-length($datalist)=1">
<xsl:element name="processedItem">
<xsl:value-of select="$datalist * 10"/>
</xsl:element>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
In 1.0 you need to write a recursive template - except you don't, because it's already been written. Download the str:tokenize template from http://www.exslt.org.
If you can use exslt there's a tokenize() function that will do this nicely.
node-set str:tokenize(string, string?)
See http://www.exslt.org/str/functions/tokenize/
This code will split a delimited string in XSLT 1.0
(It will work for 2.0, but don't use the node-set.)
It will also optionally suppress empty elements in the string
or optionally upper case the elements.
<!-- Example delimited string. -->
<xsl:variable name="delimitedString" select="'a, b, c, , , d, e, f, g'"/>
<!-- Create a node set where each node contains one of the elements from the
delimited string. -->
<xsl:variable name="splitNodes">
<xsl:call-template name="getNodeListFromDelimitedList">
<xsl:with-param name="inStrList" select="$delimitedString"/>
<xsl:with-param name="delimiter" select="','"/>
<xsl:with-param name="suppressEmptyElements" select="false()"/>
<xsl:with-param name="upperCase" select="false()"/>
<xsl:with-param name="allTrim" select="false()"/>
</xsl:call-template>
</xsl:variable>
<!-- Use this for XSLT 1.0 only. -->
<xsl:variable name="splitNodesList" select="msxml:node-set($splitNodes)"/>
<!-- Use the split node list to do something. For example, create a string like
the delimited string, but without the delimiters. -->
<xsl:variable name="nonDelimitedString">
<xsl:for-each select="$splitNodesList/element">
<xsl:value-of select="."/>
</xsl:for-each>
</xsl:variable>
<!-- Do something with the nonDelimitedString. -->
<!--
*****************************************************************************************
This template converts a delimited string list to a node list as follows:
Each value in the delimited input string is extracted from the string. Then, a node is
created to contain the value. The name of the node is 'element', and it is added to the
list. To use this template, create an variable and call this template from within the variable.
If you are using XSLT version 1.0, convert the node list to a node set using the node-set
function. You can access the element as follows: $SomeVariableNodeSet/element
*****************************************************************************************
-->
<xsl:template name="getNodeListFromDelimitedList">
<!-- Delimited string with one or more delimiters. -->
<xsl:param name="inStrList"/>
<!-- The delimiter. -->
<xsl:param name="delimiter" select="'|'"/>
<!-- Set to true to suppress empty elements from being added to node list. Otherwise, set to 'false'.-->
<xsl:param name="suppressEmptyElements" select="true()"/>
<!-- Set to true to upper case the strings added to the node list. -->
<xsl:param name="upperCase" select="false()"/>
<!-- Set to true to left trim and right trim the strings added to the nodes list. -->
<xsl:param name="allTrim" select="false()"/>
<xsl:variable name="element">
<xsl:choose>
<xsl:when test="contains($inStrList,$delimiter)">
<xsl:value-of select="substring-before($inStrList,$delimiter)"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$inStrList"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<!-- Write out the element based on parameters. -->
<xsl:if test="not($suppressEmptyElements) or normalize-space($element) != ''">
<!-- Put the element in the list. -->
<xsl:element name="element">
<xsl:choose>
<xsl:when test="$allTrim">
<xsl:call-template name="all-trim">
<xsl:with-param name="inStr" select="$element"/>
<xsl:with-param name="upperCase" select="$upperCase"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="$upperCase">
<xsl:value-of select="translate($element, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$element"/>
</xsl:otherwise>
</xsl:choose>
</xsl:element>
</xsl:if>
<xsl:if test="contains($inStrList,$delimiter)">
<!-- Call template recursively to process the next element. -->
<xsl:call-template name="getNodeListFromDelimitedList">
<xsl:with-param name="inStrList" select="substring-after($inStrList,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
<xsl:with-param name="suppressEmptyElements" select="$suppressEmptyElements"/>
<xsl:with-param name="upperCase" select="$upperCase"/>
<xsl:with-param name="allTrim" select="$allTrim"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<!--
*****************************************************************************************
This template trims the blanks from the left and right sides of a string.
*****************************************************************************************
-->
<xsl:template name="all-trim">
<!-- The string that you want to all trim. -->
<xsl:param name="inStr"/>
<xsl:param name="upperCase" select="false()"/>
<xsl:variable name="leftTrimmed">
<xsl:call-template name="left-trim">
<xsl:with-param name="inStr" select="$inStr"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="rightTrimmed">
<xsl:call-template name="right-trim">
<xsl:with-param name="inStr" select="$leftTrimmed"/>
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<xsl:when test="$upperCase">
<xsl:value-of select="translate($rightTrimmed, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$rightTrimmed"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--
*****************************************************************************************
This template trims the blanks from the left side of a string.
*****************************************************************************************
-->
<xsl:template name="left-trim">
<!-- The string you want to left trim. -->
<xsl:param name ="inStr"/>
<xsl:choose>
<xsl:when test="$inStr!=''">
<xsl:variable name="temp" select="substring($inStr, 1, 1)"/>
<xsl:choose>
<xsl:when test="$temp=' '">
<xsl:choose>
<xsl:when test="string-length($inStr) > 1">
<xsl:call-template name="left-trim">
<xsl:with-param name="inStr" select="substring($inStr, 2, string-length($inStr)-1)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="''"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$inStr"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="''"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--
*****************************************************************************************
This template trims the blanks from the right side of a string.
*****************************************************************************************
-->
<xsl:template name="right-trim">
<!-- The string you want to right trim. -->
<xsl:param name ="inStr"/>
<xsl:choose>
<xsl:when test="$inStr!=''">
<xsl:variable name="temp" select="substring($inStr, string-length($inStr), 1)"/>
<xsl:choose>
<xsl:when test="$temp=' '">
<xsl:choose>
<xsl:when test="string-length($inStr) > 1">
<xsl:call-template name="right-trim">
<xsl:with-param name="inStr" select="substring($inStr, 1, string-length($inStr)-1)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="''"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$inStr"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="''"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Based on #Abhinav solution I just simplified recursive solution to work with general strings. My input string which I need to split is "GEN_EME2_G9_3311|A55;GEN_EME2_G9_3312|A55;foooo_3312|A42"
<xsl:variable name="delimiter">
<xsl:text>;</xsl:text>
</xsl:variable>
<xsl:template name="fooTemplate">
...
<xsl:choose>
<xsl:when test="$conditionlink != ''">
<xsl:call-template name="processconditionlinktemplate">
<xsl:with-param name="datalist" select="$conditionlink"/>
</xsl:call-template>
</xsl:when>
</xsl:choose>
...
</xsl:template>
<xsl:template name="processconditionlinktemplate">
<xsl:param name="datalist"/>
<xsl:choose>
<xsl:when test="contains($datalist,$delimiter)">
<xsl:element name="processedItem">
<xsl:value-of select="substring-before($datalist,$delimiter)"/>
</xsl:element>
<xsl:call-template name="processconditionlinktemplate">
<xsl:with-param name="datalist" select="substring-after($datalist,$delimiter)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:element name="processedItem">
<xsl:value-of select="$datalist"/>
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
I have a string of single values and ranges, and I want to split them and put them in order of single values together (comma separated) and ranges together (comma separated), using XSLT1.0. I don't want the result in form of XML nodes, I do want the result on the same line. I have found that existing solutions here on Stack Exchange works, but it puts them on different lines as XML nodes.
The string is:
Input: <mark>9, 11, 345-900, 100-200, 1023, 200-400, 99</mark>
Desired Output: 9, 11, 1023, 99, 345-900, 100-200, 200-400
Code:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="mark">
<xsl:call-template name="tokenizeStringWithoutHyphen">
<xsl:with-param name="list" select="."/>
<xsl:with-param name="delimiter" select="','"/>
</xsl:call-template>
<xsl:call-template name="tokenizeStringWithHyphen">
<xsl:with-param name="list" select="."/>
<xsl:with-param name="delimiter" select="','"/>
</xsl:call-template>
</xsl:template>
<!--############################################################-->
<!--## Template to tokenize strings ##-->
<!--############################################################-->
<xsl:template name="separatestrings">
<xsl:param name="list"/>
<xsl:param name="delimiter"/>
</xsl:template>
<xsl:template name="tokenizeStringWithHyphen">
<!--passed template parameter -->
<xsl:param name="list"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<xsl:variable name="stringContainsHyphen" select="contains(substring-before($list,$delimiter),'-')"/>
<!--<test><xsl:value-of select="$stringContainsHyphen"/></test>-->
<xsl:choose>
<xsl:when test="$stringContainsHyphen = true()">
<!-- get everything in front of the first delimiter -->
<xsl:value-of select="normalize-space(substring-before($list,$delimiter))"/>
</xsl:when>
</xsl:choose>
<xsl:call-template name="tokenizeStringWithHyphen">
<!-- store anything left in another variable -->
<xsl:with-param name="list" select="substring-after($list,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="$list = ''">
<xsl:text/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$list"/>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="tokenizeStringWithoutHyphen">
<!--passed template parameter -->
<xsl:param name="list"/>
<xsl:param name="delimiter"/>
<xsl:choose>
<xsl:when test="contains($list, $delimiter)">
<xsl:variable name="stringContainsHyphen" select="contains(substring-before($list,$delimiter),'-')"/>
<!--<test><xsl:value-of select="$stringContainsHyphen"/></test>-->
<xsl:choose>
<xsl:when test="$stringContainsHyphen = false()">
<!-- get everything in front of the first delimiter -->
<xsl:value-of select="normalize-space(substring-before($list,$delimiter))"/>
</xsl:when>
</xsl:choose>
<xsl:call-template name="tokenizeStringWithoutHyphen">
<!-- store anything left in another variable -->
<xsl:with-param name="list" select="substring-after($list,$delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I don't want the result in form of XML nodes, I do want the result on
the same line.
If you want to sort the values, you must tokenize them into nodes first. Then sort the nodes and output them back as text values in a single line:
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:strip-space elements="*"/>
<xsl:template match="mark">
<xsl:variable name="tokens">
<xsl:call-template name="tokenize"/>
</xsl:variable>
<xsl:for-each select="exsl:node-set($tokens)/token">
<xsl:sort select="number(contains(., '-'))" data-type="number" order="ascending"/>
<xsl:value-of select="." />
<xsl:if test="position()!=last()">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="tokenize">
<xsl:param name="text" select="."/>
<xsl:param name="delimiter" select="', '"/>
<xsl:choose>
<xsl:when test="contains($text, $delimiter)">
<token>
<xsl:value-of select="substring-before($text, $delimiter)"/>
</token>
<!-- recursive call -->
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after($text, $delimiter)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<token>
<xsl:value-of select="$text"/>
</token>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I've searched far and wide for an example of this, and have so far had no luck in applying a sum template to make my XSTL work.
This is the XML (number of lines varies on each planfeature)
<PlanFeatures>
<PlanFeature name="Line0001">
<CoordGeom>
<Line>
<Start pntRef="7540">5605 8950 1020</Start>
<End pntRef="7541">5605 8951 1019</End>
</Line>
<Line>
<Start pntRef="7541">5605 8951 1019</Start>
<End pntRef="7542">5605 8947 1019</End>
</Line>
<Line>
<Start pntRef="7542">5605 8947 1019</Start>
<End pntRef="7543">5605 8940 1011</End>
</Line>
<Line>
<Start pntRef="7543">5605 8940 1011</Start>
<End pntRef="7544">5605 8931 1020</End>
</Line>
</CoordGeom>
</PlanFeature>
</PlanFeatures>
This is where I'm at with the XSL, which uses a recursive call template to calculate the distance of each line segment.
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:landxml="http://www.landxml.org/schema/LandXML-1.2" xmlns:hexagon="http://xml.hexagon.com/schema/HeXML-1.5" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" version="1.0" encoding="UTF-16" indent="no" omit-xml-declaration="yes"/>
<xsl:variable name="XML" select="/"/>
<xsl:variable name="fileExt" select="'txt'"/>
<xsl:variable name="fileDesc" select="'line distance report'"/>
<xsl:template match="/">
<xsl:for-each select="$XML">
<xsl:for-each select="landxml:LandXML/landxml:PlanFeatures/landxml:PlanFeature">
<xsl:value-of select="#name"/><xsl:text>::</xsl:text>
<xsl:for-each select="landxml:CoordGeom/landxml:Line">
<xsl:value-of select="landxml:Start/#pntRef"/><xsl:text>-</xsl:text>
<xsl:variable name="lista" select="landxml:Start"/>
<xsl:variable name="x1" select="substring-before($lista,' ')"/>
<xsl:variable name="yt1" select="substring-after($lista,' ')"/>
<xsl:variable name="y1" select="substring-before($yt1,' ')"/>
<xsl:variable name="z1" select="substring-after($yt1,' ')"/>
<xsl:variable name="listb" select="landxml:End"/>
<xsl:value-of select="landxml:End/#pntRef"/><xsl:text>: </xsl:text>
<xsl:variable name="x2" select="substring-before($listb,' ')"/>
<xsl:variable name="yt2" select="substring-after($listb,' ')"/>
<xsl:variable name="y2" select="substring-before($yt2,' ')"/>
<xsl:variable name="z2" select="substring-after($yt2,' ')"/>
<xsl:variable name="seg" select= "((($x2 - $x1)*($x2 - $x1))+(($y2 - $y1)*($y2 - $y1))+(($z2 - $z1)*($z2 - $z1)))"/>
<xsl:call-template name="root">
<xsl:with-param name="X" select="$seg"/>
</xsl:call-template>
<xsl:text>, </xsl:text>
</xsl:for-each>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
<xsl:template name="root">
<xsl:param name="X"/>
<xsl:param name="xn" select="0"/>
<xsl:param name="xn_1" select="($X+1) div 2"/>
<xsl:choose>
<xsl:when test="string(number($X)) = 'NaN'">
<xsl:value-of select=" ' ' "/>
</xsl:when>
<xsl:when test="($xn_1 - $xn) * ($xn_1 - $xn) < 0.00000001">
<xsl:value-of select='format-number($xn_1, "#.000")'/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="root">
<xsl:with-param name="X" select="$X"/>
<xsl:with-param name="xn" select="$xn_1"/>
<xsl:with-param name="xn_1" select="($xn_1 + ($X div $xn_1)) div 2"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I need to sum the value of X (distance) from the root call template, to create a value which represents the sum of each line segment. I think I need to use a match template, but so far it hand even got close to working.
Currently exporting LINEID::StartPt-EndPt: dist, StartPt-EndPt: dist, etc. I need the sum of the 'dist' to be shown at the end of each line as well. As below
Line0001::7540-7541: 1.414, 7541-7542: 2.000, 7542-7543: 12.042, 7543-7544: 12.720
but I would like
Line0001::7540-7541: 1.414, 7541-7542: 2.000, 7542-7543: 12.042, 7543-7544: 12.728 -- 28.184
Any help would be appreciated... the examples on this site have helped me so much already, but I just can't seem to get through this roadblock.
Cheers,
Chris
You can accomplish this with some relatively simple recursion and parameter passing. Try replacing your first template with these four templates:
<xsl:template match="/">
<xsl:for-each select="$XML">
<xsl:apply-templates
select="landxml:LandXML/landxml:PlanFeatures/landxml:PlanFeature" />
</xsl:for-each>
</xsl:template>
<xsl:template match ="landxml:PlanFeature">
<xsl:value-of select="concat(#name, '::')" />
<xsl:apply-templates select="landxml:CoordGeom/landxml:Line[1]" />
<xsl:text>
</xsl:text>
</xsl:template>
<xsl:template match="landxml:Line">
<xsl:param name="total" select="0" />
<xsl:value-of
select="concat(landxml:Start/#pntRef, '-', landxml:End/#pntRef, ': ')"/>
<xsl:variable name="len">
<xsl:call-template name="root">
<xsl:with-param name="X">
<xsl:call-template name="seg" />
</xsl:with-param>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$len"/>
<xsl:variable name="next" select="following-sibling::landxml:Line[1]" />
<xsl:variable name="newTot" select="$total + $len" />
<xsl:choose>
<xsl:when test="$next">
<xsl:text>, </xsl:text>
<xsl:apply-templates select="$next">
<xsl:with-param name="total" select="$newTot" />
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(' -- ', format-number($newTot, '#.000'))" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="seg">
<xsl:variable name="lista" select="landxml:Start"/>
<xsl:variable name="x1" select="substring-before($lista,' ')"/>
<xsl:variable name="yt1" select="substring-after($lista,' ')"/>
<xsl:variable name="y1" select="substring-before($yt1,' ')"/>
<xsl:variable name="z1" select="substring-after($yt1,' ')"/>
<xsl:variable name="listb" select="landxml:End"/>
<xsl:variable name="x2" select="substring-before($listb,' ')"/>
<xsl:variable name="yt2" select="substring-after($listb,' ')"/>
<xsl:variable name="y2" select="substring-before($yt2,' ')"/>
<xsl:variable name="z2" select="substring-after($yt2,' ')"/>
<xsl:value-of select= "($x2 - $x1)*($x2 - $x1)+
($y2 - $y1)*($y2 - $y1)+
($z2 - $z1)*($z2 - $z1)"/>
</xsl:template>
When run on your sample input (after adjusting it to match the paths in your XSLT), the result is:
Line0001::7540-7541: 1.414, 7541-7542: 4.000, 7542-7543: 10.630, 7543-7544: 12.728 -- 28.772
I have the following data:
XML
<team>
<rectx>30</rectx>
<diadata>
<bestAnd>-350</bestAnd>
</diadata>
<diadata>
<bestAnd>-250</bestAnd>
</diadata>
<diadata>
<bestAnd>-50</bestAnd>
</diadata>
</team>
XSL
<xsl:variable name="list">
<xsl:value-of select="'M'" />
<xsl:for-each select="/team/diadata/bestAnd">
<xsl:choose>
<xsl:when test=". <0">
<xsl:value-of select=".*-1+400" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="." />
</xsl:otherwise>
</xsl:choose>
<xsl:variable name="position" select="position()" />
<xsl:value-of select="concat(/team/rectx*$position+40,' ',.,' L')" />
</xsl:for-each>
</xsl:variable>
<xsl:variable name="finallist">
<xsl:value-of select="substring($list, 1, string-length($list) - 2)" />
</xsl:variable>
<text x="250" y="50"
style="font-family: Arial;
font-size : 24;
stroke : #000000;
fill : #000000;">
<xsl:value-of select="$finallist" />
</text>
The output has to be
M70 750 L100 650 L130 450
however with the choose statement it is
M75070 -350 L650100 -250 L450130 -50
so it does
"letter""y-val after calc""x-val" "y-val"
I can't understand why the concat does not work with the choose statement but without it works great. Prob is that I can't have negative numbers but instead need to take those and convert them to positive (*-1) and add 400.
Any ideas?
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:variable name="list">
<xsl:value-of select="'M'"/>
<xsl:for-each select="/team/diadata/bestAnd">
<xsl:variable name="position" select="position()"/>
<xsl:value-of select="concat(/team/rectx*$position+40,' ')"/>
<xsl:choose>
<xsl:when test=". <0">
<xsl:value-of select=".*-1+400"/>
<xsl:value-of select="' L'"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
</xsl:template>
</xsl:stylesheet>
There is XML document:
<data>how;many;i;can;tell;you</data>
Need to get XML using XSLT version 1:
<manydata>
<onedata>how</onedata>
<onedata>many</onedata>
<onedata>i</onedata>
<onedata>can</onedata>
<onedata>tell</onedata>
<onedata>you</onedata>
</manydata>
How I can do it?
UPDATE:
Output format must be XML.
This recursive solution is probably one of the shortest possible:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="data">
<manydata><xsl:apply-templates/></manydata>
</xsl:template>
<xsl:template match="text()" name="tokenize">
<xsl:param name="pText" select="."/>
<xsl:if test="string-length($pText)">
<onedata>
<xsl:value-of select=
"substring-before(concat($pText,';'),';')"/>
</onedata>
<xsl:call-template name="tokenize">
<xsl:with-param name="pText" select=
"substring-after($pText,';')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document;
<data>how;many;i;can;tell;you</data>
the wanted, correct result is produced:
<manydata>
<onedata>how</onedata>
<onedata>many</onedata>
<onedata>i</onedata>
<onedata>can</onedata>
<onedata>tell</onedata>
<onedata>you</onedata>
</manydata>
<xsl:template match="data">
<manydata>
<!--
empty <manydata/> will be generated,
if <data/> without child text()
-->
<xsl:apply-templates select="text()"/>
</manydata>
</xsl:template>
<xsl:template match="data/text()">
<!-- start point for recursion -->
<xsl:call-template name="tokenize-string">
<xsl:with-param name="separator" select="';'"/>
<xsl:with-param name="string" select="text()"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="tokenize-string">
<xsl:param name="separator"/>
<xsl:param name="string"/>
<xsl:variable name="string-before-separator"
select="substring-before( $string, $separator )"/>
<onedata>
<xsl:choose>
<!-- if $separator presents in $string take first piece -->
<xsl:when test="$string-before-separator">
<xsl:value-of select="$string-before-separator"/>
</xsl:when>
<!-- take whole $string, if no $separator in the $string -->
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</onedata>
<!-- recursive call, if separator was found -->
<xsl:if test="$string-before-separator">
<xsl:call-template name="tokenize-string">
<xsl:with-param name="separator" select="$separator"/>
<xsl:with-param name="string"
select="substring-after( $string, $separator )"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
Try this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="data">
<xsl:element name="manydata">
<xsl:call-template name="splitsemicolons">
<xsl:with-param name="text" select="text()" />
</xsl:call-template>
</xsl:element>
</xsl:template>
<xsl:template name="splitsemicolons">
<xsl:param name="text" />
<xsl:choose>
<xsl:when test="contains($text,';')">
<xsl:element name="onedata">
<xsl:value-of select="substring-before($text,';')" />
</xsl:element>
<xsl:call-template name="splitsemicolons">
<xsl:with-param name="text" select="substring-after($text,';')" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:element name="onedata">
<xsl:value-of select="$text" />
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
This uses a named template that is called recursively, each time outputting what's before the first ;, and calling itself with everything after the first ;. If there isn't a ;, it just outputs whatever's left as-is.
You can use the XSLT extension library to get the tokenize function. Here is how the final code will look like:
<xsl:template match="/">
<manydata>
<xsl:for-each select="str:tokenize(data, ';')">
<xsl:value-of select="." />
</xsl:for-each>
</manydata>
</xsl:template>
</xsl:stylesheet>
Please note you will have to import the extension library into you XSLT using:
<xsl:import href="path/str.xsl" />
before you use the library functions.