Re-grouping nodes in variable containing sequence of nodes
I am trying to create a two-column print-able HTML phone list from an XML file of organizational groups containing associated personnel
<group print_order=1>
<group_name><![CDATA[Human Resources]]></group_name>
<person>...(name, address, phone data)...</person>
<person> ... </person>
<person> ... </person>
</group>
<group print_order=2>
<group_name><![CDATA[Operations]]></group_name>
<person> ... </person>
<person> ... </person>
<person> ... </person>
<person> ... </person>
</group>
<group print_order=3>
<group_name><![CDATA[IT Services]]></group_name>
etc.
I'm trying to get to the following html output <table>
<tr>
<td>
<div class=group>Human Resources
<person>
<span class=name> lastname, firstname </span>
<span class=addr> room# building </span>
<span class=phone> 555-5555 </span>
</person>
(repeated for each person in group)
</div>
<div class=group>Operations
<开发者_StackOverflow社区;person>
<span class=name> lastname, firstname </span>
<span class=addr> room# building </span>
<span class=phone> 555-5555 </span>
</person>
</div>
</td>
<td>
<div class=group>IT Services
<person>
<span class=name> lastname, firstname </span>
<span class=addr> room# building </span>
<span class=phone> 555-5555 </span>
</person>
(repeated for each person in group)
</div>
etc.
</td>
</tr>
</table>
<p class=page_break>
<table> (next table on next page)
<tr>
<td> (next set of groups with people)
</td>
<td> ( next set of groups with people )
</td>
</tr>
</table>
(Repeat tables as needed until all groups output)
The problem is that I want the number of lines in a table cell to be less than or equal to the number lines on a printed page, and I don't want groups split across table elements. I've tried the xsl:for-each-group instruction, but I can't figure out what the group-by or group-adjacent code should look like. I've also tried the following horrendous test:
<xsl:if test="sum($nodes/group[following-sibling::group[preceding-sibling::group[1] is current()]]/@line_count) mod 40 > sum($nodes/group[following-sibling::group[preceding-sibling::group[2] is current()]]/@line_count) mod 40 ">
in a for-each
loop, where nodes is a variable containing the sequence of groups, line_count is the number lines per group, and 40 is the # of lines per page. I'm using HTML tables to maximize browser support.
Is there a better way to do this? Using CSS maybe? Larry Wall, the author of the Perl language, said that in a programming language, easy things should be easy, and hard things should be possible. Is what I'm after possible in XSLT/XPath? Thanks to anyone willing to wade through all this.
I assume that each person takes a single line, and that no single group is too large to fit on one page. The following stylesheet paginates by recursively collecting groups into a variable ($currentColumn
) as long as the total number of persons is lower than the desired line count. Columns are collected into another variable ($columns
) as long as the total number of columns is lower than the desired column count. Once the column limit is reached, a page is output, and then the recursion continues with the next page.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="http://www.w3.org/2005/xpath-functions">
<xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:param name="lineCount" select="25"/>
<xsl:param name="columnCount" select="2"/>
<xsl:template match="/">
<html>
<head>
<title></title>
</head>
<body>
<xsl:call-template name="paginate">
<xsl:with-param name="groups" select="/groups/group"/>
</xsl:call-template>
</body>
</html>
</xsl:template>
<xsl:template name="paginate">
<xsl:param name="groups"/>
<xsl:param name="currentColumn" select="/.."/>
<xsl:param name="columns" select="/.."/>
<xsl:choose>
<xsl:when test="not($groups)">
<!-- End of input. Print current page and stop recursion. -->
<xsl:variable name="column">
<column><xsl:sequence select="$currentColumn"/></column>
</xsl:variable>
<xsl:call-template name="print-page">
<xsl:with-param name="columns" select="$columns,$column"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="count($currentColumn//person) + count($groups[1]/person) le $lineCount">
<xsl:call-template name="paginate">
<xsl:with-param name="currentColumn" select="$currentColumn,$groups[1]"/>
<xsl:with-param name="columns" select="$columns"/>
<xsl:with-param name="groups" select="$groups[position() gt 1]"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="count($groups[1]/person) gt $lineCount">
<xsl:message terminate="yes">Too many persons in a group!</xsl:message>
</xsl:when>
<xsl:when test="count($columns) + 1 lt $columnCount">
<!-- Start a new column -->
<xsl:variable name="column">
<column><xsl:sequence select="$currentColumn"/></column>
</xsl:variable>
<xsl:call-template name="paginate">
<xsl:with-param name="columns" select="$columns,$column"/>
<xsl:with-param name="groups" select="$groups"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<!-- All columns full. Print current page and page break, then advance to next page. -->
<xsl:variable name="column">
<column><xsl:sequence select="$currentColumn"/></column>
</xsl:variable>
<xsl:call-template name="print-page">
<xsl:with-param name="columns" select="$columns,$column"/>
</xsl:call-template>
<p class="page_break"/>
<xsl:call-template name="paginate">
<xsl:with-param name="groups" select="$groups"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="print-page">
<xsl:param name="columns"/>
<table>
<tr>
<xsl:for-each select="$columns">
<td>
<xsl:apply-templates select="column/group"/>
</td>
</xsl:for-each>
</tr>
</table>
</xsl:template>
<xsl:template match="group">
<div class="group">
<xsl:value-of select="group_name"/>
<xsl:for-each select="person">
<person>
<span class="name"><xsl:value-of select="name"/></span>
<span class="addr"><xsl:value-of select="addr"/></span>
<span class="phone"><xsl:value-of select="phone"/></span>
</person>
</xsl:for-each>
</div>
</xsl:template>
</xsl:stylesheet>
精彩评论