Introduction
http://www.codeproject.com/KB/XML/groupxml.aspx
用XML+XSLT实现打印报表话,这篇文章写得不错,可以参考一下!
Processing a list of XML elements using XSLT is fairly simple if you want to process each element. But what if you want to group the XML elements, to show a summary? Consider the following XML:
<?xml version="1.0" ?> <Employees> <Employee> <TeamID>1</TeamID> <TeamName>Sales</TeamName> <TaskID>1</TaskID> <Hours>5</Hours> <EmployeeID>1</EmployeeID> <Name>Bob</Name> <Surname>Shibob</Surname> </Employee> <Employee> <TeamID>1</TeamID> <TeamName>Sales</TeamName> <TaskID>2</TaskID> <Hours>4</Hours> <EmployeeID>1</EmployeeID> <Name>Bob</Name> <Surname>Shibob</Surname> </Employee> <Employee> <TeamID>1</TeamID> <TeamName>Sales</TeamName> <TaskID>4</TaskID> <Hours>7</Hours> <EmployeeID>2</EmployeeID> <Name>Sara</Name> <Surname>Lee</Surname> </Employee> <Employee> <TeamID>2</TeamID> <TeamName>Finance</TeamName> <TaskID>5</TaskID> <Hours>2</Hours> <EmployeeID>3</EmployeeID> <Name>John</Name> <Surname>Smith</Surname> </Employee> <Employee> <TeamID>2</TeamID> <TeamName>Finance</TeamName> <TaskID>3</TaskID> <Hours>4</Hours> <EmployeeID>4</EmployeeID> <Name>Penny</Name> <Surname>Wise</Surname> </Employee> <Employee> <TeamID>2</TeamID> <TeamName>Finance</TeamName> <TaskID>5</TaskID> <Hours>3</Hours> <EmployeeID>4</EmployeeID> <Name>Penny</Name> <Surname>Wise</Surname> </Employee> </Employees>
Suppose that you need to show a summary of Employee hours, grouped by Team. Something like this:
The unwieldy approach
One way to do this is to loop through the list of <Employee>
elements, and only show a row whenever the EmployeeID
changes. While this would work, this approach is unwieldy and inefficient, because for each <Employee>
being processed, you would be required to keep track of the IDs of the previous <Employee>
element. This is not a pretty sight.
The efficient approach
A cleaner, more efficient way to do this is to build a list of unique keys, then use these keys to group the results. (This is called the Muenchian Method.)
First, you must define the keys required to group the <Employee>
elements. You will need one for the TeamID
, and one for the EmployeeID
.
<xsl:keyname
="keyTeamID" match="Employee"use
="TeamID" /> <xsl:keyname
="keyEmployeeID" match="Employee"use
="EmployeeID" />
Select the first element of each group of elements for each unique TeamID
.
<xsl:for-each select="//Employee[generate-id(.) = generate-id(key('keyTeamID', TeamID)[1]
)]">
Get all the <Employee>
elements that belong to that Team
, into a variable.
<!-- Save the ID of the Team to a variable -->
<xsl:variable name="lngTeamID"><xsl:value-of select="TeamID" /></xsl:variable>
<!-- Select all the Employees belonging to the Team -->
<xsl:variable name="lstEmployee" select="//Employee[TeamID=$lngTeamID]
" />
The <Employee>
elements in this list must now be grouped by EmployeeID
. This is similar to grouping by TeamID
, except that in this case you only need to select elements in the list contained in the variable; you do not need to select elements from the entire result set.
<xsl:for-each select="$lstEmployee
[generate-id(.) = generate-id(key('keyEmployeeID', EmployeeID
)[1])]">
It is now fairly simple to show the total Hours
for each Employee
.
<xsl:value-of select="sum($lstEmployee[EmployeeID=$lngEmployeeID]/Hours
)" />
The full source
This is the entire XSLT used to render the table in the image:
<?xml version="1.0" ?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- Define keys used to group elements --><xsl:key name="keyTeamID" match="Employee" use="TeamID" /> <xsl:key name="keyEmployeeID" match="Employee" use="EmployeeID" />
<xsl:template match="/"> <html> <head> <title>Employee Hours By Team</title> <link type="text/css" rel="stylesheet" href="groupxml.css" /> </head> <body> <h3>Employee Hours By Team</h3> <table> <!-- Process each Team --><xsl:for-each select="//Employee[generate-id(.) = generate-id(key('keyTeamID', TeamID)[1])]">
<xsl:variable name="lngTeamID"><xsl:value-of select="TeamID" /></xsl:variable> <!-- Select all the Employees belonging to the Team --><xsl:variable name="lstEmployee" select="//Employee[TeamID=$lngTeamID]" />
<!-- Show details for Employees in Team --> <xsl:call-template name="ShowEmployeesInTeam"><xsl:with-param name="lstEmployee" select="$lstEmployee" />
</xsl:call-template> </xsl:for-each> <tr> <td colspan="4" class="RightJustified DarkBack">Grand Total</td> <td colspan="1" class="RightJustified DarkBack"> <!-- Show Grand Total of hours for all Employees --> <xsl:value-of select="sum(//Employee/Hours)" /> </td> </tr> </table> </body> </html> </xsl:template> <xsl:template name="ShowEmployeesInTeam"><xsl:param name="lstEmployee" />
<!-- Show the name of the Team currently being processed --> <tr> <td colspan="4" class="DarkBack">TEAM: <xsl:value-of select="$lstEmployee[1]/TeamName
" /></td> <td colspan="1" class="DarkBack RightJustified">HOURS</td> </tr> <!-- Show the total hours for each Employee in the Team --><xsl:for-each select="$lstEmployee[generate-id(.) = generate-id(key('keyEmployeeID', EmployeeID)[1])]">
<xsl:variable name="lngEmployeeID" select="EmployeeID" /> <!-- Show details of each Employee --> <tr> <td colspan="4"> <xsl:value-of select="$lstEmployee[EmployeeID=$lngEmployeeID]/Name" /> <xsl:value-of select="$lstEmployee[EmployeeID=$lngEmployeeID]/Surname" /> </td> <td colspan="1" class="RightJustified"> <!-- Show the total hours for the current Employee --><xsl:value-of select="sum($lstEmployee[EmployeeID=$lngEmployeeID]/Hours)" />
</td> </tr> </xsl:for-each> <tr> <td colspan="4" class="LightBack RightJustified">Sub-Total</td> <td colspan="1" class="LightBack RightJustified"> <!-- Show the total hours for all Employees in the Team --> <xsl:value-of select="sum($lstEmployee/Hours)" /> </td> </tr> </xsl:template> </xsl:stylesheet>
The CSS used to render the table in the image:
table { border-collapse: collapse; width: 30%; table-layout: fixed; border-style: solid; } table, td { border-width: 1px; } td { color: black; font-family: Arial; font-size: x-small; border-right-style: none; border-left-style: none; border-top-style: solid; border-bottom-style: solid; } .DarkBack { background-color: #0066FF; background-color: blue; color: white; font-weight: bold; } .LightBack { background-color: #99CCFF; color: black; } .RightJustified { text-align: right; }