I want to perform division of tables into three in my xslt code dynamically.
Give me structure to append my text at specific node like if i am having 100 nodes i need to append my text at 33rd 66th nodes .
My XSLT code :
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:key name="routes-by-origin" match="/schedules/Routes/org" use="./text()"/>
<xsl:template match="schedules" name="Main">
<html>
<head></head>
<body>
<table>
<tr> <td> Heading </td> </tr>
<table>
<xsl:for-each select="Routes/org[generate-id(.) = generate-id(key('routes-by-origin',.)[1])]">
<xsl:value-of select="./text()"/>
<xsl:apply-templates select="//Routes[org/text() = current()/text()]"/>
</xsl:for-each>
</table>
</xsl:template>
Template design called from above template
<xsl:template match="Routes">
<xsl:value-of select="des/text()"/>
<xsl:variable name="via" select="Via/text()"/>
<xsl:value-of select="Flgno/text()"/>
</xsl:template>
</xsl:stylesheet>
Input Document
<schedules>
<Routes>
<org>Agartala</org>
<des>Bangalore</des>
<Flgno>SG 872</Flgno>
<Via>CCU, HYD</Via>
</Routes>
<Routes>
<org>Agartala</org>
<des>Guwahati</des>
<Flgno>SG 873</Flgno>
<Via> BOM </Via>
</Routes>
<Routes>
<org>Agartala</org>
<des>Hyderabad</des>
<Flgno>SG 872</Flgno>
<Via>CCU</Via>
</Routes>
<Routes>
<org>Agartala</org>
<des>Kolkata</des>
<Flgno>SG 872</Flgno>
<Via> - </Via>
</Routes>
<Routes>
<org>Agartala</org>
<des>Kolkata</des>
<Flgno>SG 874</Flgno>
<Via> - </Via>
</Routes>
<Routes>
<org>Agartala</org>
<des>Mumbai</des>
<Flgno>SG 874</Flgno>
<Via>CCU</Via>
</Routes>
<Routes>
<org>Ahmedabad</org>
<des>Bangalore</des>
<Flgno>SG 528</Flgno>
<Symbols> - </Symbols>
<Via>BOM</Via>
</Routes>
</schedules>
Expected Result
My output should be in three tables in one page:
in first table
first 2 records
second table - next 2 records
third table last 3 records
2 records
2 records
3 records
<table>
<table>
<tr>
<td>Agartala</td>
<td>Bangalore</td>
<td>SG 872</td>
<td>CCU</td>
</tr>
<tr>
<td> Agartala</td>
<td>Guwahati</td>
<td>SG 87</td>
<td>BOM</td>
</tr>
</table>
<table>
<tr>
<td>Agartala</td>
<td>Hyderabad</td>
<td>SG 872 </td>
<td>CCU</td>
</tr>
<tr>
<td>Agartala</td>
<td>Kolkatta</td>
<td>SG 872</td>
<td> - </td>
</tr>
</table>
<table>
<tr>
<td> Agartala</td>
<td>Kolkatta</td>
<td>SG 874</td>
<td> - </td>
</tr>
<tr>
<td>Agartala</td>
<td>Mumbai</td>
<td>SG 874 </td>
<td>CCU</td>
</tr>
<tr>
<td>Agartala</td>
<td>Bangalore</td>
<td>SG 528</td>
<td>BOM</td>
</tr>
</table>
</table>
Note : My input data is dynamic
This XSLT 1.0 style-sheet is a copy of Dimitre's solution here, slightly tweaked for the OP's particular data and having the 3rd table have ceil( row-count / 3) rows.
XSLT 1.0 Solution
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="prowLimit" select="floor(count(/*/Routes) div 3)" />
<xsl:template match="/">
<table>
<xsl:apply-templates select="*/Routes" />
</table>
</xsl:template>
<xsl:template match="Routes">
<xsl:if test="(position() mod $prowLimit = 1) and
(position() < (3 * $prowLimit + 1))">
<xsl:variable name="is-last-table" select="position() - (2*$prowLimit)" />
<table>
<xsl:for-each select=".|following-sibling::Routes[
not(position() > $prowLimit - 1) or ($is-last-table > 0)]" >
<tr>
<td><xsl:value-of select="org" /></td>
<td><xsl:value-of select="des" /></td>
<td><xsl:value-of select="Flgno" /></td>
<td><xsl:value-of select="Via" /></td>
</tr>
</xsl:for-each>
</table>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
XSLT 2.0 Solution
It gets easier in XSLT 2.0 ...
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
exclude-result-prefixes="xsl fn">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="prowLimit" select="floor(count(/*/Routes) div 3)" />
<table>
<xsl:for-each-group select="*/Routes"
group-adjacent="fn:min((floor((position() - 1) div $prowLimit),2))" >
<table>
<xsl:apply-templates select="current-group()" />
</table>
</xsl:for-each-group>
</table>
</xsl:template>
<xsl:template match="Routes">
<tr>
<td><xsl:value-of select="org" /></td>
<td><xsl:value-of select="des" /></td>
<td><xsl:value-of select="Flgno" /></td>
<td><xsl:value-of select="Via" /></td>
</tr>
</xsl:template>
</xsl:stylesheet>
Related
Here is some XSL script:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msa="http://www.publictalksoftware.co.uk/msa">
<xsl:output method="html" indent="yes" version="4.01"
doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
doctype-public="//W3C//DTD XHTML 1.0 Transitional//EN"/>
<xsl:variable name="DutyHistory" select="document('DutyAssignHistory.XML')"/>
<xsl:template match="/">
<html>
<head>
<title>Test</title>
</head>
<body>
<xsl:for-each select="MeetingWorkBook/Meeting">
<p>
<xsl:value-of select ="Date/#ThisWeek"/>
</p>
<xsl:variable name="Week" select="Date/#ThisWeek"/>
<table>
<tr>
<td>Sound</td>
<td>
<xsl:value-of select="$DutyHistory/msa:DutyAssignmentHistory/msa:DutyAssignments/msa:DutyAssignmentEntry[#Week=$Week and #Mode='Weekend']/msa:Assignment[#Index='1' and #IndexType='Fixed']"/>
</td>
</tr>
<tr>
<td>Platform</td>
<td>
<xsl:value-of select="$DutyHistory/msa:DutyAssignmentHistory/msa:DutyAssignments/msa:DutyAssignmentEntry[#Week=$Week and #Mode='Weekend']/msa:Assignment[#Index='5' and #IndexType='Fixed']"/>
</td>
</tr>
<tr>
<td>Left Mike</td>
<td>
<xsl:value-of select="$DutyHistory/msa:DutyAssignmentHistory/msa:DutyAssignments/msa:DutyAssignmentEntry[#Week=$Week and #Mode='Weekend']/msa:Assignment[#Index='7' and #IndexType='Fixed']"/>
</td>
</tr>
<tr>
<td>Right Mike</td>
<td>
<xsl:value-of select="$DutyHistory/msa:DutyAssignmentHistory/msa:DutyAssignments/msa:DutyAssignmentEntry[#Week=$Week and #Mode='Weekend']/msa:Assignment[#Index='8' and #IndexType='Fixed']"/>
</td>
</tr>
<tr>
<td>Public Talk Chairman</td>
<td>
<xsl:value-of select="$DutyHistory/msa:DutyAssignmentHistory/msa:DutyAssignments/msa:DutyAssignmentEntry[#Week=$Week and #Mode='Weekend']/msa:Assignment[#Index='4' and #IndexType='Custom']"/>
</td>
</tr>
<tr>
<td>Watchtower Reader</td>
<td>
<xsl:value-of select="$DutyHistory/msa:DutyAssignmentHistory/msa:DutyAssignments/msa:DutyAssignmentEntry[#Week=$Week and #Mode='Weekend']/msa:Assignment[#Index='5' and #IndexType='Custom']"/>
</td>
</tr>
</table>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
As you can see it is linking in another XML document for reference. Here is one example of that linked in file:
<?xml version="1.0" encoding="utf-8"?>
<DutyAssignmentHistory xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.publictalksoftware.co.uk/msa">
<DutyAssignments>
<DutyAssignmentEntry Date="2018-01-04" Week="W20180101" Template="0" Mode="Midweek">
<Assignment Index="2" IndexType="Fixed">Name 1</Assignment>
<Assignment Index="5" IndexType="Fixed">Name 2</Assignment>
<Assignment Index="7" IndexType="Fixed">Name 3</Assignment>
<Assignment Index="8" IndexType="Fixed">Name 4</Assignment>
<Assignment Index="13" IndexType="Fixed">Name 5</Assignment>
<Assignment Index="14" IndexType="Fixed">Name 6</Assignment>
</DutyAssignmentEntry>
</DutyAssignments>
</DutyAssignmentHistory>
Potentially a user might want to reference the information in the XML and display it however they like but I am wanting to show them the simplest method.
As you can see there are several criteria:
Week (WYYYYMMDD)
Mode (Midweek, Weekend or Weekly)
Template(0 or higher)
The above will filter to the right week of assignments. Then, to identify the actual assignment:
Index (numeric value)
IndexType (Fixed, CustomFixed or Custom)
Can I use templates in any way (perhaps with variables) to simplify the code as it is getting repetative?
You could use a template and pass parameters, or in XSLT 2.0 or higher you could also define a function, which makes it much easier to use and saves some typing. But for what you are currently doing, a variable and some predicate filters seems to be the most simple and easy.
The most simple and easy way would be to bind a variable with the weekend assignments, and then apply your predicate filter to select the one with the #Index and #IndexType:
<xsl:variable name="Week" select="Date/#ThisWeek"/>
<xsl:variable name="weekend-assignments"
select="$DutyHistory/msa:DutyAssignmentHistory/msa:DutyAssignments
/msa:DutyAssignmentEntry[#Week=$Week and #Mode='Weekend']/msa:Assignment"/>
<table>
<tr>
<td>Sound</td>
<td>
<xsl:value-of select="$weekend-assignments[#Index='1' and #IndexType='Fixed']"/>
</td>
</tr>
If you make the variable hold an unfiltered set of Assignment elements, you could perform all of the filtering in the predicates:
<xsl:variable name="Week" select="Date/#ThisWeek"/>
<xsl:variable name="assignments"
select="$DutyHistory/msa:DutyAssignmentHistory/msa:DutyAssignments
/msa:DutyAssignmentEntry/msa:Assignment"/>
<table>
<tr>
<td>Sound</td>
<td>
<xsl:value-of
select="$assignments[#Index='1' and #IndexType='Fixed']
[..[#Week=$Week and #Mode='Weekend' and #Template='0']]"/>
</td>
</tr>
If you want to consolidate the logic for generating the columns, you could define a template for msa:Assignment:
<xsl:template match="msa:Assignment">
<td>
<xsl:value-of select="."/>
</td>
</xsl:template>
And then use it like this:
<table>
<tr>
<td>Sound</td>
<xsl:apply-templates select="$weekend-assignments[#Index='1' and #IndexType='Fixed']"/>
If you want to consolidate the logic for generating rows, you could define a template for msa:Assignment and send in a parameter for the first column:
<xsl:template match="msa:Assignment">
<xsl:param name="label"/>
<tr>
<td><xsl:value-of select="$label"/></td>
<td>
<xsl:value-of select="."/>
</td>
</tr>
</xsl:template>
And then use it like this:
<table>
<xsl:apply-templates select="$weekend-assignments[#Index='1' and #IndexType='Fixed']">
<xsl:with-param name="label" select="'Sound'"/>
</xsl:apply-templates>
I'm using XSL1.0. My editor/debugger is OxygenXML with Saxon (OxygenXML can't debug with MSXML) and it will deployed to work with a 3rd party app that only uses MSXML. This means I can't use a variable containing a nodeset if I want to be able to debug.
The problem could probably be expressed as how to sequentially number output of the following -
<xsl:for-each select="node1">
<xsl:variable name="current_ID" select="ID">
<xsl:for-each select="sub_node1">
<xsl:value-of select="../ID"/>-<xsl:value-of select="Sub_ID"/>
</xsl:for-each>
</xsl:for-each>
understanding that I cannot simply use this in my scenario:
<xsl:for-each select="node1/sub_node1">
<xsl:value-of select="position()"/>
</xsl:for-each>
Below is a manufactured example that shows the problem I'm trying to solve as part of a much larger XSL/XML combo. I basically need to create manufacturing instructions. All nodes in the XML with the exception of products/versions (by qty) are in the correct order and I cannot change it. I need to generate the same set of sequential numbers from 3 different XSL's. My current context will always be shipments/deliveries/delivery_products (i.e. my XSL has to process the nodes in the seq shown). I need to produce a list of products sorted by version qty and their deliveries. Each row should have a sequential no (1-4) in example below
<shipments>
<product>
<name>Product 1</name>
<prod_id>P1</prod_id>
<version>
<version_id>P1_V1</version_id>
<qty>8800</qty>
</version>
<version>
<version_id>P1_V2</version_id>
<qty>1100</qty>
</version>
<version>
<version_id>P1_V3</version_id>
<qty>100</qty>
</version>
</product>
<product>
<name>Product 2</name>
<prod_id>P2</prod_id>
<version>
<version_id>P2_V1</version_id>
<qty>5000</qty>
</version>
<version>
<version_id>P2_V2</version_id>
<qty>5000</qty>
</version>
<version>
<version_id>P2_V3</version_id>
<qty>2000</qty>
</version>
</product>
<deliveries>
<del_id>1</del_id>
<destination>Miami</destination>
<delivery_products>
<version_id>P1_V1</version_id>
<qty>8000</qty>
</delivery_products>
<delivery_products>
<version_id>P2_V1</version_id>
<qty>5000</qty>
</delivery_products>
</deliveries>
<deliveries>
<del_id>2</del_id>
<destination>New York</destination>
<delivery_products>
<version_id>P1_V1</version_id>
<qty>800</qty>
</delivery_products>
<delivery_products>
<version_id>P2_V2</version_id>
<qty>1000</qty>
</delivery_products>
</deliveries>
Expected output is below. Note seq # starts from 1 and counts up to 4
<table>
<thead>
<tr>
<td class="col_head">
Seq
</td>
<td class="col_head">
Version
</td>
<td class="col_head">
Destination
</td>
<td class="col_head">
Qty
</td>
</tr>
</thead>
<tr>
<td colspan="4" class="rev_heading">Product 1</td>
</tr>
<tr>
<td>1</td>
<td>P1_V1</td>
<td>Miami</td>
<td>8000</td>
</tr>
<tr>
<td>2</td>
<td>P1_V1</td>
<td>New York</td>
<td>800</td>
</tr>
<tr>
<td colspan="4" class="rev_heading">Product 2</td>
</tr>
<tr>
<td>3</td>
<td>P2_V1</td>
<td>Miami</td>
<td>5000</td>
</tr>
<tr>
<td>4</td>
<td>P2_V2</td>
<td>New York</td>
<td>5000</td>
</tr>
</table>
Here's my XSL so far (just stuck a position() in for a place holder for the seq #)
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format">
<xsl:output method="html" doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"/>
<xsl:template match="shipments">
<html>
<head>
<title>Seq Test</title>
<style type="text/css">
table {border: 1px solid black; border-collapse: collapse;}
td {border: 1px solid black; padding: 1px 5px 1px 5px;}
.col_head {font-weight: 600;}
.rev_heading {color: red; text-align: center; padding-top: 15px;}
</style>
</head>
<body>
<table>
<thead>
<tr>
<!-- SEQ# -->
<td class="col_head">
Seq
</td>
<!-- Imprint/Version -->
<td class="col_head">
Version
</td>
<!-- Ship to -->
<td class="col_head">
Destination
</td>
<!-- Qty -->
<td class="col_head">
Qty
</td>
</tr>
</thead>
<xsl:for-each select="product">
<xsl:sort data-type="number" select="qty"/>
<xsl:for-each select="version">
<xsl:variable name="curr_version" select="version_id"/>
<xsl:if test="position() = 1">
<tr>
<td colspan="4" class="rev_heading">
<xsl:value-of select="../name"/>
</td>
</tr>
</xsl:if>
<xsl:for-each select="../../deliveries/delivery_products[version_id = $curr_version]">
<tr >
<!-- SEQ# -->
<td>
<xsl:value-of select="position()"/>
</td>
<!-- Version -->
<td>
<xsl:value-of select="version_id"/>
</td>
<!-- Ship to -->
<td>
<xsl:value-of select="../destination"/>
</td>
<!-- QTY -->
<td>
<xsl:value-of select="qty"/>
</td>
</tr>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
I'm using XSL1.0. My editor/debugger is OxygenXML with Saxon
(OxygenXML can't debug with MSXML) and it will deployed to work with a
3rd party app that only uses MSXML. This means I can't use a variable
containing a nodeset if I want to be able to debug.
You can still use oXygen and the EXSLT node-set() function.
When you are finished, simply change the namespace-uri from "http://exslt.org/common" to "urn:schemas-microsoft-com:xslt"
Here is a short example of this technique. Suppose you are finished debugging the below transformation:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<xsl:variable name="vrtfPass1">
<xsl:apply-templates select="num[. mod 3 = 0]"/>
</xsl:variable>
<xsl:copy-of select="sum(ext:node-set($vrtfPass1)/*)"/>
</xsl:template>
<xsl:template match="num">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
Then you make the change from the EXSLT namespace-uri to the MSXSL namespace uri:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="urn:schemas-microsoft-com:xslt">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<xsl:variable name="vrtfPass1">
<xsl:apply-templates select="num[. mod 3 = 0]"/>
</xsl:variable>
<xsl:copy-of select="sum(ext:node-set($vrtfPass1)/*)"/>
</xsl:template>
<xsl:template match="num">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
Finally, you run this last transformation with MSXML and it produces exactly the same result as the initial transformation that uses EXSLT:
18
Here is a fraction of the XML data I am processing
<?xml version="1.0" encoding="utf-16"?>
<ScorecardSummary>
<DivisionSummary>
<DivisionName>
<string> SYSTEM</string>
</DivisionName>
<ScorecardSummaryByDivision>
<ScorecardSummaryByKPI>
<Header>
<string>Committed Time of Arrival</string>
<string>Goal</string>
<string>1D</string>
<string>7D</string>
<string>QTD</string>
<string>YTD</string>
<string>YTD Event Cars</string>
</Header>
<Data>
<ScorecardContract>
<TypeName>System</TypeName>
<Goal>68</Goal>
<GoalWarning>64.6</GoalWarning>
<TotalCountYear>1234</TotalCountYear>
<Value1D>79</Value1D>
<Value7D>79.2</Value7D>
<ValueQTD>79.1</ValueQTD>
<ValueYTD>73.3</ValueYTD>
</ScorecardContract>
<ScorecardContract>
<TypeName>AG</TypeName>
<Goal>68</Goal>
<GoalWarning>64.6</GoalWarning>
<TotalCountYear>1111</TotalCountYear>
<Value1D>80.9</Value1D>
<Value7D>78.7</Value7D>
<ValueQTD>78.4</ValueQTD>
<ValueYTD>69.7</ValueYTD>
</ScorecardContract>
This is a small part of the XSL that produces the tables:
<xsl:template match="ScorecardSummary/DivisionSummary/DivisionName">
<h1>
<xsl:value-of select="current()/string"/>
</h1>
</xsl:template>
<xsl:template match="ScorecardSummaryByDivision">
<xsl:apply-templates select="current()/ScorecardSummaryByKPI"/>
</xsl:template>
<xsl:template match="ScorecardSummaryByKPI">
<table border="1" cellspacing="0" cellpadding="5">
<tr>
<xsl:choose>
<xsl:when test="count(preceding-sibling::ScorecardSummaryByKPI) mod 6 < 4">
<td>
<table border="1" cellspacing="0" cellpadding="5">
<xsl:apply-templates select="Header"/>
<xsl:apply-templates select="Data"/>
</table>
</td>
</xsl:when>
<xsl:otherwise>
<td>
<table border="1" cellspacing="0" cellpadding="5">
<xsl:apply-templates select="Header"/>
<xsl:apply-templates select="Data"/>
</table>
</td>
</xsl:otherwise>
</xsl:choose>
</tr>
</table>
</xsl:template>
The XSL produces 6 tables repeatedly like this:
1
2
3
4
5
6
1
2
3
4
5
6
But I want to order them like this:
1 4
2 5
3 6
1 4
2 5
3 6
and so on. I tried using this check, but it doesn't work.
count(preceding-sibling::ScorecardSummaryByKPI) mod 6 < 4
Can anyone help?
Explanation
Your table must have two <td> per row (if you want two columns). Your XSLT does generate only one.
Solution is to interate over one half of the list and generate two <td> per iteration.
So first I would define a size of the table. Example:
<xsl:param name="size" select="count(catalog/cd)"/>
Then iterate over only a half of it ($size div 2). The number must be rounded if the input list can contain a non-even number of elements: ceiling($size div 2) (Rounding up to catch last element)
<xsl:for-each select="catalog/cd[ceiling($size div 2) >= position()]">
In each iteration, first render an element itself:
<td><xsl:value-of select="title"/></td>
Then render an appropriate element from the second half of the table (offset is the number defined before: ceiling($size div 2) Half size of the table)
<td><xsl:value-of select="following::cd[ceiling($size div 2)]/title"/></td>
You can wrap element rendering in a separate template to avoid code repeating.
Working example
Check this transformation example with W3C XSL TryIt (http://www.w3schools.com/xsl/tryxslt.asp?xmlfile=cdcatalog&xsltfile=cdcatalog):
<?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="/">
<xsl:param name="size" select="count(catalog/cd)"/>
<html>
<body>
<h2>My CD Collection</h2>
<table border="1">
<tr bgcolor="#9acd32">
<th>Title</th>
<th>Title</th>
</tr>
<xsl:for-each select="catalog/cd[ceiling($size div 2) >= position()]">
<tr>
<td><xsl:value-of select="title"/></td>
<td><xsl:value-of select="following::cd[ceiling($size div 2)]/title"/></td>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
It splits CD-catalog (given in example link above) in two columns.
Perhaps something like this is what you are looking for:
(This only shows the idea, you have to adapt it to your input.)
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns="http://www.w3.org/1999/xhtml">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/" >
<xsl:apply-templates select="//t[count(preceding-sibling::t) < 3]" mode="tables" />
</xsl:template>
<xsl:template match="t" >
{<xsl:value-of select="text()"/>}
</xsl:template>
<xsl:template match="t" mode="tables">
<table border="1">
<tr>
<td>
<table border="1" >
<xsl:apply-templates select="." />
</table>
</td>
<td>
<table border="1">
<xsl:apply-templates select="following-sibling::t[count(preceding-sibling::t) = count(current()/preceding-sibling::t) +3]" />
</table>
</td>
</tr>
</table>
</xsl:template>
</xsl:stylesheet>
With this short test input xml:
<?xml version="1.0" encoding="utf-8"?>
<xml>
<tables>
<t>1</t>
<t>2</t>
<t>3</t>
<t>4</t>
<t>5</t>
<t>6</t>
</tables>
</xml>
It will generate this output:
<?xml version="1.0"?>
<table xmlns="http://www.w3.org/1999/xhtml" border="1">
<tr>
<td>
<table border="1">
{1}
</table>
</td>
<td>
<table border="1">
{4}
</table>
</td>
</tr>
</table><table xmlns="http://www.w3.org/1999/xhtml" border="1">
<tr>
<td>
<table border="1">
{2}
</table>
</td>
<td>
<table border="1">
{5}
</table>
</td>
</tr>
</table><table xmlns="http://www.w3.org/1999/xhtml" border="1">
<tr>
<td>
<table border="1">
{3}
</table>
</td>
<td>
<table border="1">
{6}
</table>
</td>
</tr>
</table>
I have the following xml:
input
<page>
<group category="cat1">
<item fileunder="#">.45 colt</item>
<item fileunder="#">8 queens</item>
<item fileunder="#">9 lives</item>
<item fileunder="#">99 bottles of beer</item>
<item fileunder="A">An innocent man</item>
<item fileunder="A">Academy awards</item>
<item fileunder="B">Before the dawn</item>
</group>
<group category="cat2">
<item fileunder="R">Rows of houses</item>
</group>
</page>
The input items are already sorted.
desired output
I want to produce a 3-column HTML table for every group, with a subheading (a 3-column spanning cell) for each distinct fileunder, optimally presented in a top-down, then-next-column (the items are already sorted):
<table>
<tr><td colspan="3">#</td></tr>
<tr><td>.45 colt</td><td>9 lives</td><td>99 bottles of beer</td></tr>
<tr><td>8 queens</td></tr>
<tr><td colspan="3">A</td></tr>
<tr><td>An innocent man</td><td>Academy awards</td></tr>
<tr><td colspan="3">B</td></tr>
<tr><td>Before the dawn</td></tr>
</table>
<table>
<tr><td colspan="3">R</td></tr>
<tr><td>Rows of houses</td></tr>
</table>
I can live if the items are presented as left-to-right, then-next-row.
What I have so far is:
current xslt
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="itm_grp" match="/page/group/item" use="concat(../#category,':',#fileunder)"/>
<xsl:template match="page/group">
<table>
<xsl:for-each select="item[.=key('itm_grp',concat(../#category,':',#fileunder))[1]]">
<tr><td colspan="3"><xsl:value-of select="#fileunder"/></td></tr>
<xsl:variable name="nodeset" select="key('itm_grp',concat(../#category,':',#fileunder))"/>
<xsl:for-each select="$nodeset[position() mod 3=1]">
<tr>
<td><xsl:value-of select="."/></td>
<td><xsl:value-of select="following-sibling::item[1]"/></td>
<td><xsl:value-of select="following-sibling::item[2]"/></td>
</tr>
</xsl:for-each>
</xsl:for-each>
</table>
</xsl:template>
</xsl:stylesheet>
which produces a left-to-right, then-next-row output (non-optimal); however, the following-sibling selects produce a “bleed-through” effect:
#
.45 colt 8 queens 9 lives
99 bottles of beer An innocent man Academy awards
A
An innocent man Academy awards Before the dawn
B
Before the dawn
R
Rows of houses
As you can see, fileunder # has two A items, and fileunder A has one B item.
So, my question is:
How can I produce the desired output (column-wise)?
If I can't do that, how can I have the row-wise output avoiding the “bleeding”?
Please note that I have very little experience with XSLT, so if my code is blatantly inefficient/idiotic/whatever, please feel free to educate me by replacing all of it!
NB: XSLT version 1, so apparently no index-of function is available.
There is a slight contradiction between your narrative and your listed expected output. You have asked for top-down, then left-right column fill order, which you have so in the listing for the non-empty values, but not for the empties. This spatial order implies that a whole column must be filled out before the next column can begin. I have assumed that your listing was a mistake and what your really want in output is ...
<table>
<tr>
<td colspan="3">#</td>
</tr>
<tr>
<td>.45 colt</td>
<td>9 lives</td>
<td>&npsp;</td>
</tr>
<tr>
<td>8 queens</td>
<td>99 bottles of beer</td>
<td>&npsp;</td>
</tr>
<tr>
<td colspan="3">A</td>
</tr>
<tr>
<td>An innocent man</td>
<td>Academy awards</td>
<td>&npsp;</td>
</tr>
<tr>
<td colspan="3">B</td>
</tr>
<tr>
<td>Before the dawn</td>
<td>&npsp;</td>
<td>&npsp;</td>
</tr>
</table>
<table>
<tr>
<td colspan="3">R</td>
</tr>
<tr>
<td>Rows of houses</td>
<td>&npsp;</td>
<td>&npsp;</td>
</tr>
</table>
... which is consistent top-down, then left-right column fill order.
This XSLT 1.0 style-sheet...
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" doctype-system="about:legacy-compat" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="kItemByFile" match="item" use="concat(../#category,':',#fileunder)"/>
<xsl:template match="/">
<html lang="en">
<head><title>Songs</title></head>
<body>
<xsl:apply-templates select="*/group" />
</body>
</html>
</xsl:template>
<xsl:template match="group">
<xsl:variable name="cat" select="concat(#category,':')" />
<table>
<xsl:apply-templates select="item[
generate-id() = generate-id(key('kItemByFile',concat($cat,#fileunder))[1])]"
mode="group-head" />
</table>
</xsl:template>
<xsl:template match="item" mode="group-head">
<xsl:variable name="items"
select="key('kItemByFile',concat(../#category,':',#fileunder))" />
<xsl:variable name="row-count" select="ceiling( count($items) div 3)" />
<tr><td colspan="3"><xsl:value-of select="#fileunder" /></td></tr>
<xsl:for-each select="$items[position() <= $row-count]">
<xsl:variable name="pos" select="position()" />
<xsl:apply-templates select="." mode="row">
<xsl:with-param name="items" select="$items" />
<xsl:with-param name="row" select="$pos" />
<xsl:with-param name="row-count" select="$row-count" />
</xsl:apply-templates>
</xsl:for-each>
</xsl:template>
<xsl:template match="item" mode="row">
<xsl:param name="items" select="/.." />
<xsl:param name="row" select="1" />
<xsl:param name="row-count" select="1" />
<tr>
<xsl:apply-templates select="
$items[(position() mod $row-count) = ($row mod $row-count)]" mode="td" />
<xsl:variable name="full-cols" select="floor((count($items) div $row-count))" />
<xsl:variable name="part-col" select="number($row <
((count($items) mod $row-count) + 1))" />
<xsl:variable name="empties" select="3 - ($full-cols + $part-col)" />
<xsl:for-each select="(document('')/*/*)[position() <= $empties]">
<xsl:call-template name="empty-cell" />
</xsl:for-each>
</tr>
</xsl:template>
<xsl:template match="item" mode="td">
<td><xsl:value-of select="." /></td>
</xsl:template>
<xsl:template name="empty-cell">
<td> </td>
</xsl:template>
</xsl:stylesheet>
...when applied to this input...
<page>
<group category="cat1">
<item fileunder="#">.45 colt</item>
<item fileunder="#">8 queens</item>
<item fileunder="#">9 lives</item>
<item fileunder="#">99 bottles of beer</item>
<item fileunder="A">An innocent man</item>
<item fileunder="A">Academy awards</item>
<item fileunder="B">Before the dawn</item>
</group>
<group category="cat2">
<item fileunder="R">Rows of houses</item>
</group>
</page>
...yields...
<!DOCTYPE html SYSTEM "about:legacy-compat">
<html lang="en">
<head>
<META http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Songs</title>
</head>
<body>
<table>
<tr>
<td colspan="3">#</td>
</tr>
<tr>
<td>.45 colt</td>
<td>9 lives</td>
<td> </td>
</tr>
<tr>
<td>8 queens</td>
<td>99 bottles of beer</td>
<td> </td>
</tr>
<tr>
<td colspan="3">A</td>
</tr>
<tr>
<td>An innocent man</td>
<td>Academy awards</td>
<td> </td>
</tr>
<tr>
<td colspan="3">B</td>
</tr>
<tr>
<td>Before the dawn</td>
<td> </td>
<td> </td>
</tr>
</table>
<table>
<tr>
<td colspan="3">R</td>
</tr>
<tr>
<td>Rows of houses</td>
<td> </td>
<td> </td>
</tr>
</table>
</body>
</html>
Note
For the empty cells in the output, when viewing the lexical HTML, you will get either or the literal white space equivalent. It is XSLT processor implementation dependant, but should not cause you any concern because it is model-equivalent.
Easiest way to fix that:
<xsl:variable name="header" select="#fileunder"/>
...
<xsl:value-of select="following-sibling::item[#fileunder=$header][1]"/>
<xsl:value-of select="following-sibling::item[#fileunder=$header][2]"/>
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>