Below is my XML I want to show all the values of AgentSales if State = Talking Out
<AgentSales>
<AgentName>WRIGHT SIMMONS NATHANIEL</AgentName>
<State>Talking Out</State>
<Reason/>
<time>3</time></AgentSales>
here is my XSLT
<xsl:if test="/NewDataSet/AgentSales/State[text() = \'Talking Out\']">
<xsl:sort data-type="number" select="time" order="descending"/>
<tr>
<td><xsl:value-of select="AgentName"/></td>
<td><xsl:value-of select="State"/></td>
<td><xsl:value-of select="time"/></td>
</tr>
</xsl:if>
This is my error
Error loading stylesheet: Parsing an XSLT stylesheet failed.
Blockquote
First, you don't need to "escape" the apos like \'Talking Out\', just use 'Talking Out'.
Second, the xsl:sort instruction can only be child of xsl:apply-templates or xsl:for-each instructions in XSLT 1.0
Third, the XSLT style:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="Agents">
<table>
<xsl:apply-templates>
<xsl:sort select="time" data-type="number" order="descending"/>
</xsl:apply-templates>
</table>
</xsl:template>
<xsl:template match="AgentSales[State='Talking Out']">
<tr>
<xsl:apply-templates/>
</tr>
</xsl:template>
<xsl:template match="AgentSales/*">
<td>
<xsl:value-of select="."/>
</td>
</xsl:template>
<xsl:template match="AgentSales/Reason|AgentSales"/>
</xsl:stylesheet>
With this input:
<Agents>
<AgentSales>
<AgentName>WRIGHT SIMMONS NATHANIEL</AgentName>
<State>Talking Out</State>
<Reason>whatever</Reason>
<time>3</time>
</AgentSales>
<AgentSales>
<AgentName>SOMEONE</AgentName>
<State>Talking In</State>
<Reason>whatever</Reason>
<time>2</time>
</AgentSales>
<AgentSales>
<AgentName>SOMEONE ELSE</AgentName>
<State>Talking Out</State>
<Reason>whatever</Reason>
<time>5</time>
</AgentSales>
</Agents>
Output:
<table>
<tr>
<td>SOMEONE ELSE</td>
<td>Talking Out</td>
<td>5</td>
</tr>
<tr>
<td>WRIGHT SIMMONS NATHANIEL</td>
<td>Talking Out</td>
<td>3</td>
</tr>
</table>
There are two problems in the stylesheet you posted: a) you don't need to screen ' using \, just write 'Talking Out'. b) xsl:sort cannot be a child of xsl:if.
Use:
<xsl:template match="AgentName[../State='Talking Out']">
<td><xsl:value-of select="."/></td>
</xsl:template>
Also, read a good book on XSLT to get at least the fundamentals: <xsl:template>, <xsl:for-each>, <xsl:sort>, the identity rule and overriding it, ..., etc.
Related
I am trying to implement a logic which would do choosing a value if it matches the condition. The problem is, if my list contains more elements than one, it wouldn't display the value which matches the condition, but only the first one. Could you provide some advice? thanks. I need this to implement multiple filtering by conditions. Meaning that in this for-each I would display only books with year 2008, but in another table I could also use 2010 from the list of years. The below example is a part of the template for one table which uses 2008, but I will have multiple tables where I would filter books by 2010 as well.
XSL:
<xsl:for-each select="years">
<xsl:choose>
<xsl:when test="year=2008">
<td class="year"><xsl:value-of select="year"/></td>
</xsl:when>
</xsl:choose>
</xsl:for-each>
XML:
<book>
<title>Professional ASP.NET 4 in C# and VB</title>
<author>Bill Evjen, Scott Hanselman, Devin Rader</author>
<country>USA</country>
<price>580</price>
<years>
<year>2010</year>
<year>2008</year>
</years>
</book>
This one would populate 2010 into my HTML, not 2008 as I would expect. Is this doable at all?
Update:
I've tried this approach for example to filter against multiple conditions separately:
<xsl:for-each select="library/book">
<tr>
<td class="filterTd title"><xsl:value-of select="title"/></td>
<td class="filterTd author"><xsl:value-of select="author"/></td>
<td class="filterTd price"><xsl:value-of select="price"/></td>
<xsl:for-each select="years/year[.2008]">
<td class="year">
<xsl:value-of select="."/>
</td>
</xsl:for-each>
<xsl:for-each select="years/year[.2010]">
<td class="year">
<xsl:value-of select="."/>
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
This would not populate any td at all in my case..
If you only want to display books for a specific year, you would put a condition in the xsl:for-each that selects the book
<xsl:for-each select="library/book[years/year=$year]">
Where $year is a variable (or parameter) containing the year you want
Do note, if you know you are dealing with a specific year, then you don't actually need to do <xsl:value-of select="year" />, you can just output the year value you are working with.
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" indent="yes" />
<xsl:template match="/">
<xsl:call-template name="table">
<xsl:with-param name="year" select="2008" />
</xsl:call-template>
</xsl:template>
<xsl:template name="table">
<xsl:param name="year" />
<table>
<xsl:for-each select="library/book[years/year=$year]">
<tr>
<td class="filterTd title"><xsl:value-of select="title"/></td>
<td class="filterTd author"><xsl:value-of select="author"/></td>
<td class="filterTd price"><xsl:value-of select="price"/></td>
<td class="year">
<xsl:value-of select="$year"/>
</td>
</tr>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
Note, you could also use a key here to look up books by year
<xsl:key name="booksByYear" match="book" use="years/year" />
Then, the xsl:for-each to select books, looks like this:
<xsl:for-each select="key('booksByYear', $year)">
Could someone please explain why the following does not work.
Here is the xml:
<?xml version="1.0" ?>
<testsuites>
<testsuite errors="0" failures="0" name="test_ui_orchestration" tests="10" time="90.190"/>
<testsuite errors="1" failures="0" name="test_ui_tables" tests="13" time="1115.771"/>
<testsuite errors="0" failures="3" name="test_ui_dashboard" tests="18" time="116.397"/>
</testsuites>
I want to calculate total number of tests, total pass and fail. I have a problem getting total number of failures (failures + errors) and total number of pass (for simplicity: tests - failures). I call the same xml-template for calculating totals passing in different values using "with-param" as follows (section within "Calculate sums" comments):
<?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="/testsuites">
<html>
<body>
<!-- Calculate sums start -->
<xsl:variable name="allSum">
<xsl:call-template name="testsSum">
<xsl:with-param name="numTests" select="testsuite/#tests"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="failedSum">
<xsl:call-template name="testsSum">
<xsl:with-param name="numTests" select="testsuite/#failures"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="errorSum">
<xsl:call-template name="testsSum">
<xsl:with-param name="numTests" select="testsuite/#errors"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="failederrorSum">
<xsl:call-template name="testsSum">
<xsl:with-param name="numTests" select="testsuite/#failures + testsuite/#errors"/>
</xsl:call-template>
</xsl:variable>
<!-- Calculate sums ends -->
<h2>Test Results</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>Test Area</th>
<th>Total</th>
<th>Pass</th>
<th>Fail</th>
<th>Error</th>
<th>Fail and Error</th>
</tr>
<xsl:for-each select="testsuite">
<tr>
<td><xsl:value-of select="#name"/></td>
<td><xsl:value-of select="#tests"/></td>
<td><xsl:value-of select="#tests - (#failures + #errors)"/></td>
<td><xsl:value-of select="#failures"/></td>
<td><xsl:value-of select="#errors"/></td>
<td><xsl:value-of select="#failures + #errors"/></td>
<td> </td>
</tr>
</xsl:for-each>
<tr>
<td><b>All Tests</b></td>
<td><xsl:value-of select="$allSum"/></td>
<td>foo</td>
<!--
<td><xsl:value-of select="$passedSum"/></td>
-->
<td><xsl:value-of select="$failedSum"/></td>
<td><xsl:value-of select="$errorSum"/></td>
<td><xsl:value-of select="$failederrorSum"/></td>
<td></td>
</tr>
</table>
</body>
</html>
</xsl:template>
<xsl:template name="testsSum">
<xsl:param name="numTests"/>
<xsl:choose>
<xsl:when test="$numTests">
<xsl:variable name="recursive_result">
<xsl:call-template name="testsSum">
<xsl:with-param name="numTests" select="$numTests[position() > 1]"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="number($numTests[1]) + $recursive_result"/>
</xsl:when>
<xsl:otherwise><xsl:value-of select="0"/></xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
This produces the following output for the sums (full output is at the end of this post):
41 3 1 0
while it should be:
41 3 1 4
Each individual sum works fine, the corresponding attribute is found. However, when I attempt to add two attributes and pass that to the sum template, it does not work as expected.
It gets worse. The code for generating total passed tests is:
<xsl:variable name="passedSum">
<xsl:call-template name="testsSum">
<xsl:with-param name="numTests"
select="testsuite/#tests - testsuite/#failures"/>
</xsl:call-template>
</xsl:variable>
When the above code is enabled, I get the following error:
Failed to evaluate the expression of variable 'numTests'
I've been searching online and understand that the "select" of "with-param" accepts "An XPath expression that defines the value of the parameter" (from w3schools). An XPath expression can contain arithmetic operations (http://www.w3schools.com/xpath/xpath_operators.asp), so why do the above fail?
Here is full output of the xsl transformation:
Test Area Total Pass Fail Error Fail and Error
----------------------------------------------------------------------
test_ui_orchestration 10 10 0 0 0
test_ui_tables 13 12 0 1 1
test_ui_dashboard 18 15 3 0 3
----------------------------------------------------------------------
All Tests 41 foo 3 1 0
Here is the expected output of the xsl transformation (modified values marked with *):
Test Area Total Pass Fail Error Fail and Error
----------------------------------------------------------------------
test_ui_orchestration 10 10 0 0 0
test_ui_tables 13 12 0 1 1
test_ui_dashboard 18 15 3 0 3
----------------------------------------------------------------------
All Tests 41 37* 3 1 4*
As Stormtroopr says, you haven't shown us the part of the XSLT code that sets the context, and the fact that you left this out suggests you haven't understood how important context is in XSLT. If your XPath expressions select anything at all, we can probably assume that the context item is the testsuites element, in which case path expressions such as testsuite/#failures all select multiple values (a node set containing several attribute nodes). That's fine if the template you are calling expects a node-set. But when you use a node-set containing multiple nodes as input to an arithmetic operation, then:
(a) In XSLT 1.0 it uses the first node in the node-set and ignores the others
(b) In XSLT 2.0 you get a type error saying that what you are doing makes no sense.
Since you're getting an error, I assume you are using XSLT 2.0, though it doesn't seem a very helpful error message (which XSLT processor are you using?)
As for fixing the problem, I can't help with that because you haven't explained what you are trying to achieve - I can't reverse-engineer the requirements from incorrect code.
Since the question has changed dramatically, I've made a new answer.
Your summing template (in fact most of the template) works fine. All you need to do to get those totals down the bottom, you just need to perform addition/subtraction on the variables you've already created:
<xsl:variable name="failederrorSum" select="$failedSum + $errorSum" />
<xsl:variable name="passedSum" select="$allSum - $failederrorSum" />
This stylesheet applied to the input XML:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/testsuites">
<html>
<body>
<!-- Calculate sums start -->
<xsl:variable name="allSum">
<xsl:call-template name="testsSum">
<xsl:with-param name="numTests" select="testsuite/#tests"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="failedSum">
<xsl:call-template name="testsSum">
<xsl:with-param name="numTests" select="testsuite/#failures"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="errorSum">
<xsl:call-template name="testsSum">
<xsl:with-param name="numTests" select="testsuite/#errors"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="failederrorSum" select="$failedSum + $errorSum" />
<xsl:variable name="passedSum" select="$allSum - $failederrorSum" />
<!-- Calculate sums ends -->
<h2>Test Results</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>Test Area</th>
<th>Total</th>
<th>Pass</th>
<th>Fail</th>
<th>Error</th>
<th>Fail and Error</th>
</tr>
<xsl:apply-templates />
<tr>
<td><b>All Tests</b></td>
<td><xsl:value-of select="$allSum"/></td>
<td><xsl:value-of select="$passedSum"/></td>
<td><xsl:value-of select="$failedSum"/></td>
<td><xsl:value-of select="$errorSum"/></td>
<td><xsl:value-of select="$failederrorSum"/></td>
<td></td>
</tr>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="testsuite">
<tr>
<td><xsl:value-of select="#name"/></td>
<td><xsl:value-of select="#tests"/></td>
<td><xsl:value-of select="#tests - (#failures + #errors)"/></td>
<td><xsl:value-of select="#failures"/></td>
<td><xsl:value-of select="#errors"/></td>
<td><xsl:value-of select="#failures + #errors"/></td>
<td> </td>
</tr>
</xsl:template>
<xsl:template name="testsSum">
<xsl:param name="numTests"/>
<xsl:choose>
<xsl:when test="$numTests">
<xsl:variable name="recursive_result">
<xsl:call-template name="testsSum">
<xsl:with-param name="numTests" select="$numTests[position() > 1]"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="number($numTests[1]) + $recursive_result"/>
</xsl:when>
<xsl:otherwise><xsl:value-of select="0"/></xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Gives this output:
<html>
<body>
<h2>Test Results</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>Test Area</th>
<th>Total</th>
<th>Pass</th>
<th>Fail</th>
<th>Error</th>
<th>Fail and Error</th>
</tr>
<tr>
<td>test_ui_orchestration</td>
<td>10</td>
<td>10</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>
</td>
</tr>
<tr>
<td>test_ui_tables</td>
<td>13</td>
<td>12</td>
<td>0</td>
<td>1</td>
<td>1</td>
<td>
</td>
</tr>
<tr>
<td>test_ui_dashboard</td>
<td>18</td>
<td>15</td>
<td>3</td>
<td>0</td>
<td>3</td>
<td>
</td>
</tr>
<tr>
<td>
<b>All Tests</b>
</td>
<td>41</td>
<td>37</td>
<td>3</td>
<td>1</td>
<td>4</td>
<td/>
</tr>
</table>
</body>
</html>
Which looks like this:
Unfortunately, you've only posted fragments of your XSLT, but I believe the problem is that you are calling testsuite/#failures from the testsuites element.
Rather than returning a number, this is instead returning either a result tree fragment or node-set with the values of the failures attribute for each testsuite element.
As such the traditional numeric operations are failing, as they expect a number and get an RTF or nodeset instead, and attempt to apply the operation giving an unexpected result.
Without knowing what the rest of the code is, I'd suggest the way to fix is to wrap the variable instantiations either in a for-each loop, or even better, in a template that matches on an individual testsuite.
I am trying to get the sum. Here is the xslt code.
<xsl:template match="Entry">
<xsl:if test="position() <= 10">
<tr>
<td>
<xsl:value-of select="substring-before(#Value,'||')"/>
</td>
<td>
<xsl:value-of select="format-number(substring(substring-after(#Value,'||||'),1,10),'#.#')"/>
</td>
</tr>
</xsl:if>
</xsl:template>
above code will fillter data as two coloums. It is ok. Now I need to get the sum of <xsl:value-of select="format-number(substring(substring-after(#Value,'||||'),1,10),'#.#')"/>
I am from procedural programming. I read many articles but I still coudnt figure out that how to get the sum of this. can anybody help me?
Here is the xml
<TopHoldings Currency="xxx">
<Entry Type="CName||C||S||Fund Weight (%)||Benchmark weight (%)" Value="Ab||U||||1.2170000000000||" Date="8/31/2011" />
here is the whole xslt
<table style="width:50%;font-size:12px;" cellspacing="0" cellpadding="0">
<tr style="width:50%; text-align:left;background-color:E6F1F9;">
<th> </th>
<th> % of funds </th>
</tr>
<xsl:apply-templates select="$items">
<xsl:sort select="format-number(substring(substring-after(#Value,'||||'),1,10),'#.#')" order="descending"/>
<xsl:sort select="substring-before(#Value,'||')"/>
</xsl:apply-templates>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="Entry">
<xsl:if test="position() <= 10">
<tr>
<td>
<xsl:value-of select="substring-before(#Value,'||')"/>
</td>
<td>
<xsl:value-of select="format-number(substring(substring-after(#Value,'||||'),1,10),'#.#')"/>
</td>
</tr>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When you need to sum the values of multiple values on XSLT 1.0 you have to rely on recursion [EDIT: in XSLT 1.0 the function sum it is also available] (in XSLT 2.0 there is an XPath function sum()).
The following template performs the sum of the given elements through the elements-to-sum parameter, extracting the value to sum from the attribute #Value as you specified.
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html" />
<xsl:template match="TopHoldings">
<table>
<xsl:call-template name="sum">
<xsl:with-param name="elements-to-sum"
select="Entry" />
</xsl:call-template>
</table>
</xsl:template>
<xsl:template name="sum">
<xsl:param name="elements-to-sum" />
<xsl:param name="sum" select="'0'" />
<!-- Store in variables the following operations to avoid performing them more than once -->
<xsl:variable name="current-element" select="$elements-to-sum[1]" />
<xsl:variable name="current-value" select="format-number(substring(substring-after($current-element/#Value,'||||'),1,10),'#.#')" />
<!-- Output the table row -->
<tr>
<td><xsl:value-of select="substring-before($current-element/#Value, '||')" /></td>
<td><xsl:value-of select="$current-value" /></td>
</tr>
<!-- Determine whether continue -->
<xsl:choose>
<!-- Case END: we have just one element to sum so we perform the sum and we output the desired result -->
<xsl:when test="count($elements-to-sum) = 1">
<tr>
<td>Result:</td>
<td><xsl:value-of select="$current-value + $sum" /></td>
</tr>
</xsl:when>
<!-- Case RECURSION : we call this template again adding the current value to the sum and removing the first element from the parameter elements-to-sum -->
<xsl:otherwise>
<xsl:call-template name="sum">
<xsl:with-param name="elements-to-sum"
select="$elements-to-sum[position() > 1]" />
<xsl:with-param name="sum"
select="$sum + $current-value" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
I assumed that you wanted to display the result of the sum as a new row in the same table, if that is not the case and you want to display the result elsewhere the solution would be slightly different. Tell me if this solution is acceptable to you or you need to display the sum outside the element.
NOTE: Instead of doing those 'complex' string operations in the attribute #Value, I would consider splitting all the information within that attribute into different attributes.
Just use the XPath function sum(), it is available in both XPath 2.0 for XSLT 2.0 and XPath 1.0 for XSLT 1.0 as described in http://www.w3.org/TR/xpath/#function-sum. If the numbers you want to get the sum of are attributes "Value" to an element "Entry" use sum(//Entry/#Value)" to grab all and sum up. Change this to get the 10 elements you want in your xml data.
I have a stylesheet I'm using with a perl module that only works with XSLT 1.0. I want to create a JSON array inside a JSON dictionary so I need proper comma seperation for the elements. I'm parsing an XHTML table where there are 1 or more spans in the second cell. So for-each select="./tr" and then for-each select="./td[1]/span" or something like that.
After changing it a little it behaves as expected as Ian said it would.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" version="1.0" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" doctype-public="-//W3C//DTD HTML 4.01//EN" encoding="UTF-8" />
<xsl:template match="text()">
</xsl:template>
<xsl:template match="table/tbody">
<xsl:text>[</xsl:text>
<xsl:for-each select="./tr[not(#class='no-results')]">
<xsl:text>{"</xsl:text>
<xsl:value-of select="normalize-space(.//strong)" />
<xsl:text>":{"ingredients":{</xsl:text>
<xsl:for-each select=".//div[#class='reagent-list']//a[#class='item-link reagent']">
<xsl:value-of select="substring(./#href, 14)" />:<xsl:value-of select="normalize-space(./span[1])" />
<xsl:if test="position() != last()">
<xsl:text>,</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:text>}</xsl:text>
<xsl:if test="position() != last()">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:text>
</xsl:text>
</xsl:for-each>
<xsl:text>]</xsl:text>
</xsl:template>
</xsl:stylesheet>
I realize that the stylesheet does not match the xml below. The actual document is huge. I hope you understand what I mean, though. I just made this up:
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
<th>c</th>
</tr>
</thead>
<tbody>
<tr>
<td>foo</td>
<td><span>an element</span></td>
<td>bar</td>
</tr>
<tr>
<td>foo</td>
<td><span>an element</span><span>an element</span></td>
<td>bar</td>
</tr>
<tr>
<td>foo</td>
<td><span>an element</span><span>an element</span><span>an element</span><span>an element</span></td>
<td>bar</td>
</tr>
</tbody>
</table>
=>
{
"Row one":["an element"],
"Row two":["an element", "an element"],
"Row three":["an element", "an element", "an element", "an element"]
}
Instead I get this:
{
"Row one":["an element",],
"Row two":["an element", "an element",],
"Row three":["an element" "an element" "an element" "an element"]
}
I've been using position() and last() in a test tag to print a comma and it seems to work correctly for the outer loop, but how do I tell my test tag to use the inner for-each scope when printing the commas that seperate the array?
As mentioned by #IanRoberts, it is difficult to give targeted assistance without seeing what your existing XSLT looks like.
That said, here is a solution that is push-oriented (i.e., no <xsl:for-each>) and does not require last().
When this XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my"
exclude-result-prefixes="my"
version="1.0">
<xsl:output omit-xml-declaration="no" indent="yes" method="text" />
<xsl:strip-space elements="*" />
<my:ones>
<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>
</my:ones>
<xsl:template match="/*">
<xsl:text>{
</xsl:text>
<xsl:apply-templates select="tbody/tr" />
<xsl:text>
}</xsl:text>
</xsl:template>
<xsl:template match="tr">
<xsl:variable name="vPos" select="position()" />
<xsl:if test="$vPos > 1">,
</xsl:if>
<xsl:text> "Row </xsl:text>
<xsl:value-of select="document('')/*/my:ones/*[$vPos]" />
<xsl:text>":[</xsl:text>
<xsl:apply-templates select="td[2]/span" />
<xsl:text>]</xsl:text>
</xsl:template>
<xsl:template match="span">
<xsl:if test="position() > 1">, </xsl:if>
<xsl:value-of select="concat('"', ., '"')" />
</xsl:template>
</xsl:stylesheet>
...is run against the provided XML:
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
<th>c</th>
</tr>
</thead>
<tbody>
<tr>
<td>foo</td>
<td>
<span>an element</span></td>
<td>bar</td>
</tr>
<tr>
<td>foo</td>
<td>
<span>an element</span><span>an element</span></td>
<td>bar</td>
</tr>
<tr>
<td>foo</td>
<td>
<span>an element</span><span>an element</span><span>an element</span><span>an element</span></td>
<td>bar</td>
</tr>
</tbody>
</table>
...the wanted result is produced:
{
"Row one":["an element"],
"Row two":["an element", "an element"],
"Row three":["an element", "an element", "an element", "an element"]
}
Explanation:
The first template matches the root element. It's purpose is to apply templates to that element's <tr> grandchildren and sandwich those results between { and } (adding newlines as appropriate).
The second template matches <tr> elements. It outputs row information and is instructed to apply templates to all <span> children of the second <td> element (again, sandwiching the results between braces and other text as necessary).
NOTE: instead of using last(), you'll see that the first element outputs a comma, followed by a newline, if the position of this <tr> in the current context is greater than 1. This has the same effect of applying commas correctly; it's merely a different way of looking at the same problem (and is what I use because it seems more efficient to me ;) ).
NOTE: to make this solution more extensible, you'll see that I'm not statically outputting the words "one", "two", etc. in each row. Instead, at the top of the XSLT, I've defined a <my:ones> element to hold onto the text values of each "ones" number. When processing each <tr>, I use the position of that <tr> in the current context to retrieve the correct <num> element's value. I've left it as an exercise to the reader, but it would indeed be possible to define <my:tens>, <my:hundreds>, etc. to scale this solution up to potentially large numbers of rows.
The final template matches <span> elements. Again, it uses an <xsl:if> element to test whether the position of this <span> in the current context is greater than 1; if so, a comma (followed by a space) is output. After that, we merely concatenate two " symbols with the value of the span sandwiched in-between.
The source XML (this is just foobar data, in reality it is thousands of rows wich can be both positive and negative):
<accounting>
<entry id="1">
<accounting_date>2010-10-29</accounting_date>
<transfer_date>2010-10-29</transfer_date>
<description>Start balance</description>
<vat>0</vat>
<sum>87287</sum>
</entry>
<entry id="2">
<accounting_date>2011-01-24</accounting_date>
<transfer_date>2011-02-17</transfer_date>
<description>Bill 1</description>
<vat>175</vat>
<sum>875</sum>
</entry>
<entry id="3">
<accounting_date>2011-01-31</accounting_date>
<transfer_date>2011-01-18</transfer_date>
<description>Bill 2</description>
<vat>350</vat>
<sum>1750</sum>
</entry>
</accounting>
I want to transform this XML to an HTML table to display to the user. Most of the transformation is just putting values in the right places, but the balance-field is giving me headache.
My XSLT (that does not work):
<table>
<tr>
<th>Accounting date</th>
<th>Description</th>
<th>Sum</th>
<th>Balanche</th>
</tr>
<xsl:for-each select="/accounting/entry">
<tr>
<td><xsl:value-of select="accounting_date" /></td>
<td><xsl:value-of select="description" /></td>
<td><xsl:value-of select="sum" /></td>
<td><xsl:value-of select="sum(../entry[position() < current()/position()]/sum)" /></td><!-- This XPath is the problem! -->
</tr>
</xsl:for-each>
</table>
Expected result:
<table>
<tr>
<th>Accounting date</th>
<th>Description</th>
<th>Sum</th>
<th>Balanche</th>
</tr>
<tr>
<td>2010-10-29</td>
<td>Start balance</td>
<td>87287</td>
<td>87287</td>
</tr>
<tr>
<td>2011-01-24</td>
<td>Bill 1</td>
<td>875</td>
<td>88162</td>
</tr>
<tr>
<td>2011-01-31</td>
<td>Bill 2</td>
<td>1750</td>
<td>89912</td>
</tr>
</table>
Chrome is blank, and Firefox gives me:
Error loading stylesheet: XPath parse failure: Name or Nodetype test expected:
I'm stuck, please help. :)
The best solution might depend a bit on whether you are using XSLT 1.0 or XSLT 2.0. You really need to say, since at present there's a roughly even mix of both in use in the field. (It seems you're running it in the browser, which suggests you want a 1.0 solution, so that's what I've given you).
But either way, recursion is your friend. In this case, "sibling recursion" where you write a template to process an entry, and it does apply-templates to process the next entry, passing the total so far as a parameter: something like this
<xsl:template match="entry">
<xsl:param name="total-so-far" select="0"/>
<tr>
<td><xsl:value-of select="accounting_date" /></td>
<td><xsl:value-of select="description" /></td>
<td><xsl:value-of select="sum" /></td>
<td><xsl:value-of select="$total-so-far + sum"/></td><
</tr>
<xsl:apply-templates select="following-sibling::entry[1]">
<xsl:with-param name="total-so-far" select="$total-so-far + sum"/>
</xsl:apply-templates>
</xsl:template>
Then you need to start the process off with
<xsl:template match="accounting">
<table>
<xsl:apply-templates select="entry[1]"/>
</table>
</xsl:template>
If there are thousands of rows then this could cause stack overflow in an XSLT processor that doesn't do tail call optimisation. I've no idea whether the XSLT processors in today's browsers implement this optimisation or not.
Alternatively you can use the preceding-sibling axes
<table>
<tr>
<th>Accounting date</th>
<th>Description</th>
<th>Sum</th>
<th>Balanche</th>
</tr>
<xsl:for-each select="/accounting/entry">
<tr>
<td>
<xsl:value-of select="accounting_date" />
</td>
<td>
<xsl:value-of select="description" />
</td>
<td>
<xsl:value-of select="sum" />
</td>
<td>
<xsl:value-of select="sum(preceding-sibling::*/sum)+sum" />
</td>
</tr>
</xsl:for-each>
</table>
In addition to the correct answer by #Michael Kay, here is a general template/function from FXSL to use for computing running totals. Its DVC variant will never (for practical purposes) crash due to stack overflow. With DVC (Divide and Conquer) recursion, processing a sequence of 1000000 (1M) items requires maximum stack depth of only 19.
Here is an example of using the scanl template:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:myAdd="f:myAdd"
xmlns:myParam="f:myParam"
>
<xsl:import href="scanlDVC.xsl"/>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<myAdd:myAdd/>
<myParam:myParam>0</myParam:myParam>
<xsl:template match="/">
<xsl:variable name="vFun" select="document('')/*/myAdd:*[1]"/>
<xsl:variable name="vZero" select="document('')/*/myParam:*[1]"/>
<xsl:call-template name="scanl">
<xsl:with-param name="pFun" select="$vFun"/>
<xsl:with-param name="pQ0" select="$vZero" />
<xsl:with-param name="pList" select="/*/num"/>
</xsl:call-template>
</xsl:template>
<xsl:template match="myAdd:*" mode="f:FXSL">
<xsl:param name="pArg1" select="0"/>
<xsl:param name="pArg2" select="0"/>
<xsl:value-of select="$pArg1 + $pArg2"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML file:
<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 correct result (running totals) is produced:
<el>0</el>
<el>1</el>
<el>3</el>
<el>6</el>
<el>10</el>
<el>15</el>
<el>21</el>
<el>28</el>
<el>36</el>
<el>45</el>
<el>55</el>
Using it for the provided XML document:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:myAdd="f:myAdd"
xmlns:myParam="f:myParam"
>
<xsl:import href="scanlDVC.xsl"/>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<myAdd:myAdd/>
<myParam:myParam>0</myParam:myParam>
<xsl:template match="/">
<xsl:variable name="vFun" select="document('')/*/myAdd:*[1]"/>
<xsl:variable name="vZero" select="document('')/*/myParam:*[1]"/>
<xsl:call-template name="scanl">
<xsl:with-param name="pFun" select="$vFun"/>
<xsl:with-param name="pQ0" select="$vZero" />
<xsl:with-param name="pList" select="/*/*/sum"/>
</xsl:call-template>
</xsl:template>
<xsl:template match="myAdd:*" mode="f:FXSL">
<xsl:param name="pArg1" select="0"/>
<xsl:param name="pArg2" select="0"/>
<xsl:value-of select="$pArg1 + $pArg2"/>
</xsl:template>
</xsl:stylesheet>
and the correct result is produced::
<el>0</el>
<el>87287</el>
<el>88162</el>
<el>89912</el>