1. | Arrange data in multiple columns |
| David Carlisle
> When an item is the 5th, 10th, 15th etc.
> (using "position() mod 5 =0")
> it should close this row and start the next.
Wrong viewpoint! In XSL you should think in terms of
constructing a tree not of writing out a linear XML
syntax. Clearly in a `tree view' there is no sense to
</tr><tr>.
What you want to do is select every 5th node, and put that
node and its four next siblings into a tr node. so
(untested)
<xsl:for-each select="item[position() mod 5 = 1]">
<tr>
<xsl:apply-templates
select=".|following-sibling::item[position() < 5]"/>
</tr>
<xsl:for-each>
|
2. | Generating a table, Example |
| Steve Muench
| I need some help figuring out how to do a somewhat complex transform (XML
| into HTML) that I need help with. Its essentially a cross-tab type
| situation.
Here's a stylesheet that produces the cross-product
you're looking for. It uses an <xsl:key> like functional
indexes to speed up the iteration of unique regions.
Assuming the input is:
<demo>
<salesman id="0001" name="Rick Peterson">
<account id="act001" region="Midwest">Johnosn's Laundry</account>
<account id="act002" region="Canada">Franks's Laundry</account>
<account id="act003" region="Alaska">Mary's Laundry</account>
<account id="act004" region="New Jersey">Bill's Laundry</account>
<account id="act005" region="Midwest">Hammond's Laundry</account>
</salesman>
<salesman id="0003" name="Ty Coon">
<account id="act006" region="Canada">Franks's Diner</account>
<account id="act007" region="Midwest">Johnosn's Diner</account>
<account id="act008" region="Canada">Hammond's Diner</account>
<account id="act009" region="Alaska">Mary's Diner</account>
<account id="act010" region="Alaska">Bill's Diner</account>
</salesman>
</demo>
The following stylesheet produces an HTML table crosstab
with salespeople down the left (ordered by name) and regions
across the top (ordered by name), with a grid cell for each
crosstab, and &nsbp; for empty cells.
<!-- Example of a "CrossTab" stylesheet
using <xsl:key>'s for grouping -->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="b" match="@region" use="."/>
<xsl:template match="/">
<html>
<body>
<table border="1" cellspacing="0">
<!-- Generate the row of table header cells -->
<tr>
<th>Salesperson</th>
<!--
| Generate a header cell for each unique region name
|
| See Chapter 9 of "Building Oracle XML Applications"
from O'Reilly
| for a detailed explanation of how this
<xsl:key> based technique works
+-->
<xsl:for-each
select="//@region[generate-id(.)=generate-id(key('b',.)[1])]">
<!-- Sort by the region name
(the value of the current @region attribute -->
<xsl:sort select="."/>
<th>
<xsl:value-of select="."/>
</th>
</xsl:for-each>
</tr>
<!-- Generate a row for each salesman -->
<xsl:for-each select="demo/salesman">
<!-- Sort by salesman name -->
<xsl:sort select="@name"/>
<!-- Keep the current salesman in a variable for later -->
<xsl:variable name="cur" select="."/>
<tr>
<!-- First cell has the salesman's name -->
<td bgcolor="yellow">
<xsl:value-of select="@name"/></td>
<!-- Generate a cell for each unique region -->
<xsl:for-each
select="//@region[generate-id(.)=
generate-id(key('b',.)[1])]">
<td>
<!-- If no accts for current salesman in current region,
do -->
<xsl:if
test="not($cur/account[@region=current()])">
 </xsl:if>
<!-- List matching accounts for current salesman in current region -->
<xsl:for-each select="$cur/account[@region=current()]">
<xsl:value-of select="."/>
<xsl:if test="position() != last()"><br/></xsl:if>
</xsl:for-each>
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
|
3. | Making balanced two-column tables from one-column data |
| Mike Brown
4.17.1
Input
<?xml version='1.0'?>
<TASKS>
<TASK>
<COMPONENTS>
<COMPONENT>A</COMPONENT>
<COMPONENT>B</COMPONENT>
<COMPONENT>C</COMPONENT>
<COMPONENT>D</COMPONENT>
<COMPONENT>E</COMPONENT>
<COMPONENT>F</COMPONENT>
<COMPONENT>G</COMPONENT>
<COMPONENT>H</COMPONENT>
<COMPONENT>I</COMPONENT>
<COMPONENT>J</COMPONENT>
<COMPONENT>K</COMPONENT>
</COMPONENTS>
</TASK>
</TASKS>
xsl
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html" indent="yes"/>
<xsl:template match="/">
<xsl:apply-templates/>
</xsl:template>
<xsl:template match="TASKS/TASK/COMPONENTS">
<xsl:variable name="t-size" select="count(COMPONENT)"/>
<xsl:variable name="half" select="ceiling($t-size div 2)"/>
<TABLE>
<xsl:for-each select="COMPONENT[position() <= $half]">
<xsl:variable name="here" select="position()"/>
<TR>
<TD><xsl:value-of select="."/></TD>
<TD>
<xsl:choose>
<xsl:when test="../COMPONENT[$here+$half]">
<xsl:value-of select="../COMPONENT[$here+$half]"/>
</xsl:when>
<xsl:otherwise></xsl:otherwise>
</xsl:choose>
</TD>
</TR>
</xsl:for-each>
</TABLE>
</xsl:template>
</xsl:stylesheet>
Result
<TABLE>
<TR>
<TD>A</TD><TD>G</TD>
</TR>
<TR>
<TD>B</TD><TD>H</TD>
</TR>
<TR>
<TD>C</TD><TD>I</TD>
</TR>
<TR>
<TD>D</TD><TD>J</TD>
</TR>
<TR>
<TD>E</TD><TD>K</TD>
</TR>
<TR>
<TD>F</TD><TD></TD>
</TR>
</TABLE>
</TR>
</xsl:for-each>
</TABLE>
|
4. | Balanced tables from a single list, using attribute values |
| Mark Eisenhut Example of creating two column balanced table from one column data using
XML element attributes. Example input
<?xml version='1.0'?>
<TASKS>
<TASK>
<COMPONENTS>
<COMPONENT type="A"></COMPONENT>
<COMPONENT type="B"></COMPONENT>
<COMPONENT type="C"></COMPONENT>
<COMPONENT type="D"></COMPONENT>
<COMPONENT type="E"></COMPONENT>
<COMPONENT type="F"></COMPONENT>
<COMPONENT type="G"></COMPONENT>
<COMPONENT type="H"></COMPONENT>
<COMPONENT type="I"></COMPONENT>
<COMPONENT type="J"></COMPONENT>
<COMPONENT type="K"></COMPONENT>
</COMPONENTS>
</TASK>
</TASKS>
And the XSLT <xsl:template match="TASKS/TASK/COMPONENTS">
<xsl:variable name="t-size" select="count(COMPONENT)"/>
<xsl:variable name="half" select="ceiling($t-size div 2)"/>
<TABLE border="1">
<xsl:for-each select="COMPONENT[position() <= $half]">
<xsl:variable name="here" select="position()"/>
<TR>
<TD><xsl:value-of select="@type"/></TD>
<TD>
<xsl:choose>
<xsl:when test="../COMPONENT[$here+$half]">
<xsl:value-of select="../COMPONENT[$here+$half]/@type"/>
</xsl:when>
<xsl:otherwise></xsl:otherwise>
</xsl:choose>
</TD>
</TR>
</xsl:for-each>
</TABLE>
|
5. | Alternate colors on TABLE rows |
| Steve Muench
| Does anyone have any tips for adding a background color to every *alternate*
| TABLE row in the result tree?
There are lots of ways to do this, but the one I've settled
on in the XSLT-driven database apps I build is the following:
(1) I create a CSS Stylesheet containing two CSS classes
like "row0" and "row1" as follows (I shorten to "r0" and "r1"):
# My CSS File named "Something.css"
.r0 {background-color: #f9f9f9}
.r1 {background-color: #f7f7e7}
(2) I create an XSLT stylesheet that creates an HTML page
that links to this Something.css stylesheet: <!-- Root template of my stylesheet -->
<xsl:template match="/">
<html>
<head>
<title>Cool XSLT App</title>
<link rel="stylesheet" type="text/css" href="Something.css"/>
</head>
<body><xsl:apply-templates/></body>
</html>
</xsl:template>
(3) In my template that is creating HTML table rows, I
use an attribute value template to alternate the
*name* of the CSS class in use for that row to
"toggle" between the names "r0" for even rows and
"r1" for odd rows...
<!-- Match a row of database query query results in XML -->
<xsl:template match="ROW">
<tr class="r{position() mod 2}">
<xsl:apply-templates/>
</tr>
</xsl:template>
The expression {position() mod 2} will alternate
between the values 1 and 0 so the effective value
of the "class" attribute on the <tr> element I'm
creating is "r1" and "r0". This way, I can control the fonts/colors of my entire
site by touching a single CSS file, while XSLT takes
care of all the fancy stuff. |
6. | Turning columns into Rows |
| Francis Norton
The column-first representation of the data is somewhat
counter-intuitive, and normally unhelpful.
However it's not a big deal. Here's a solution
t.xml
<?xml version="1.0"?>
<root>
<field name="field1">
<string>field1.row1</string>
<string>field1.row2</string>
<string>field1.row3</string>
<string>field1.row4</string>
</field>
<field name="field2">
<string>field2.row1</string>
<string>field2.row2</string>
<string>field2.row3</string>
<string>field2.row4</string>
</field>
<field name="field3">
<string>field3.row1</string>
<string>field3.row2</string>
<string>field3.row3</string>
<string>field3.row4</string>
</field>
</root>
t.xsl
<?xml version="1.0" encoding="iso-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- main body and loop -->
<xsl:template match="/">
<html>
<head>
<title>Results</title>
</head>
<body>
<table>
<!-- get horizontal - <xsl:for-each> is inside the <tr> -->
<tr>
<xsl:for-each select="//field">
<th>
<xsl:apply-templates select="@name"/>
</th>
</xsl:for-each>
</tr>
<!-- get vertical - <tr>s are inside the <xsl:for-each> -->
<xsl:for-each select="//field[1]/string">
<tr>
<xsl:call-template name="row">
<xsl:with-param name="row-no" select="position()"/>
</xsl:call-template>
</tr>
</xsl:for-each>
</table>
</body>
</html>
</xsl:template>
<!-- do a row here - use parameter to get position() into pattern -->
<xsl:template name="row">
<xsl:param name="row-no"/>
<xsl:for-each select="//field/string[position() = $row-no]">
<td>
<xsl:apply-templates/>
</td>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
|
7. | Broken tables |
| David Carlisle
Given <TABROW><TT>ati</TT><COLSEP>ATI</TABROW>
<TABROW><TT>r128</TT><COLSEP>ATI Rage 128</TABROW>
Each table row, instead of tagging up the cell data,
tags up the seperators!
<TABLE ALIGN="CENTER" BORDER="1">
<TABULAR CA="|l|l|">
<TABROW>Driver Name<COLSEP/></TABROW>
<TABROW><TT>apm</TT><COLSEP/>Alliance Pro Motion</TABROW>
<TABROW><TT>ati</TT><COLSEP/>ATI</TABROW>
<TABROW><TT>chips</TT><COLSEP/>Chips & Technologies</TABROW>
<TABROW><TT>cirrus</TT><COLSEP/>Cirrus Logic</TABROW>
<TABROW><TT>cyrix</TT> (*)<COLSEP/>Cyrix MediaGX</TABROW>
<TABROW><TT>fbdev</TT><COLSEP/>Linux fbdev</TABROW>
<TABROW><TT>glide</TT><COLSEP/>Glide2x (3Dfx)</TABROW>
<TABROW><TT>glint</TT><COLSEP/>3Dlabs, TI</TABROW>
<TABROW><TT>i740</TT><COLSEP/>Intel i740</TABROW>
<TABROW><TT>i810</TT><COLSEP/>Intel i810</TABROW>
<TABROW><TT>mga</TT><COLSEP/>Matrox</TABROW>
<TABROW><TT>neomagic</TT><COLSEP/>NeoMagic</TABROW>
<TABROW><TT>nv</TT><COLSEP/>NVIDIA</TABROW>
<TABROW><TT>r128</TT><COLSEP/>ATI Rage 128</TABROW>
<TABROW><TT>rendition</TT><COLSEP/>Rendition</TABROW>
<TABROW><TT>s3virge</TT><COLSEP/>S3 ViRGE</TABROW>
<TABROW><TT>sis</TT><COLSEP/>SiS</TABROW>
<TABROW><TT>tdfx</TT><COLSEP/>3Dfx</TABROW>
<TABROW><TT>tga</TT><COLSEP/>DEC TGA</TABROW>
<TABROW><TT>trident</TT><COLSEP/>Trident</TABROW>
<TABROW><TT>tseng</TT><COLSEP/>Tseng Labs</TABROW>
<TABROW><TT>vga</TT><COLSEP/>Generic VGA</TABROW>
</TABULAR>
</TABLE>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0"
>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="TABULAR">
<tbody>
<xsl:apply-templates/>
</tbody>
</xsl:template>
<xsl:template match="TABROW">
<row>
<xsl:call-template name="entry"/>
</row>
</xsl:template>
<xsl:template match="TT">
<tt>
<xsl:apply-templates/>
</tt>
</xsl:template>
<xsl:template name="entry">
<xsl:param name="x" select="0"/>
<entry>
<xsl:apply-templates
select="node()[count(preceding-sibling::COLSEP)=$x]"/>
</entry>
<xsl:if test="$x < count(COLSEP)">
<xsl:call-template name="entry">
<xsl:with-param select="$x+1" name="x"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
|
8. | Generating balanced tables |
| Warren Hedley My xml tables don't have the same number of cells
in each row. How can I convert such that the output
contains empty cells for missing ones on the input.
The following stylesheet should do what you want (and
does with Saxon 5.3.2). Obviously there are some
optimisations possible - all of "match"ed templates
could be moved into the root template, for instance -
but I separated it out for clarity. Sorry, no
documentation, but I think it's pretty clear what's
going on - the key thing to note is that you have
to use recursion to implement counting functionality
in XSLT.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="Table">
<xsl:variable name="num_columns">
<xsl:call-template name="count_columns" />
</xsl:variable>
<table>
<xsl:apply-templates select="HeadRow">
<xsl:with-param name="num_columns" select="$num_columns" />
</xsl:apply-templates>
<xsl:apply-templates select="Row">
<xsl:with-param name="num_columns" select="$num_columns" />
</xsl:apply-templates>
<xsl:apply-templates select="Caption">
<xsl:with-param name="num_columns" select="$num_columns" />
</xsl:apply-templates>
</table>
</xsl:template>
<xsl:template name="count_columns">
<xsl:param name="num_columns" select="'0'" />
<xsl:param name="num_rows" select="count(HeadRow) + count(Row)" />
<xsl:param name="current_row" select="1" />
<xsl:choose>
<xsl:when test="$current_row < $num_rows">
<xsl:variable name="row" select="(HeadRow | Row)[$current_row]" />
<xsl:choose>
<xsl:when test="count($row/Cell) > $num_columns">
<xsl:call-template name="count_columns">
<xsl:with-param name="num_columns" select="count($row/Cell)" />
<xsl:with-param name="num_rows" select="$num_rows" />
<xsl:with-param name="current_row" select="$current_row + 1" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="count_columns">
<xsl:with-param name="num_columns" select="$num_columns" />
<xsl:with-param name="num_rows" select="$num_rows" />
<xsl:with-param name="current_row" select="$current_row + 1" />
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$num_columns" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="HeadRow">
<xsl:param name="num_columns" />
<thead>
<tr>
<xsl:for-each select="Cell">
<td><xsl:copy-of select="*|text()" /></td>
</xsl:for-each>
<xsl:call-template name="insert_blank_cells">
<xsl:with-param name="num_cells" select="$num_columns - count(Cell)" />
</xsl:call-template>
</tr>
</thead>
</xsl:template>
<xsl:template match="Row">
<xsl:param name="num_columns" />
<tr>
<xsl:for-each select="Cell">
<td><xsl:copy-of select="*|text()" /></td>
</xsl:for-each>
<xsl:call-template name="insert_blank_cells">
<xsl:with-param name="num_cells" select="$num_columns - count(Cell)" />
</xsl:call-template>
</tr>
</xsl:template>
<xsl:template match="Caption">
<xsl:param name="num_columns" />
<tr>
<td colspan="{$num_columns}"><xsl:copy-of select="*|text()" /></td>
</tr>
</xsl:template>
<xsl:template name="insert_blank_cells">
<xsl:param name="num_cells" />
<xsl:if test="$num_cells > 0">
<td><xsl:value-of select="' '" /></td>
<xsl:call-template name="insert_blank_cells">
<xsl:with-param name="num_cells" select="$num_cells - 1" />
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
|
9. | XSL Table Styling |
| Mike Brown
> What I'm trying to do is lay out two rows of data with 4 items per row
Generally when people ask questions about HTML tables, they've tried a
tag-based approach, which inevitably fails because they are thinking
about how to arbitrarily declare the beginning and end of each row. In
the well-formed world of XML and XSLT, one needs a more object-oriented
approach. If you break the problem down into the answers to these
questions, you might see your solution more quickly:
- What always determines when a new row is added?
- What always goes into each row?
- What always goes into each cell?
In your case, for each "row" of data, you're wanting to create 3 table
rows. Your stylesheet is trying to create a new set of table rows for
each *unit* of data (each 'hit' element that is matched), so of course
you end up with too many table rows and not enough columns in your
table.
I'm guessing your XML looks something like
<result>
<hit>
<name>Foo</name>
<small-image>foo.jpg</small-image>
<short-desc>This is foo.</short-desc>
</hit>
...
</result> Here is one way to approach the problem, and it is flexible for any
number of desired columns with an unknown number of 'hit' elements.
It will also fill in cells in empty columns, in case the number of
'hit' elements isn't evenly divisible by the number of columns
desired. (untested code; watch for typos)
<xsl:template match="result">
<xsl:variable name="cols" select="4"/>
<xsl:variable name="all_hits" select="hit"/>
<table>
<!-- start a new data row for every 1st, 5th, 9th, etc. 'hit' element -->
<xsl:for-each select="$all_hits[position() mod $cols = 1]">
<xsl:variable name="this_hit_pos" select="position()"/>
<xsl:variable name="current_row_hits"
select="$all_hits[position() >= $this_hit_pos and position()
< $this_hit_pos + $cols]"/>
<!-- go generate the 3 table rows for this one data row -->
<xsl:call-template name="make_table_rows">
<xsl:with-param name="cols" select="$cols"/>
<xsl:with-param name="current_row_hits" select="$current_row_hits"/>
</xsl:call-template>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template name="make_table_rows">
<xsl:param name="cols" select="1"/>
<xsl:param name="current_row_hits" select="/.."/>
<!-- selects above are defaults in case nothing was passed in -->
<xsl:if test="$current_row_hits">
<xsl:variable name="num_empty_cols" select="$cols - $current_row_hits"/>
<tr>
<xsl:for-each select="$current_row_hits">
<td width="175">
<img width="175" height="175" src="{small-image}"/>
</td>
</xsl:for-each>
<xsl:if test="$num_empty_cols"> <!-- true if not zero -->
<xsl:call-template name="make_empty_cells">
<xsl:with-param name="num" select="$num_empty_cols"/>
</xsl:call-template>
</xsl:if>
</tr>
<tr>
<xsl:for-each select="$current_row_hits">
<td width="175">
<font face="Verdana" size="2">
<b>
<xsl:value-of select="name"/>
</b>
</font>
</td>
</xsl:for-each>
<xsl:if test="$num_empty_cols"> <!-- true if not zero -->
<xsl:call-template name="make_empty_cells">
<xsl:with-param name="num" select="$num_empty_cols"/>
</xsl:call-template>
</xsl:if>
</tr>
<tr>
<xsl:for-each select="$current_row_hits">
<td width="175">
<font face="Verdana" size="1">
<xsl:value-of select="short-desc"/>
</font>
</td>
</xsl:for-each>
<xsl:if test="$num_empty_cols"> <!-- true if not zero -->
<xsl:call-template name="make_empty_cells">
<xsl:with-param name="num" select="$num_empty_cols"/>
</xsl:call-template>
</xsl:if>
</tr>
</xsl:if>
</xsl:template>
<xsl:template name="make_empty_cells">
<xsl:param name="num" select="0"/>
<xsl:if test="$num">
<td> </td>
<xsl:call-template name="make_empty_cells">
<xsl:with-param name="num" select="$num - 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
|
10. | Output two diff. elements side by side in HTML |
| Jeni Tennison
>i.e. The text should flow around the image which can come, once on the
>left side, the second time, on the right side. The whole thing is inside
>a table and the images and paragraphs are cells.
I'm not exactly sure what the table you're after looks like, so I'm going
to assume it's something like:
<table>
<tr>
<td>image-1</td>
<td colspan="2">paragraph-1</td>
</tr>
<tr>
<td colspan="2">paragraph-2</td>
<td>image-2</td>
</tr>
<tr>
<td colspan="3">paragraph-3</td>
</tr>
...
</table>
To make it easy to find the images, I'm going to define a global variable
to hold them:
<xsl:variable name="images" select="/story/image" /> You need a template that matches something high up, either the root node
(/) or the document element (story) to manage the processing (limiting it
to the paragraphs) and to put in the HTML that wraps around your table:
<xsl:template match="story">
<html>
<head><title>A story</title></head>
<body>
<table>
<xsl:apply-templates select="paragraph" />
</table>
</body>
</html>
</xsl:template>
The paragraph-matching template needs to insert a row that includes the
paragraph into the table. If there is an image with the equivalent
position, it also needs to insert the image into the table. It can find
such an image, if there is one by finding the image within the $images
variable at the position of the current paragraph:
<xsl:variable name="position" select="position()" />
<xsl:variable name="image" select="$images[$position]" />
If it is inserting an image, and it's an odd paragraph, the image needs to
go in the first cell; if it's an even paragraph, the image needs to go in
the second cell. You can find out whether a paragraph is even or odd by
looking at the result of the position of the paragraph mod 2. This kind of
conditional processing is best managed using xsl:choose, xsl:when and
xsl:otherwise. I've nested it here for clarity:
<xsl:template match="paragraph">
<xsl:variable name="position" select="position()" />
<xsl:variable name="image" select="$images[$position]" />
<xsl:choose>
<xsl:when test="$image">
<xsl:choose>
<xsl:when test="$position mod 2 = 1">
<tr>
<td><img src="{$image}" /></td>
<td colspan="2"><xsl:value-of select="." /></td>
</tr>
</xsl:when>
<xsl:otherwise>
<tr>
<td colspan="2"><xsl:value-of select="." /></td>
<td><img src="{$image}" /></td>
</tr>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<tr><td colspan="3"><xsl:value-of select="." /></td></tr>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Using SAXON, this gives the output I was aiming for with the source that
you provided. I hope that it's close enough to what you want to give you
an idea about how to proceed.
|
11. | Multiple Rows in a Table |
| Mike Brown
> I am trying to create a Calendar table with a new row after every 7th day.
> Here is the XML I'm working with:
>
> <Month>
> <day date="1" />
> <day date="2"/>
> <day date="3" />
> [...]
> <day date="31"/>
> </Month> First I would constrain myself to a good template-based design pattern.
Once you've determined what days you're processing, any given day in the
set is going to be treated the same way, i.e. it gets a cell with the date
in it:
<xsl:template match="day">
<td><xsl:value-of select="@date"/></td>
</xsl:template>
The trick is to choose the right days to process. You were on the right
track by looking at every 7th day. When processing each of those days,
you want to go find the next 6 to complete the row.
Be careful about the use of mod and position(); the first node is at
position 1, and you want positions 1, 8, 15, etc., not 0, 7, 14 -- so it
would be position() mod 7 = 1.
<xsl:template match="month">
<xsl:for-each select="day[position() mod 7 = 1]">
<tr>
<xsl:apply-templates select=". | following-sibling::day[position() < 7]"/>
</tr>
</xsl;for-each>
</xsl:template>
Your relatively flat schema lends itself well to the use of
following-sibling. If it were not so flat, another option is to get the
following 6 day elements by their position in the set of all day elements:
<xsl:template match="month">
<xsl:variable name="currentmonth" select="."/>
<xsl:for-each select="day[position() mod 7 = 1]">
<xsl:variable name="pos" select="position()"/>
<tr>
<xsl:apply-templates
select="$currentmonth/day[position()
>= $pos and position() < $pos + 7]"/>
</tr>
</xsl;for-each>
</xsl:template>
However this would be wasteful in your case. One thing to note is that HTML tables need to have all cell spaces
accounted for. If there are fewer than 7 days in the last set to process,
you'll be left with some unfilled cells. So before that </tr> I would add
something like this to make one big last cell:
<xsl:if test="last() < position() + 6">
<td colspan="{position() + 6 - last()}"> </td>
</xsl:if>
or this plus the named template below:
<xsl:if test="last() < position() + 6">
<xsl:call-template name="make_empty_cells">
<xsl:with-param name="count" select="position() + 6 - last()"/>
</xsl:call-template>
</xsl:if>
<xsl:template name="make_empty_cells">
<xsl:param name="count"/>
<xsl:if test="$count">
<td> </td>
<xsl:call-template name="make_empty_cells">
<xsl:with-param name="count" select="$count - 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
Mike Kay offers The basic principle is to structure template rules according to the output
structure, not the input structure. You want a rule that generates a <week>
element containing seven <day> elements? Write it like this:
<xsl:template match="day[position() mod 7 = 0]"/>
<week>
<xsl:for-each select=". | following-sibling::day[position() < 7]">
<day><xsl:value-of select="@date"/></day>
</xsl:for-each>
</week>
</xsl:template>
<xsl:template match="day"/> <!-- ignore the other days -->
|
12. | Retriving next 10 elements |
| Jeni Tennison
> we are using Xml with Xsl for presentaion.In this our requirment is ,If
>we are getting 100 results,in the xml format we want to display 10 at a time
>without going to the database again.I think the xsl should be able to take
>all the 100 results and display 10 each time.please suggest with code if
>possible. Since you don't give any example XML to work with, I'm going to give a
general answer with a silly example that is:
<list>
<item>1</item>
<item>2</item>
...
</list>
Note that in this example all the items that you're interested in appear on
the same level: they're siblings. There is a more general solution where
this isn't the case, but this *is* the case in the vast majority of
situations where people want to group by position, as far as I can tell.
The design pattern for doing this has two stages:
1. select the first of the group
2. during the processing of the first of the group, select the rest of the
group
Selecting the first of the group involves selecting those nodes that have a
position in the set that, when 'mod'ed with the number of elements per
group, evaluates to 1, so generally:
$nodes[position() mod $group-size = 1]
or with my example:
item[position() mod 10 = 1]
When you've identified the first of a group, finding the rest of the group
is a matter of looking for the next n siblings, where n is the size of the
group minus 1 or, in other words, those that have a position in the set of
following siblings that is less than the size of the group. The group
consists of the node you've got (the first in the group) and the next n nodes:
. | following-sibling::*[position() < $group-size] or with my example:
. | following-sibling::item[position() < 10]
You can do the actual processing with either xsl:apply-templates or with
xsl:for-each. With xsl:apply-templates it looks like:
<xsl:template match="list">
<xsl:apply-templates select="item[position() mod 10 = 1]"
mode="group" />
</xsl:template>
<xsl:template match="item" mode="group">
<xsl:apply-templates
select=". | following-sibling::item[position() < 10]" />
</xsl:template>
With xsl:for-each it looks like:
<xsl:template match="list">
<xsl:for-each select="item[position() mod 10 = 1]">
<xsl:apply-templates
select=". | following-sibling::item[position() < 10]" />
</xsl:for-each>
</xsl:template>
Generally, I would keep the size of the groups that you wanted to generate
in a separate variable or, better, a global parameter, that would allow you
to vary it later on, either by changing the stylesheet at one place or by
passing a different value for the parameter into the stylesheet:
<xsl:param name="group-size" select="10" /> |
13. | Colouring table cells |
| David Carlisle
If there really is only a fixed range, you'll probably have an easier
time with a lookup table than hex arithmetic, something like <xsl:template match="parent">
<xxx bgcolor="{document('')/xsl:stylesheet/x:colors[position() = current()/@level]}>
...
where your stylesheet has
<xsl:stylesheet ...
xmlns:x="data:anything">
<x:colors>#FFFFFF</x:colors>
<x:colors>#FFFFBB</x:colors>
<x:colors>#FFFF77</x:colors>
...
with whatever 10 values you want.
On the other hand, if you specify the function that maps your level
range to your required colour gradient, it would be possible....
|
14. | Splitting up tables |
| Oliver Becker > I have the following structure
> <TABLE>
> <HEADERROW>
> <HEADER>a</HEADER>
> <HEADER>b</HEADER>
> <HEADER>c</HEADER>
> </HEADERROW>
> <DATAROWS>
> <DATAROW>
> <DATA>1</DATA> <DATA>1</DATA> <DATA>1</DATA>
> </DATAROW>
> <DATAROW>
> <DATA>2</DATA> <DATA>2</DATA> <DATA>2</DATA>
> </DATAROW>
... [snip]
> </DATAROWS>
> </TABLE>
>
> What I need is to generate three tables from the same XML each with its own
> table header.
>
> First table would (arbitrarily) have 5 consective rows (rows 1-5).
> Second table would have 2 consecutive rows (6-7).
> Third table would have 2 consective rows (8-9).
The trick is to select the rows you want to process with an XPath
expression using the context position of each row.
E.g. the first table should contain all the rows that have a position
greater or equal 1 and lower or equal 5: <xsl:apply-templates select="DATAROW[position() >= 1 and
position() <= 5]" /> Now you have to take care to create the table headers at the appropriate
position in your stylesheet: first skip them, but afterwards process them
explicitly.
I hope the following complete stylesheet shows what I shortly tried to
explain:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="TABLE">
<!-- skip HEADERROW -->
<xsl:apply-templates select="DATAROWS" />
</xsl:template>
<xsl:template match="DATAROWS">
Table 1:
<table>
<xsl:apply-templates select="preceding-sibling::HEADERROW" />
<xsl:apply-templates select="DATAROW[position() >= 1 and
position() <= 5]" />
</table>
Table 2:
<table>
<xsl:apply-templates select="preceding-sibling::HEADERROW" />
<xsl:apply-templates select="DATAROW[position() >= 6 and
position() <= 7]" />
</table>
Table 3:
<table>
<xsl:apply-templates select="preceding-sibling::HEADERROW" />
<xsl:apply-templates select="DATAROW[position() >= 8 and
position() <= 9]" />
</table>
</xsl:template>
<xsl:template match="HEADERROW | DATAROW">
<tr><xsl:apply-templates /></tr>
</xsl:template>
<xsl:template match="HEADER">
<th><xsl:value-of select="."/></th>
</xsl:template>
<xsl:template match="DATA">
<td><xsl:value-of select="."/></td>
</xsl:template>
</xsl:stylesheet>
|
15. | Alternate bg colors for table and address summaries |
| Jeni Tennison
>I want to have one summary return with a grey background and the other
>summary with a white background.
You want to have the two summaries in different
colours. The place where you set the colour of the summary is within
@STYLE attribute of the DIV element:
>
<DIV STYLE="background-color:#FFFFFF; color:white; padding:4px"> Now, for the first summary, you want a gray background:
STYLE="background-color:gray; color:white; padding:4px"
And for the second summary, you want a white background:
STYLE="background-color:white; color:white; padding:4px"
To change the value of an attribute dependent on a condition, the best
thing to do is to use xsl:attribute to create the attribute, and then
xsl:choose to determine what the content should be inside it:
<DIV>
<xsl:attribute name="STYLE">
<xsl:choose>
<xsl:when test="...">background-color:gray; </xsl:when>
<xsl:otherwise>background-color:white; </xsl:otherwise>
</xsl:choose>
<xsl:text>color:white; padding:4px</xsl:text>
</xsl:attribute>
...
</DIV>
The difficulty that it sounds like you were facing is what the 'test'
should be - how to determine whether the background colour should be gray
or white. At the point at which this test is made, you're within an
xsl:for-each; the current node is a Summary, and the current node list
contains all the Summary elements that are children of Address-Summaries
within the document. This means you can tell whether the current node (a
Summary) is the first Summary element in the list or not by checking its
position():
test="position() = 1" If you have lots of Summary elements and you want to alternate colours odd
and even, then you can check whether a Summary element is in an odd
position using mod:
test="position() mod 2 = 1"
So, try:
<DIV>
<xsl:attribute name="STYLE">
<xsl:choose>
<xsl:when test="position() mod 2 = 1">background-color:gray; </xsl:when>
<xsl:otherwise>background-color:white; </xsl:otherwise>
</xsl:choose>
<xsl:text>color:white; padding:4px</xsl:text>
</xsl:attribute>
...
</DIV>
|
16. | Balancing the number of cells in a table |
| Steve Muench
Here's a solution that does what I think you're trying
to achieve (generate an appropriate colspan="xxx" for
rows that have fewer than the max(cells) number of cells) Given your input:
<table>
<row>
<cell>...</cell>
</row>
<row>
<cell>...</cell>
<cell>...</cell>
<cell>...</cell>
</row>
<row>
<cell>...</cell>
<cell>...</cell>
</row>
</table>
the stylesheet below produces the output:
<html>
<body>
<table>
<tr>
<td colspan="3">...</td>
</tr>
<tr>
<td>...</td>
<td>...</td>
<td>...</td>
</tr>
<tr>
<td>...</td>
<td colspan="2">...</td>
</tr>
</table>
</body>
</html>
Here's the stylesheet:
<x:stylesheet
xmlns:x="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!-- Compute the max(count(cell)) for all <row> elements -->
<x:variable name="maxcells">
<x:for-each select="/table/row">
<x:sort data-type="number"
order="descending" select="count(cell)"/>
<x:if test="position()=1"><x:value-of
select="count(cell)"/></x:if>
</x:for-each>
</x:variable>
<x:template match="cell">
<td>
<!-- If we're processing the last cell and $maxcells is greater -->
<x:if test="position()=last() and $maxcells > position()">
<x:attribute name="colspan">
<x:value-of select="$maxcells - position() + 1"/>
</x:attribute>
</x:if>
<x:apply-templates/>
</td>
</x:template>
<x:template match="/">
<html><body>
<x:apply-templates/>
</body></html>
</x:template>
<x:template match="table">
<table>
<x:apply-templates select="row"/>
</table>
</x:template>
<x:template match="row">
<tr>
<x:apply-templates select="cell"/>
</tr>
</x:template>
</x:stylesheet>
|
17. | How to find the Maximum number of cells in a row |
| Mike Kay
> I need to compare the number of cells in each row element and
> get the count
> of cell elements in the row that has the most
there's no built in max and min functions; alternatives are: sort, and find the first/last | recursive template | extension functions saxon:max(), saxon:min() |
Doing (a), with <table> as current node: <xsl:variable name="maxcells">
<xsl:for-each select="row"><xsl:sort
select="count(cell)"
order="descending"/>
<xsl:if test="position()=1"><xsl:value-of
select="count(cell)"/></xsl:if>
</xsl:for-each>
</xsl:variable> |
18. | Table from ADO |
| James Newman
I have just been given the task at my company to look in XSLT and SQL
server. I had run into a brick wall when it came to producing a general
HTML table from the output of an ADO object (recordset.save XX,
adXMLPersist) because all the attributes are specified under a separate
node <s:Schema> but the actual data is in the form <row fieldName1="XX"
fieldName2="YY"/>.
The problem being that not all fields are passed back as attributes to the
row node but are omitted under certain circumstances.
I had an example working but the time to transform was a product of rows,
fields requested and fields actual present. I was
able to find a much neater (and quicker) solution. Anyway I'm sure there
are better solutions but I thought I'd send you my stylesheet just to say
thank you because it now handles any standard "select * from TABLE" SQL
statement. THE STYLESHEET
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema"
xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882">
<xsl:template match="/">
<HTML>
<BODY>
<h1>Collected <xsl:value-of
select="count(//rs:data/z:row)"/> records</h1>
<table rules="all">
<tr>
<td>Rec Num</td>
<xsl:apply-templates
select="xml/s:Schema/s:ElementType/s:AttributeType"/>
</tr>
<xsl:call-template name="rows">
<xsl:with-param name="cnum" select="1"/>
</xsl:call-template>
</table>
</BODY>
</HTML>
</xsl:template>
<xsl:template name="rows">
<xsl:param name="cnum"/>
<!-- row exists then carry on -->
<xsl:if test="xml/rs:data/z:row[$cnum]">
<tr>
<td><xsl:value-of select="$cnum"/></td>
<!-- go through all the attributes (i.e. fields) defined -->
<xsl:for-each
select="xml/s:Schema/s:ElementType/s:AttributeType">
<td>
<!-- set variable to be value of attribute we will search for -->
<xsl:variable name="myTest" select="@name"/>
<!-- insert value of X attribute, if doesn't exist get blank -->
<b><xsl:value-of
select="/xml/rs:data/z:row[$cnum]/@*[name() = $myTest]"/></b>
<!-- add blank to stop <td/> tag, bad in netscape apparently -->
<xsl:text> </xsl:text>
</td>
</xsl:for-each>
</tr>
<!-- do again with next record -->
<xsl:call-template name="rows">
<xsl:with-param name="cnum" select="$cnum + 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<!-- Insert headers -->
<xsl:template match="s:AttributeType">
<th><xsl:value-of select="@name"/></th>
</xsl:template>
</xsl:stylesheet>
THE DATA FROM ADO
<?xml-stylesheet type="text/xsl" href="sample.xsl"?>
<xml xmlns:s="uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882"
xmlns:dt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882"
xmlns:rs="urn:schemas-microsoft-com:rowset" xmlns:z="#RowsetSchema">
<s:Schema id="RowsetSchema">
<s:ElementType name="row" content="eltOnly">
<s:AttributeType name="field1"
rs:number="1" rs:nullable="true"
rs:writeunknown="true">
<s:datatype dt:type="string" rs:dbtype="str" dt:maxLength="20"/>
</s:AttributeType>
<s:AttributeType name="field2" rs:number="2"
rs:nullable="true"
rs:writeunknown="true">
<s:datatype dt:type="string" rs:dbtype="str" dt:maxLength="40"/>
</s:AttributeType>
<s:AttributeType name="field3" rs:number="3"
rs:nullable="true"
rs:writeunknown="true">
<s:datatype dt:type="string" rs:dbtype="str" dt:maxLength="20"/>
</s:AttributeType>
<s:extends type="rs:rowbase"/>
</s:ElementType>
</s:Schema>
<rs:data>
<z:row field1="XX" field2="YY"/>
</rs:data>
|
19. | Cals table stylesheet |
| Andrew Welch For anyone working with CALS tables, here is a stylesheet I wrote a while
back to display them. I think it covers all of the variations that CALS
allows (which is a lot).
It will produce a table 300px high, with a 'fixed header' so that the body
scrolls while the head is fixed.
It just requires that 't' 'b' 'l' and 'r' are defined in your CSS as 1px
solid black borders.
Im confident that it can take whatever gets thrown at it, but if anyone can
improve it (and repost :) or find a fault, please let me know.
(Ive just removed some things specific to my company, so if some of the html
structure looks slightly off, its probably because Ive just deleted some it)
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl"
version="1.0">
<xsl:param name="print" select="'no'"/>
<xsl:param name="printWidth" select="0"/>
<!-- tests for aircrew dm, we dont use scroll bars on aircrew dm's-->
<xsl:variable name="acrw" select="boolean(/dmodule/content/acrw)"/>
<xsl:variable name="colspecs-firstPass-rtf">
<xsl:for-each select="//table">
<table id="{generate-id()}">
<xsl:for-each select="tgroup">
<tgroup id="{generate-id()}">
<xsl:for-each select=".//colspec">
<col id="{generate-id()}" colname="{@colname}"
number="{count(preceding-sibling::colspec|.)}"
width="{translate(@colwidth,'inm*','')}" align="{@align}">
<xsl:attribute name="inHead">
<xsl:choose>
<xsl:when test="parent::thead">yes</xsl:when>
<xsl:otherwise>no</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
</col>
</xsl:for-each>
<xsl:for-each select="spanspec">
<spanspec id="{generate-id()}" spanname="{@spanname}"
namest="{@namest}" nameend="{@nameend}" align="{@align}"/>
</xsl:for-each>
</tgroup>
</xsl:for-each>
</table>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="colspecs-firstPass"
select="exsl:node-set($colspecs-firstPass-rtf)"/>
<xsl:variable name="colspecs-rtf">
<xsl:for-each select="$colspecs-firstPass">
<xsl:for-each select="table">
<table id="{@id}">
<xsl:for-each select="tgroup">
<tgroup id="{@id}">
<xsl:variable name="totalGroupWidth" select="sum(col[@inHead =
'no']/@width)"/>
<xsl:variable name="totalHeadWidth" select="sum(col[@inHead =
'yes']/@width)"/>
<xsl:for-each select="col">
<col id="{@id}" colname="{@colname}" number="{@number}"
align="{@align}" inHead="{@inHead}">
<xsl:attribute name="width">
<xsl:choose>
<xsl:when test="@inHead = 'no'">
<xsl:value-of select="@width div $totalGroupWidth *
100"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="@width div $totalHeadWidth *
100"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
</col>
</xsl:for-each>
<xsl:for-each select="spanspec">
<xsl:copy-of select="."/>
</xsl:for-each>
</tgroup>
</xsl:for-each>
</table>
</xsl:for-each>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="colspecs" select="exsl:node-set($colspecs-rtf)"/>
<xsl:variable name="spanspecs-rtf">
<xsl:for-each select="$colspecs">
<xsl:for-each select="table/tgroup">
<xsl:variable name="tgroupId" select="@id"/>
<xsl:for-each select="spanspec">
<col id="{@id}" spanname="{@spanname}">
<xsl:attribute name="colspan">
<xsl:value-of select="1 + preceding-sibling::col[@colname =
current()/@nameend]/@number - preceding-sibling::col[@colname =
current()/@namest]/@number"/>
</xsl:attribute>
<xsl:attribute name="width">
<xsl:call-template name="calculateWidth">
<xsl:with-param name="tgroupId" select="$tgroupId"/>
<xsl:with-param name="startCol"
select="preceding-sibling::col[@colname = current()/@namest]/@number"/>
<xsl:with-param name="endCol"
select="preceding-sibling::col[@colname = current()/@nameend]/@number"/>
</xsl:call-template>
</xsl:attribute>
<xsl:attribute name="align">
<xsl:choose>
<xsl:when test="string-length(@align)"><xsl:value-of
select="@align"/></xsl:when>
<xsl:otherwise><xsl:value-of
select="preceding-sibling::col[@colname =
current()/@nameend]/@align"/></xsl:otherwise>
</xsl:choose>
</xsl:attribute>
</col>
</xsl:for-each>
</xsl:for-each>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="spanspecs" select="exsl:node-set($spanspecs-rtf)"/>
<xsl:template name="calculateWidth">
<xsl:param name="tgroupId" select="0"/>
<xsl:param name="startCol" select="0"/>
<xsl:param name="endCol" select="0"/>
<xsl:param name="totalWidth" select="0"/>
<xsl:choose>
<xsl:when test="$startCol <= $endCol">
<xsl:call-template name="calculateWidth">
<xsl:with-param name="tgroupId" select="$tgroupId"/>
<xsl:with-param name="startCol" select="$startCol + 1"/>
<xsl:with-param name="endCol" select="$endCol"/>
<xsl:with-param name="totalWidth">
<xsl:for-each select="$colspecs">
<xsl:for-each select="table/tgroup[@id = $tgroupId]">
<xsl:value-of select="col[@number = $startCol]/@width +
$totalWidth"/>
</xsl:for-each>
</xsl:for-each>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$totalWidth"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="getBorders">
<xsl:param name="frame" select="''"/>
<xsl:choose>
<xsl:when test="$frame = 'ALL'">t b l r</xsl:when>
<xsl:when test="$frame = 'TOP'">t</xsl:when>
<xsl:when test="$frame = 'TOPBOT'">t b</xsl:when>
<xsl:when test="$frame = 'BOTTOM'">b</xsl:when>
<xsl:when test="$frame = 'SIDES'">l r</xsl:when>
<xsl:when test="$frame = 'NONE'">t b l r</xsl:when>
<xsl:otherwise>t b l r</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="table">
<xsl:variable name="tableFrame">
<xsl:call-template name="getBorders">
<xsl:with-param name="frame" select="@frame"/>
</xsl:call-template>
</xsl:variable>
<div>
<xsl:apply-templates>
<xsl:with-param name="tableFrame" select="$tableFrame"/>
</xsl:apply-templates>
</div>
</xsl:template>
<xsl:template match="tgroup">
<xsl:param name="tableFrame"/>
<xsl:variable name="tgroupId" select="generate-id()"/>
<!--When the table is dead on 300 high IE can get caught in a dependency
loop if we set the 'else'
value to 'scrollHeight', so instead we use the dummy string 'nause' to
do nothing, because it
was a right nause finding and fixing the bug... -->
<div class="{$tableFrame}">
<xsl:attribute name="style">
<xsl:text>width:100%;</xsl:text>
<xsl:if
test="$print = 'no'">overflow:auto;height:expression(scrollHeight >= 300 ? 300 :
'nause');</xsl:if>
</xsl:attribute>
<xsl:if test="$print = 'no' and .//thead">
<xsl:attribute
name="onscroll">nextSibling.scrollLeft=scrollLeft</xsl:attribute>
</xsl:if>
<table cellpadding="3" cellspacing="0" style="width:100%">
<colgroup>
<xsl:for-each select="$colspecs">
<xsl:for-each select="table/tgroup[@id = $tgroupId]/col[@inHead =
'no']">
<col width="{@width}%"/>
</xsl:for-each>
</xsl:for-each>
</colgroup>
<xsl:if test="thead">
<thead>
<xsl:for-each select="thead/row">
<tr>
<xsl:apply-templates select="entry">
<xsl:with-param name="inHead" select="'true'"/>
<xsl:with-param name="fakey" select="'true'"/>
<xsl:with-param name="tgroupId" select="$tgroupId"/>
</xsl:apply-templates>
</tr>
</xsl:for-each>
</thead>
</xsl:if>
<tbody>
<xsl:apply-templates select="tbody">
<xsl:with-param name="tgroupId" select="$tgroupId"/>
</xsl:apply-templates>
</tbody>
</table>
</div>
<xsl:if test="$print = 'no' and .//thead">
<div class="t b {$tableFrame}">
<xsl:attribute name="style">
<xsl:text>position:relative;
background-color:expression(document.body.currentStyle.backgroundColor);
overflow:hidden;
top:expression(-previousSibling.clientHeight-((previousSibling.scrollWidth>p
reviousSibling.clientWidth)?18:2));</xsl:text>
<xsl:choose>
<!--take into account border widths-->
<xsl:when test="contains($tableFrame,'l
r')">width:expression(previousSibling.clientWidth + 2)</xsl:when>
<xsl:otherwise>width:expression(previousSibling.clientWidth)</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<div style="width:expression(parentNode.previousSibling.scrollWidth)">
<table cellSpacing="0" cellpadding="3" style="width:100%">
<xsl:variable name="genIdh" select="generate-id()"/>
<colgroup>
<xsl:for-each select="$colspecs">
<xsl:choose>
<xsl:when test="table/tgroup[@id =
$tgroupId]/col[@inHead = 'yes']">
<xsl:for-each
select="table/tgroup[@id = $tgroupId]/col[@inHead = 'yes']">
<col width="{@width}%"/>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:for-each
select="table/tgroup[@id = $tgroupId]/col[@inHead= 'no']">
<col width="{@width}%"/>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</colgroup>
<thead>
<xsl:for-each select="thead/row">
<tr>
<xsl:apply-templates select="entry">
<xsl:with-param name="inHead" select="'true'"/>
<xsl:with-param name="tgroupId" select="$tgroupId"/>
</xsl:apply-templates>
</tr>
</xsl:for-each>
</thead>
</table>
</div>
</div>
</xsl:if>
</xsl:template>
<xsl:template match="tbody">
<xsl:param name="tgroupId" select="''"/>
<xsl:apply-templates>
<xsl:with-param name="tgroupId" select="$tgroupId"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="row|caprow">
<xsl:param name="tgroupId" select="''"/>
<xsl:param name="inHead" select="'false'"/>
<xsl:if test="@id"><a><xsl:attribute name="name"><xsl:value-of
select="@id"/></xsl:attribute></a></xsl:if>
<tr>
<xsl:apply-templates>
<xsl:with-param name="tgroupId" select="$tgroupId"/>
<xsl:with-param name="inHead" select="$inHead"/>
</xsl:apply-templates>
</tr>
</xsl:template>
<xsl:template match="entry|capentry">
<xsl:param name="inHead" select="'false'"/>
<xsl:param name="fakey" select="'false'"/>
<xsl:param name="tgroupId" select="'no tgroup id generated'"/>
<xsl:if test="@id"><a><xsl:attribute
name="name"><xsl:value-of
select="@id"/></xsl:attribute></a></xsl:if>
<td>
<xsl:if test="@morerows">
<xsl:attribute name="rowspan">
<xsl:value-of select="@morerows + 1"/>
</xsl:attribute>
</xsl:if>
<xsl:choose>
<xsl:when test="@spanname">
<xsl:variable name="spanname" select="@spanname"/>
<xsl:for-each select="$spanspecs">
<xsl:attribute name="colspan">
<xsl:value-of select="col[@spanname = $spanname]/@colspan"/>
</xsl:attribute>
<xsl:if test="$inHead = 'false'">
<xsl:attribute name="width">
<xsl:value-of
select="col[@spanname = $spanname]/@width"/>%<xsl:text/>
</xsl:attribute>
</xsl:if>
<xsl:attribute name="align">
<xsl:value-of select="col[@spanname = $spanname]/@align"/>
</xsl:attribute>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:if test="@namest">
<xsl:variable name="nameStart" select="@namest"/>
<xsl:variable name="nameEnd" select="@nameend"/>
<xsl:variable name="nameStartId"
select="generate-id(ancestor::tgroup/colspec[@colname = $nameStart])"/>
<xsl:variable name="nameEndId"
select="generate-id(ancestor::tgroup/colspec[@colname = $nameEnd])"/>
<xsl:attribute name="colspan">
<xsl:for-each select="$colspecs">
<xsl:value-of select="1 + table/tgroup[@id = $tgroupId]/col
[@id = $nameEndId]/@number - table/tgroup
[@id = $tgroupId]/col[@id = $nameStartId]/@number"/>
</xsl:for-each>
</xsl:attribute>
<xsl:if test="$inHead = 'false'">
<xsl:attribute name="width">
<xsl:call-template name="calculateWidth">
<xsl:with-param name="tgroupId" select="$tgroupId"/>
<xsl:with-param name="startCol">
<xsl:for-each select="$colspecs">
<xsl:value-of select="table[@id =
$tgroupId]/col[@id = $nameStartId]/@number"/>
</xsl:for-each>
</xsl:with-param>
<xsl:with-param name="endCol">
<xsl:for-each select="$colspecs">
<xsl:value-of select="table[@id =
$tgroupId]/col[@id = $nameEndId]/@number"/>
</xsl:for-each>
</xsl:with-param>
</xsl:call-template>
<xsl:text>%</xsl:text>
</xsl:attribute>
</xsl:if>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
<xsl:variable name="colname" select="@colname"/>
<xsl:for-each select="$colspecs">
<xsl:if test="table/tgroup[@id = $tgroupId]/col[@colname =
$colname]/@align">
<xsl:attribute name="align">
<xsl:value-of
select="table/tgroup[@id = $tgroupId]/col[@colname = $colname]/@align"/>
</xsl:attribute>
</xsl:if>
</xsl:for-each>
<xsl:if test="@align">
<xsl:attribute name="align">
<xsl:value-of select="@align"/>
</xsl:attribute>
</xsl:if>
<xsl:attribute name="valign">
<xsl:choose>
<xsl:when test="@valign"><xsl:value-of select="@valign"/></xsl:when>
<xsl:otherwise>top</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:attribute name="class">
<xsl:if test="@rowsep='1' or (parent::*/@rowsep='1' and
$inHead = 'false')">b </xsl:if>
<xsl:if test="@colsep='1' and following-sibling::entry">r </xsl:if>
<xsl:if test="$inHead = 'true'">bold </xsl:if>
<xsl:if test="$inHead = 'true' and $print != 'no'">b </xsl:if>
</xsl:attribute>
<xsl:if test="$fakey = 'true'">
<xsl:attribute name="searchChar">`</xsl:attribute>
</xsl:if>
<xsl:apply-templates/>
<span>
<xsl:if test="$fakey = 'true'">
<xsl:attribute name="searchChar">£</xsl:attribute>
</xsl:if>
<xsl:if test=". = ''"><xsl:text>##160;</xsl:text></xsl:if>
</span>
</td>
</xsl:template>
<xsl:template match="capgrp">
<xsl:variable name="widths-rtf">
<xsl:for-each select="colspec">
<col>
<xsl:attribute name="width"><xsl:value-of
select="translate(@colwidth,'inm*','')"/></xsl:attribute>
</col>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="widths" select="exsl:node-set($widths-rtf)"/>
<p>
<xsl:attribute name="align"><xsl:value-of
select="@align"/></xsl:attribute>
<table style="width:100%">
<colgroup>
<xsl:for-each select="$widths">
<xsl:variable name="total" select="sum(col/@width)"/>
<xsl:for-each select="col">
<col>
<xsl:attribute name="width"><xsl:value-of
select="(@width div $total) * 100"/>%</xsl:attribute>
</col>
</xsl:for-each>
</xsl:for-each>
</colgroup>
<xsl:apply-templates/>
</table>
</p>
</xsl:template>
<xsl:template name="colspanWidth">
<xsl:param name="startCol" select="0"/>
<xsl:param name="endCol" select="0"/>
<xsl:param name="totalWidth" select="0"/>
<xsl:param name="tgroupId" select="0"/>
<xsl:choose>
<xsl:when test="$startCol != $endCol">
<xsl:call-template name="colspanWidth">
<xsl:with-param name="startCol">
<xsl:for-each select="$colspecs">
<xsl:value-of select="table/tgroup[@id =
$tgroupId]/col[preceding-sibling::col[1][@colname = $startCol]]/@colname"/>
</xsl:for-each>
</xsl:with-param>
<xsl:with-param name="endCol" select="$endCol"/>
<xsl:with-param name="tgroupId" select="$tgroupId"/>
<xsl:with-param name="totalWidth">
<xsl:for-each select="$colspecs">
<xsl:value-of select="table/tgroup[@id =
$tgroupId]/col[@colname= $startCol]/@width + $totalWidth"/>
</xsl:for-each>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$totalWidth"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
|
20. | Replace repetition in cell values by span values. |
| David Carlisle
> Given
<column_header>
<header rowid="0">
<data columnid="01" name="2003"/>
<data columnid="11" name="2003"/>
<data columnid="21" name="2003"/>
<data columnid="31" name="2003"/>
<data columnid="41" name="2003"/>
<data columnid="51" name="2003"/>
<data columnid="61" name="2003"/>
<data columnid="71" name="2003"/>
</header>
<header rowid="1">
<data columnid="01" name="Feb"/>
<data columnid="11" name="Feb"/>
<data columnid="21" name="Feb"/>
<data columnid="31" name="Feb"/>
<data columnid="41" name="Y-T-D(Feb)"/>
<data columnid="51" name="Y-T-D(Feb)"/>
<data columnid="61" name="Y-T-D(Feb)"/>
<data columnid="71" name="Y-T-D(Feb)"/>
</header>
<header rowid="2">
<data columnid="01" name="Actual"/>
<data columnid="11" name="Budget"/>
<data columnid="21" name="Fav/(Unfav) $"/>
<data columnid="31" name="Fav/(Unfav) %"/>
<data columnid="41" name="Actual"/>
<data columnid="51" name="Budget"/>
<data columnid="61" name="Fav/(Unfav) $"/>
<data columnid="71" name="Fav/(Unfav) %"/>
</header>
</column_header>
> I'm trying to get the results to look like this:
<table border="1" cellpadding="0" cellspacing="0"
bordercolor="#111111">
<tr>
<td colspan="8">2003</td>
</tr>
<tr>
<td colspan="4">Feb</td>
<td colspan="4">Y-T-D(Feb)</td>
</tr>
> Basically I'm doing grouping, which I have seen numerous postings for,
> but couldn't find anything that dealt with actual colspans and the
> such. My first idea was to use the "following-sibling::data[1]/@name
> = @name" test to keep track of how many columns to span. If the above
> test was false, I would draw the table cell using the current colspan
> counter. Otherwise, if the above test was true, then I would skip
> drawing the table cell and increment the colspan counter.
Your mind has been corrupted by exposure to limited programming languages
designed around the capabilities of a machine of the previous century,
If you were describing what colspan means to a person, you wouldn't (I
hope) say
you start off with an imaginary variable, initialized to zero, and then move
from one column to the next, and if the value is the same, increment this
variable by one, until you get to a column that is not followed by a column
with an identical value, whereupon you use the value as the colspan
attribute.
You're more likely to say, if you have a group of columns that use the same text, group them in one
cell and use colspan to give the size of the group of columns.
Program xslt as you would talk to a human.
You just want to use grouping (as you said) and then use count() to return
the size of the current group.
I'd use muenchian grouping for this (See jeni's site) basically index each cell by its rowid and value: <xsl:key name="x" match="data" use="concat(../@rowid,':',name())"/>
then <xsl:for-each
select="data[genarate-id(.)=generate-id(key('x',concat(../@rowid,':',name())
[1])]">
<!-- this is first of each group -->
<data>
<xsl:if test="key('x',concat(../@rowid,':',name()))[2]">
<xsl:attribute name="colspan"><xsl:value-of
select="count(key('x',concat(../@rowid,':',name())))"/>
</xsl:attribute>
|
21. | Normalize / Simplify HTML-Tables with row-span / col-span |
| Andrew Welch + Michael Müller-Hillebrand Out of a slightly different requirement Andrew Welch sent a
very good XSL stylesheet which handles "normalization" of a
table using colspan and rowspan attributes into a table
where each row has the same number of cells.
I just put in some finishing touches, comments, and removed
code which was related to the original requirement.
Imagine the following table (using table, tbody, tr, and td
elements width colspan and rowpsan attributes):
+-----------+-----------+
| a | b |
| +-----+-----+
| | c | d |
+-----------+-----+ |
| e | |
+-----+-----+-----+ |
| f | g | h | |
+-----+-----+-----+-----+
The script below changes that table to this format:
+-----+-----+-----+-----+
| a | a | b | b |
+-----+-----+-----+-----+
| a | a | c | d |
+-----+-----+-----+-----+
| e | e | e | d |
+-----+-----+-----+-----+
| f | g | h | d |
+-----+-----+-----+-----+
Thanks to Andrew for that algorithm!
<!--
TABLE NORMALIZATION
Heres a potential solution...
It works by first creating a variable with the colspans
normalized, which is easy enough. It then normalizes the
rowspans by iterating over the variable a row at time,
using two nodesets. One is the current row, one is the
previous row ($rowBlock). Where the previous row has a <td>
with a rowspan > 1, it copies it to a result variable with
the rowspan decremented. Where the previous row just has a
normal <td>, it copies the correct <td> from the current
row to the result variable. It maintains a pair of
pointers to ensure the correct <td>'s are being compared.
The 'result variable' is then used as the previous row in
the next iteration of the <tr> processing. This is based
on the fact that the first row of the table is always
complete - it contains the correct number of columns.
-->
<!--
2004-03-23: Some finishing touches by Michael Müller-Hillebrand
-->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" indent="yes"
omit-xml-declaration="yes"
encoding="UTF-8" />
<!-- FIRST: REMOVE COLSPANS FROM INPUT TABLE -->
<xsl:variable name="table_with_no_colspans-rtf">
<xsl:apply-templates mode="colspan"/>
</xsl:variable>
<xsl:variable name="table_with_no_colspans"
select="exsl:node-set($table_with_no_colspans-rtf)"/>
<!-- SECOND: REMOVE ROWSPANS FROM INPUT TABLE -->
<xsl:variable name="table_with_no_rowspan-rtf">
<xsl:for-each select="$table_with_no_colspans">
<xsl:apply-templates mode="rowspan"/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="table_with_no_rowspan"
select="exsl:node-set($table_with_no_rowspan-rtf)"/>
<!-- ROOT TEMPLATE -->
<xsl:template match="/">
<NOCOLSPAN><xsl:copy-of
select="$table_with_no_colspans"/></NOCOLSPAN>
<NOROWSPAN><xsl:copy-of
select="$table_with_no_rowspan"/></NOROWSPAN>
<!-- THIRD: REMOVE REMAINING ROWSPAN ATTRIBUTES FROM INPUT TABLE -->
<FINAL>
<xsl:for-each select="$table_with_no_rowspan">
<xsl:apply-templates mode="final"/>
</xsl:for-each>
</FINAL>
</xsl:template>
<!-- TEMPLATES USED TO REMOVE COLSPAN -->
<xsl:template match="@*|*" mode="colspan">
<xsl:copy>
<xsl:apply-templates select="@*|*" mode="colspan"/>
</xsl:copy>
</xsl:template>
<xsl:template match="td" mode="colspan">
<xsl:choose>
<xsl:when test="@colspan">
<td>
<xsl:copy-of
select="@*[not(contains('colspanwidth', name()))]"/>
<xsl:copy-of select="node()"/>
</td>
<xsl:call-template name="give_me_tds">
<xsl:with-param name="number-of-tds" select="@colspan - 1"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="give_me_tds">
<xsl:param name="number-of-tds" select="'1'"/>
<xsl:if test="$number-of-tds > 0">
<td>
<xsl:copy-of select="@*[not(contains('colspanwidth',
name()))]"/>
<xsl:copy-of select="node()"/>
</td>
<xsl:call-template name="give_me_tds">
<xsl:with-param name="number-of-tds"
select="$number-of-tds - 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<!-- TEMPLATES USED TO REMOVE ROWSPAN -->
<xsl:template match="@*|*" mode="rowspan">
<xsl:copy>
<xsl:apply-templates select="@*|*" mode="rowspan" />
</xsl:copy>
</xsl:template>
<xsl:template match="tbody" mode="rowspan">
<tbody>
<!-- first row is always okay -->
<xsl:copy-of select="tr[1]" />
<xsl:apply-templates select="tr[2]" mode="rowspan">
<xsl:with-param name="rowBlock" select="tr[1]" />
</xsl:apply-templates>
</tbody>
</xsl:template>
<xsl:template match="tr" mode="rowspan">
<xsl:param name="rowBlock" select="/.." /><!-- "/.." =
empty node-set -->
<xsl:variable name="current" select="." />
<xsl:variable name="rowBlockVar">
<xsl:call-template name="makeRow">
<xsl:with-param name="rowBlock" select="$rowBlock" />
<xsl:with-param name="current" select="$current" />
</xsl:call-template>
</xsl:variable>
<tr>
<xsl:copy-of select="$current/@*" />
<xsl:copy-of select="$rowBlockVar" />
</tr>
<xsl:apply-templates select="following-sibling::tr[1]"
mode="rowspan">
<xsl:with-param name="rowBlock"
select="exsl:node-set($rowBlockVar)" />
</xsl:apply-templates>
</xsl:template>
<xsl:template name="makeRow">
<xsl:param name="rowBlock" />
<xsl:param name="current" />
<xsl:param name="rowBlockPointer" select="1" />
<xsl:param name="currentPointer" select="1" />
<xsl:if test="$rowBlock/td[$rowBlockPointer]"><!-- any
cell in prev row? -->
<xsl:variable name="rbRowspan">
<xsl:for-each select="$rowBlock">
<xsl:choose>
<xsl:when test="td[$rowBlockPointer]/@rowspan">
<xsl:value-of select="td[$rowBlockPointer]/@rowspan"/>
</xsl:when>
<xsl:otherwise>1</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:variable>
<xsl:choose>
<xsl:when test="$rbRowspan > 1 or
not($current/td[$currentPointer])">
<td>
<xsl:copy-of
select="$rowBlock/td[$rowBlockPointer]/@*[not(contains('rows
pan', name()))]"/>
<xsl:if test="$rbRowspan > 2">
<xsl:attribute name="rowspan"><xsl:value-of
select="$rbRowspan - 1" /></xsl:attribute>
</xsl:if>
<xsl:copy-of
select="$rowBlock/td[$rowBlockPointer]/node()"/>
</td>
<xsl:call-template name="makeRow">
<xsl:with-param name="rowBlock" select="$rowBlock"/>
<xsl:with-param name="current" select="$current"/>
<xsl:with-param name="rowBlockPointer"
select="$rowBlockPointer + 1"/>
<xsl:with-param name="currentPointer"
select="$currentPointer"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:for-each select="$current">
<xsl:copy-of select="td[$currentPointer]"/>
<xsl:call-template name="makeRow">
<xsl:with-param name="rowBlock" select="$rowBlock"/>
<xsl:with-param name="current" select="$current"/>
<xsl:with-param name="rowBlockPointer"
select="$rowBlockPointer + 1"/>
<xsl:with-param name="currentPointer"
select="$currentPointer + 1"/>
</xsl:call-template>
</xsl:for-each>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
<!-- TEMPLATES FOR FINAL OUTPUT -->
<xsl:template match="@*|*" mode="final">
<xsl:copy>
<xsl:apply-templates select="@*|*" mode="final" />
</xsl:copy>
</xsl:template>
<xsl:template match="td" mode="final">
<xsl:choose>
<xsl:when test="@rowspan">
<td>
<xsl:copy-of select="@*[not(contains('rowspan', name()))]"/>
<xsl:copy-of select="node()"/>
</td>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
|
22. | How to split a large cals table |
| David Carlisle
Some of the tables I need to process are fairly large (200+ columns).
The publishing software I use will not render tables with this many
columns (unsurprisingly). I need to break these large tables into much
smaller ones, say 10 columns. Each table would repeat column 1 (row
titles).
Input
<table id="1">
<tgroup cols="239">
<colspec colname="col1" colnum="1"
colwidth="*"/>
...
<colspec colname="col239" colnum="3"
colwidth="*"/>
<tbody>
<row id="t1-1">
<entry colname="col1"
id="1">Statement</entry>
...
<entry colname="col239"
id="239"/>
</row>
...
</tbody>
</tgroup>
</table>
Desired output
OUTPUT: The colspecs are sequential and I have added new attributes
(oldcolspec & olcolname) to show the original column number)
---------
<table id="1">
<tgroup cols="11">
<colspec colname="col1" colnum="1"
oldcolspec="1" colwidth="*"/>
...
<colspec colname="col10" colnum="10"
oldcolspec="10" colwidth="*"/>
<tbody>
<row id="t1-1">
<entry colname="col1"
oldcolname="1" id="1">Statement</entry>
...
<entry colname="col10"
oldcolname="10" id="10"/>
</row>
</tbody>
</tgroup>
</table>
<table id="2">
<tgroup cols="11">
<colspec colname="col1" colnum="1"
oldcolspec="11" colwidth="*"/>
...
<colspec colname="col10" colnum="10"
oldcolspec="20" colwidth="*"/>
<tbody>
<row id="t1-1">
<entry colname="col1"
oldcolname="11" id="1">Statement</entry>
...
<entry colname="col10"
oldcolname="20" id="10"/>
</row>
</tbody>
</tgroup>
</table>
...
<table id="23">
<tgroup cols="10">
<colspec colname="col1" colnum="1"
oldcolspec="231" colwidth="*"/>
...
<colspec colname="col10" colnum="10"
oldcolspec="239" colwidth="*"/>
<tbody>
<row id="t1-1">
<entry colname="col1"
oldcolname="231" id="1">Statement</entry>
...
<entry colname="col10"
oldcolname="239" id="10"/>
</row>
</tbody>
</tgroup>
</table>
Can you promise not to have any colspan elements? (especially
overlapping ones) ?
If there are no colspans at all it's not too hard, if there are some
spanning entries but none spanning the vertical border on which you are
splitting it's doable but more tedious, if there are entries spanning
your splits then you have to start splitting up individual entries which
can be arbitrarily complicated, depending on what's in there.
An entry that spans the entire table and contains a reference to a
single image makes splitting the table interesting....
This agreed, DC produced:
<xsl:template match="table">
<xsl:for-each select="tgroup/colspec[position()!=1][position() mod 9 = 1]">
<xsl:variable name="start" select="(position()-1)*9+2"/>
<table id="t{position()}">
<tgroup>
<colspec colname="col1" colwidth="*"/>
<xsl:for-each select="(.|following-sibling::colspec)[position() <10]">
<colspec colname="col{position()+1}" colwidth="*"/>
<tbody>
<xsl:for-each select="../row">
<row>
<xsl:copy-of select="@*"/>
<xsl:for-each select="entry[position()=1 or (position()>=$start)][position()<=10]">
<entry colname="col{position()}">
<xsl:apply-templates/>
</entry>
</xsl:for-each>
</row>
</xsl:for-each>
</tbody>
</xsl:for-each>
</tgroup>
</table>
</xsl:for-each>
</xsl:template> |