I'd like to write the alphabet with a link for each letter. So I used templates but I don't how to make this letter I tried that but I had a normal mistake : (A decimal representation must imediately follow the &# in a character reference).
<xsl:template name="alphabet">
<xsl:param name="iLetter"/>
<xsl:if test="$iLetter < 91">
<a><xsl:attribute name="href">req.html?X_letter=&#<xsl:value-of select="$iLetter"/>;</xsl:attribute>&#<xsl:value-of select="$iLetter"/>;</xsl:attribute></a>
<xsl:call-template name="alphabet">
<xsl:with-param name="iLetter" select="number($iLetter)+1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
And I call this template ilke that:
<xsl:call-template name="alphabet">
<xsl:with-param name="iLetter" select="number(65)"/>
</xsl:call-template>
So, I'd like to obtain this result:
A B C D ..... X Y Z without ... of course :)
The currently accepted answer is incorrect, because it doesn't produce correctly the text child of any a element.
Here is a correct XSLT 1.0 solution:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vAlpha" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<xsl:template match="/">
<xsl:call-template name="alphabet"/>
</xsl:template>
<xsl:template name="alphabet">
<xsl:param name="pCode" select="65"/>
<xsl:if test="not($pCode > 90)">
<xsl:variable name="vChar" select=
"substring($vAlpha, $pCode - 64, 1)"/>
<a href="req.html?X_letter={$vChar}">
<xsl:value-of select="$vChar"/>
</a>
<xsl:call-template name="alphabet">
<xsl:with-param name="pCode" select="$pCode+1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on any XML document (not used), the wanted, correct result is produced:
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
P
Q
R
S
T
U
V
W
X
Y
Z
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"
xmlns:my="my:my" exclude-result-prefixes="xs my"
xmlns="http://www.w3.org/1999/xhtml">
<xsl:output omit-xml-declaration="yes" method="xhtml" indent="yes"/>
<xsl:param name="pStart" as="xs:integer" select="65"/>
<xsl:param name="pEnd" as="xs:integer" select="90"/>
<xsl:variable name="vCodes" as="xs:integer*" select=
"for $i in $pStart to $pEnd
return $i
"/>
<xsl:template match="/">
<html>
<xsl:sequence select="my:alphabet()"/>
</html>
</xsl:template>
<xsl:function name="my:alphabet" as="element()*">
<xsl:for-each select="$vCodes">
<xsl:variable name="vChar" select="codepoints-to-string(.)"/>
<a href="req.html?X_letter={$vChar}">
<xsl:sequence select="$vChar"/>
</a>
</xsl:for-each>
</xsl:function>
</xsl:stylesheet>
As Martin suggests it would be better to avoid using disable-output-escaping. You don't need it either, if you are would be satisfied with a plain ascii character instead of the numerical character reference. If so, you can use substring and a alphabet lookup-string like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="alphabet" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<xsl:template name="alphabet">
<xsl:param name="iLetter" select="65"/>
<xsl:if test="$iLetter < 91">
<a>
<xsl:attribute name="href">req.html?X_letter=<xsl:value-of select="substring($alphabet, $iLetter - 64, 1)"/></xsl:attribute>
<xsl:value-of select="substring($alphabet, $iLetter - 64, 1)"/>
</a>
<xsl:call-template name="alphabet">
<xsl:with-param name="iLetter" select="number($iLetter)+1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template match="/">
<xsl:call-template name="alphabet"/>
</xsl:template>
</xsl:stylesheet>
Cheers!
Inside of the a element content you could disable output escaping as in
<a href="req.html?X_letter={$iLetter}">
<xsl:value-of select="concat('&#', $iLetter, ';')" disable-output-escaping="yes"/>
</a>
That approach does not work within attribute nodes however so I left that part to pass the character code, not the character.
Also be warned that disable-output-escaping is an optional serialization feature that is not supported with all XSLT processors, for instance Firefox/Mozilla's built-in XSLT processor does not serialize the result tree but simply renders it so there the approach is not going to work.
XSLT 2.0 has the function codepoints-to-string(). With many XSLT 1.0 processors it should be easy enough to implement the same function as an extension function, though it will make your code dependent on that processor.
Related
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>
I need help from experts here to optimize the solution of updating string value in an element . I have this xml file as an input...
<?xml version="1.0" encoding="UTF-8"?>
<FullRequest>
<Header>
<Looptimes>3</Looptimes>
<SomeElement>blah!</SomeElement>
<AnotherElement>blah!!</AnotherElement>
</Header>
<RequestDetail>
<!-- Request Element is a string of fixed length (50 characters) -->
<Request>THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG AGAIN!</Request>
<Request>THE TIME FOX JUMPED OVER THE LAZY DOG, PROGRESSED!</Request>
<Request>OWING TO THE WIDESPREAD KNOWLEDGE OF THE PHRASE AN</Request>
</RequestDetail>
</FullRequest>
Offset 5 in Request element will be unique and can be cross-referenced. Q, T and G are the IDs in the above request.
<?xml version="1.0" encoding="UTF-8"?>
<FullResponse>
<Header>
<Looptimes>3</Looptimes>
<SomeElement>blah!</SomeElement>
<AnotherElement>blah!!</AnotherElement>
</Header>
<ResponseDetail>
<!-- Response element repeats for 3 times as indicated by the value of Looptimes -->
<!-- Id has a unique value -->
<Response>
<Id>Q</Id>
<Value1>ABC</Value1>
<Value2>XYZ</Value2>
<Value3>FGK</Value3>
</Response>
<Response>
<Id>T</Id>
<Value1>123</Value1>
<Value2>YOK</Value2>
<Value3>DSL</Value3>
</Response>
<Response>
<Id>G</Id>
<Value1>BAT</Value1>
<Value2>TKR</Value2>
<Value3>LAF</Value3>
</Response>
</ResponseDetail>
</FullResponse>
Taking the above xml, offset positions 10, 15 and 20 need to be replaced with values Value1, Value2 and Value3 respectively.
I have this XSL which does the job. Not sure how good this solution will work with few thousand records of 5000 characters each (50 characters in the Request element shown as an example here) with about 20 locations to edit.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:regexp="http://exslt.org/regular-expressions" exclude-result-prefixes="regexp">
<xsl:output omit-xml-declaration="no" indent="yes"/>
<xsl:preserve-space elements="*"/>
<xsl:variable name="WebResponse" select="document('local:///ic1-data.xml')"/>
<xsl:template match="node() | #*">
<xsl:copy>
<xsl:apply-templates select="node() | #*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/FullRequest/RequestDetail/Request">
<xsl:variable name="currentLine" select="."/>
<xsl:variable name="id" select="substring($currentLine,5,1)"/>
<xsl:for-each select="$WebResponse/FullResponse/ResponseDetail/Response">
<xsl:variable name="ResId" select="Id"/>
<xsl:if test="$id = $ResId">
<xsl:element name="{name()}">
<!-- Update Value1 -->
<xsl:variable name="from" select="substring($currentLine,10,3)"/>
<xsl:variable name="UpdatedValue1"
select="regexp:replace($currentLine,$from,'',Value1)"/>
<!-- Update Value2 -->
<xsl:variable name="from2" select="substring($UpdatedValue1,15,3)"/>
<xsl:variable name="UpdatedValue2"
select="regexp:replace($UpdatedValue1,$from2,'',Value2)"/>
<!-- Update Value3 -->
<xsl:variable name="from3" select="substring($UpdatedValue2,20,3)"/>
<xsl:value-of select="regexp:replace($UpdatedValue2,$from3,'',Value3)"/>
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The sample output will be as:
<?xml version="1.0" encoding="UTF-8"?>
<FullRequest>
<Header>
<Looptimes>3</Looptimes>
<SomeElement>blah!</SomeElement>
<AnotherElement>blah!!</AnotherElement>
</Header>
<RequestDetail>
<Response>THE QUICKABCOWXYZOXFGKMPS OVER THE LAZY DOG AGAIN!</Response>
<Response>THE TIME 123 JYOKEDDSLER THE LAZY DOG, PROGRESSED!</Response>
<Response>OWING TO BAT WTKRSPLAFD KNOWLEDGE OF THE PHRASE AN</Response>
</RequestDetail>
</FullRequest>
I can only use XSLT 1.0
Can you suggest how to make this better?
Thanks.
Another more flexible approach would be:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:strip-space elements="*"/>
<xsl:key name="kResponseById" match="Response" use="Id"/>
<xsl:variable name="WebResponse" select="document('ic1-data.xml')"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Request/text()">
<xsl:variable name="vCurrent" select="."/>
<xsl:for-each select="$WebResponse">
<xsl:call-template name="replace">
<xsl:with-param name="pString" select="$vCurrent"/>
<xsl:with-param name="pValues"
select="key('kResponseById',
substring($vCurrent,5,1)
)/*[starts-with(local-name(),'Value')]"/>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
<xsl:template name="replace">
<xsl:param name="pString"/>
<xsl:param name="pValues"/>
<xsl:choose>
<xsl:when test="$pValues">
<xsl:variable name="pLimit"
select="substring-after(
local-name($pValues[1]),
'Value'
) * 5 + 4"/>
<xsl:call-template name="replace">
<xsl:with-param name="pString"
select="concat(
substring($pString, 1, $pLimit),
$pValues[1],
substring($pString, $pLimit + 4)
)"/>
<xsl:with-param name="pValues"
select="$pValues[position()!=1]"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$pString"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Output:
<FullRequest>
<Header>
<Looptimes>3</Looptimes>
<SomeElement>blah!</SomeElement>
<AnotherElement>blah!!</AnotherElement>
</Header>
<RequestDetail>
<!-- Request Element is a string of fixed length (50 characters) -->
<Request>THE QUICKABCOWXYZOXFGKMPS OVER THE LAZY DOG AGAIN!</Request>
<Request>THE TIME 123 JYOKEDDSLER THE LAZY DOG, PROGRESSED!</Request>
<Request>OWING TO BAT WTKRSPLAFD KNOWLEDGE OF THE PHRASE AN</Request>
</RequestDetail>
</FullRequest>
I have some short form day names like so:
M -> Monday
T -> Tuesday
W -> Wednesday
R -> Thursday
F -> Friday
S -> Saturday
U -> Sunday
How can I convert an xml element like <days>MRF</days> into the long version <long-days>Monday,Thursday,Friday</long-days> using xslt?
Update from comments
Days will not be repeated
This stylesheet
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:d="day"
exclude-result-prefixes="d">
<d:d l="M" n="Monday"/>
<d:d l="T" n="Tuesday"/>
<d:d l="W" n="Wednesday"/>
<d:d l="R" n="Thursday"/>
<d:d l="F" n="Friday"/>
<d:d l="S" n="Saturday"/>
<d:d l="U" n="Sunday"/>
<xsl:variable name="vDays" select="document('')/*/d:d"/>
<xsl:template match="days">
<long-days>
<xsl:apply-templates
select="$vDays[contains(current(),#l)]"/>
</long-days>
</xsl:template>
<xsl:template match="d:d">
<xsl:value-of select="#n"/>
<xsl:if test="position()!=last()">,</xsl:if>
</xsl:template>
</xsl:stylesheet>
With this input:
<days>MRF</days>
Output:
<long-days>Monday,Thursday,Friday</long-days>
Edit: For those who wander, retaining the sequence order:
<xsl:variable name="vCurrent" select="current()"/>
<xsl:apply-templates
select="$vDays[contains($vCurrent,#l)]">
<xsl:sort select="substring-before($vCurrent,#l)"/>
</xsl:apply-templates>
Note: Because days wouldn't be repeated, this is the same as looking up for item existence in sequence with empty string separator.
That should do it... (There might be more elegant solutions though... ;-)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/days">
<long-days>
<xsl:if test="contains(.,'M')">Monday<xsl:if test="string-length(substring-before(.,'M'))=string-length(.)-1">,</xsl:if></xsl:if>
<xsl:if test="contains(.,'T')">Tuesday<xsl:if test="string-length(substring-before(.,'T'))=string-length(.)-1">,</xsl:if></xsl:if>
<xsl:if test="contains(.,'W')">Wednesday<xsl:if test="string-length(substring-before(.,'W'))=string-length(.)-1">,</xsl:if></xsl:if>
<xsl:if test="contains(.,'R')">Thursday<xsl:if test="string-length(substring-before(.,'R'))=string-length(.)-1">,</xsl:if></xsl:if>
<xsl:if test="contains(.,'F')">Friday<xsl:if test="string-length(substring-before(.,'F'))=string-length(.)-1">,</xsl:if></xsl:if>
<xsl:if test="contains(.,'S')">Saturday<xsl:if test="string-length(substring-before(.,'S'))=string-length(.)-1">,</xsl:if></xsl:if>
<xsl:if test="contains(.,'U')">Sunday<xsl:if test="string-length(substring-before(.,'U'))=string-length(.)-1">,</xsl:if></xsl:if>
</long-days>
</xsl:template>
The currently accepted solution always displays the long days names in chronological order and in addition, it doesn't display repeating (with same code) days.
Suppose we have the following XML document:
<days>STMSU</days>
I. This XSLT 1.0 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"/>
<my:days>
<M>Monday</M>
<T>Tuesday</T>
<W>Wednesday</W>
<R>Thursday</R>
<F>Friday</F>
<S>Saturday</S>
<U>Sunday</U>
</my:days>
<xsl:key name="kLongByShort" match="my:days/*"
use="name()"/>
<xsl:variable name="vstylesheet"
select="document('')"/>
<xsl:template match="days">
<long-days>
<xsl:call-template name="expand"/>
</long-days>
</xsl:template>
<xsl:template name="expand">
<xsl:param name="pcodeString" select="."/>
<xsl:if test="$pcodeString">
<xsl:variable name="vchar" select=
"substring($pcodeString,1,1)"/>
<xsl:for-each select="$vstylesheet">
<xsl:value-of select=
"concat(key('kLongByShort',$vchar),
substring(',',1,string-length($pcodeString)-1)
)
"/>
</xsl:for-each>
<xsl:call-template name="expand">
<xsl:with-param name="pcodeString" select=
"substring($pcodeString,2)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on the above document, produces the wanted, correct result:
<long-days>Saturday,Tuesday,Monday,Saturday,Sunday</long-days>
II. This XSLT 2.0 transformation:
<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:variable name="vshortCodes" as="xs:integer+"
select="string-to-codepoints('MTWRFSU')"/>
<xsl:variable name="vlongDays" as="xs:string+"
select="'Monday','Tuesday','Wenesday','Thursday',
'Friday','Saturday','Sunday'
"/>
<xsl:template match="days">
<long-days>
<xsl:for-each select="string-to-codepoints(.)">
<xsl:value-of separator="" select=
"for $pos in position() ne last()
return
($vlongDays[index-of($vshortCodes,current())],
','[$pos])
"/>
</xsl:for-each>
</long-days>
</xsl:template>
</xsl:stylesheet>
when applied on the same XML document:
<days>STMSU</days>
produce the wanted, correct result:
<long-days>Saturday,Tuesday,Monday,Saturday,Sunday</long-days>
How can we create a node set from values....
I have n numbers 1,2,3.......n.
I want to create a node set
<MYNMUMS>
<MYNUM>1</MYNUM>
<MYNUM>2</MYNUM>
<MYNUM>3</MYNUM>
<MYNUM>4</MYNUM>
....
<MYNUM>N</MYNUM>
</MYNMUMS>
As easy as that:
XSLT 1.0 solution:
This transformation:
<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="/">
<MyNums>
<xsl:call-template name="generateNumNodes">
<xsl:with-param name="pStart" select="1"/>
<xsl:with-param name="pEnd" select="10"/>
</xsl:call-template>
</MyNums>
</xsl:template>
<xsl:template name="generateNumNodes">
<xsl:param name="pStart"/>
<xsl:param name="pEnd"/>
<xsl:if test="$pEnd >= $pStart">
<xsl:variable name="vNumNodes"
select="$pStart -$pEnd+1"/>
<xsl:choose>
<xsl:when test="$vNumNodes = 1">
<MyNum><xsl:value-of select="$pStart"/></MyNum>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="vHalf" select=
"floor(($pStart+$pEnd) div 2)"/>
<xsl:call-template name="generateNumNodes">
<xsl:with-param name="pStart" select="$pStart"/>
<xsl:with-param name="pEnd" select="$vHalf"/>
</xsl:call-template>
<xsl:call-template name="generateNumNodes">
<xsl:with-param name="pStart" select="$vHalf+1"/>
<xsl:with-param name="pEnd" select="$pEnd"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on any XML document (not used), produces the desired output:
<MyNums>
<MyNum>1</MyNum>
<MyNum>2</MyNum>
<MyNum>3</MyNum>
<MyNum>4</MyNum>
<MyNum>5</MyNum>
<MyNum>6</MyNum>
<MyNum>7</MyNum>
<MyNum>8</MyNum>
<MyNum>9</MyNum>
<MyNum>10</MyNum>
</MyNums>
Do note the following:
The template generateNumNodes calls itself recursively.
This recursion is both time ( O(N) ), and space ( O(log2(N)) ) efficient and practically does overflow the stack -- no SO here!
The above feature is achieved by implementing the recursion in a DVC (DiVide and Conquer) style.
Unlike tail-recursion it will be successfully executed on any compliant XSLT processor.
The maximum recursion depth needed to generate 1000000 (one million numbers) is just 19.
XSLT 2.0 solution:
Even more elementary, no recursion, just using the XPath 2.0 to operator:
<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="/">
<MyNums>
<xsl:for-each select="1 to 10">
<MyNums>
<xsl:sequence select="."/>
</MyNums>
</xsl:for-each>
</MyNums>
</xsl:template>
</xsl:stylesheet>
XSLT is a transformation language. It is usually used when you already have some data in the form of an XML document, that you wish to transform into a different document (that may or may not be in XML format).
For the task of starting with "raw" data and generating an XML representation, XSLT is not well-suited.
I suggest you look into different langauges to solve this.
I am having a difficult time trying to do something that seems like it should be really easy to do. I basically want to multiply 2 numbers in a node and then sum the total of those numbers for all the nodes. Here is the XSLT code I have tried.
<xsl:value-of select="sum(Parts/Part/Quantity * Parts/Part/Rate)"/>
This code results in an error that says "Argument 1 of function sum cannot be converted to a node set."
Does anyone have an idea of what is wrong or how I can accomplish what I am trying to do?
Here are three possible solutions:
Solution1 XSLT2:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:sequence select="sum(/*/*/(rate * quantity))"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<parts>
<part>
<rate>0.37</rate>
<quantity>10</quantity>
</part>
<part>
<rate>0.03</rate>
<quantity>10</quantity>
</part>
</parts>
The wanted result is produced:
4
The XSLT 2.0 solution uses the fact that in XPath 2.0 it is allowed that the right argument of the last "/" operator can be an expression or generally a function. This expression/function is applied for each of the nodes selected so far acting as the context node, and each function application produces one result.
Solution2 XSLT 1.0:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:call-template name="sumProducts">
<xsl:with-param name="pList" select="*/*"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="sumProducts">
<xsl:param name="pList"/>
<xsl:param name="pAccum" select="0"/>
<xsl:choose>
<xsl:when test="$pList">
<xsl:variable name="vHead" select="$pList[1]"/>
<xsl:call-template name="sumProducts">
<xsl:with-param name="pList" select="$pList[position() > 1]"/>
<xsl:with-param name="pAccum"
select="$pAccum + $vHead/rate * $vHead/quantity"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$pAccum"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
When applied on the above XML document, the correct result is produced:
4
This is a typical XSLT 1.0 recursive solution. Do note how the sumProducts template calls itself recursively, until the entire input list, passed in the parameter $pList is processed.
Solution3 FXSL (XSLT 1.0):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
xmlns:test-map-product="test-map-product"
exclude-result-prefixes="xsl ext test-map-product"
>
<xsl:import href="sum.xsl"/>
<xsl:import href="map.xsl"/>
<xsl:import href="product.xsl"/>
<!-- This transformation is to be applied on:
salesMap.xml
It contains the code of the "sum of products" from the
article "The Functional Programming Language XSLT"
-->
<test-map-product:test-map-product/>
<xsl:output method="text"/>
<xsl:template match="/">
<!-- Get: map product /sales/sale -->
<xsl:variable name="vSalesTotals">
<xsl:variable name="vTestMap" select="document('')/*/test-map-product:*[1]"/>
<xsl:call-template name="map">
<xsl:with-param name="pFun" select="$vTestMap"/>
<xsl:with-param name="pList1" select="/sales/sale"/>
</xsl:call-template>
</xsl:variable>
<!-- Get sum map product /sales/sale -->
<xsl:call-template name="sum">
<xsl:with-param name="pList" select="ext:node-set($vSalesTotals)/*"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="makeproduct" match="*[namespace-uri() = 'test-map-product']">
<xsl:param name="arg1"/>
<xsl:call-template name="product">
<xsl:with-param name="pList" select="$arg1/*"/>
</xsl:call-template>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<sales>
<sale>
<price>3.5</price>
<quantity>2</quantity>
<Discount>0.75</Discount>
<Discount>0.80</Discount>
<Discount>0.90</Discount>
</sale>
<sale>
<price>3.5</price>
<quantity>2</quantity>
<Discount>0.75</Discount>
<Discount>0.80</Discount>
<Discount>0.90</Discount>
</sale>
</sales>
The correct result is produced:
7.5600000000000005
In the last case for each sale we calculate the product of price, quantity and all available (variable number of) discount-s.
FXSL is a pure XSLT implementation of higher order functions. In this example the higher-order function f:map() is used to map the function f:product() on the list of children of every sale element. Then the results are summed to produce the final result.
All of Dimitre's solutions work and he's right that you don't need to use extension functions but sometimes it makes life easier. It's not too harmful, especially when you use exslt extensions which are supported across multiple XSLT processors. Also, the reason you're getting the sequence errors is probably because you're using an XSLT 1 processor.
If you want to persist with your chosen solution, you'll need to use Saxon or some other XSLT processor that supports XSLT 2.
Otherwise, here's an alternative method of doing it in XSLT 1. This will work in most XSLT processors and some peope might find it easier to grok than the recursive version. Personally, I prefer the recursive version (Dimitre's 3rd proposal) because it is more portable.
<xsl:stylesheet version="1.0"
xmlns:ex="http://exslt.org/common"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template name="GetProducts">
<xsl:param name="left"/>
<xsl:param name="right"/>
<xsl:for-each select="$left/text()">
<product>
<xsl:value-of select="number(.) * number($right[position()])"/>
</product>
</xsl:for-each>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="products">
<xsl:call-template name="GetProducts">
<xsl:with-param name="left" select="Parts/Part/Rate"/>
<xsl:with-param name="right" select="Parts/Part/Quantity"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="sum(ex:node-set($products)/product)"/>
</xsl:template>
</xsl:stylesheet>