For a given xml, I need to generate a html table to represent the values in the xml. I开发者_JAVA技巧 need the recursion for any keyN, if valueN is text then just print it. If valueN is xml, then print a (nested)table with it's value. I think my lack of understanding of how to use XSLT recursion correctly is the base of the question. Any help appreciated.
Input:
<root>
<key1> Text Value </key1>
<key2>
<a> aaa </a>
<b> bbb </b>
</key2>
<keyN> valueN </keyN>
<root>
Output:
<table border="1px">
<tr>
<td> key1 </td>
<td> Text Value </td>
</tr>
<tr>
<td> key2 </td>
<td>
<table border="1px">
<tr> <td> a </td> <td> aaa </td> </tr>
<tr> <td> b </td> <td> bbb </td> </tr>
</table>
</td>
</tr>
<tr>
<td> keyN </td>
<td>
valueN (if valueN is text)
OR
<table> ... </table> (if valueN is xml)
<td>
</tr>
</table>
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/*//*[1]">
<table border="1">
<xsl:call-template name="makeRow"/>
</table>
</xsl:template>
<xsl:template match="*" name="makeRow">
<tr>
<td>
<xsl:value-of select="name()"/>
</td>
<td>
<xsl:apply-templates select="node()[1]"/>
</td>
</tr>
<xsl:apply-templates select="following-sibling::node()[1]"/>
</xsl:template>
<xsl:template match="/*">
<xsl:apply-templates select="node()[1]"/>
</xsl:template>
</xsl:stylesheet>
Output:
<table border="1">
<tr>
<td>key1</td>
<td> Text Value </td>
</tr>
<tr>
<td>key2</td>
<td>
<table border="1">
<tr>
<td>a</td>
<td> aaa </td>
</tr>
<tr>
<td>b</td>
<td> bbb </td>
</tr>
</table></td>
</tr>
<tr>
<td>keyN</td>
<td> valueN </td>
</tr>
</table>
Note: This use the fine grained traversal patter. Three rules: "first child descendant of root element", output table
and call makeRow
; makeRow
(match any element not being the first child descendant nor root element) output tr
and table cells with name and first child applycation, then apply templates to next sibling; root element rule, start the fine grained traversal.
This transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/*">
<table border="1px">
<xsl:apply-templates/>
</table>
</xsl:template>
<xsl:template match="*[*][parent::*]">
<tr>
<td><xsl:value-of select="name()"/></td>
<td>
<table border="1px">
<xsl:apply-templates/>
</table>
</td>
</tr>
</xsl:template>
<xsl:template match="*[not(*)]">
<tr>
<td><xsl:value-of select="name()"/></td>
<td><xsl:value-of select="."/></td>
</tr>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<root>
<key1> Text Value </key1>
<key2>
<a> aaa </a>
<b> bbb </b>
</key2>
<keyN> valueN </keyN>
</root>
produces the wanted, correct result:
<table border="1px">
<tr>
<td>key1</td>
<td> Text Value </td>
</tr>
<tr>
<td>key2</td>
<td>
<table border="1px">
<tr>
<td>a</td>
<td> aaa </td>
</tr>
<tr>
<td>b</td>
<td> bbb </td>
</tr>
</table>
</td>
</tr>
<tr>
<td>keyN</td>
<td> valueN </td>
</tr>
</table>
Do note the power of XSLT:
No explicit recursion.
No conditionals inside any template.
Completely push-style of processing.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<html>
<body>
<!-- apply root as a table -->
<xsl:apply-templates select="root" mode="table"/>
</body>
</html>
</xsl:template>
<xsl:template match="node()[not(self::text())]" mode="table">
<table border="1px">
<!-- create table and process children as rows -->
<xsl:apply-templates select="node()" mode="row"/>
</table>
</xsl:template>
<xsl:template match="node()[not(self::text())]" mode="row">
<tr>
<!-- make decision here.
If row contains only text - apply is as a simple row
In case there are some other nodes proces them as a table.
-->
<xsl:choose>
<xsl:when test=". = text()">
<xsl:apply-templates select="." mode="column"/>
</xsl:when>
<xsl:otherwise>
<td><xsl:value-of select="name()"/></td>
<td><xsl:apply-templates select="." mode="table"/></td>
</xsl:otherwise>
</xsl:choose>
</tr>
</xsl:template>
<xsl:template match="node()" mode="column">
<td><xsl:value-of select="name()"/></td>
<td><xsl:value-of select="."/></td>
</xsl:template>
</xsl:stylesheet>
Output:
<html>
<body>
<table border="1px">
<tr>
<td>key1</td>
<td>Text Value</td>
</tr>
<tr>
<td>key2</td>
<td>
<table border="1px">
<tr>
<td>a</td>
<td>aaa</td>
</tr>
<tr>
<td>b</td>
<td>bbb</td>
</tr>
</table>
</td>
</tr>
<tr>
<td>keyN</td>
<td>valueN</td>
</tr>
</table>
</body>
</html>
BTW it will be better to rewrite xsl:choose as two separate templates.
This sounds like homework :-)
You're working with 2 things here:
- Arbitrarily named elements that you need to apply an XSLT template to, and
- Determining what type of content an element has (either text or an XML fragment).
You want to use a template that can match elements in your XML arbitrarily (i.e. not elements with a specific name). <xsl:template match="*">
will match all elements in your XML document.
When you encounter an element, you need to create a table:
<xsl:template match="*">
<table border="1px">
</table>
</xsl:template>
Now we want to work out whether we're dealing with an XML fragment (an element) or a piece of text. To do this we match on node()
. Remember that a node can be an element, text, whitespace, processing instruction or a comment in an XML document. When you've matched a node, you want to create a new table row and display the name of the current node:
<xsl:template match="node()">
<tr>
<td>
<xsl:value-of select="local-name()"/>
</td>
</tr>
</xsl:template>
You then need to work out whether the node is a text node or not. You can use an <xsl:if>
or an <xsl:choose>
. I tend to prefer the latter. If it's a text node, display the value of the text otherwise treat the node like an XML fragment and call our initial template again (this is the recursive part).
...
<xsl:choose>
<xsl:when test="current() = text()">
<td>
<xsl:value-of select="." />
</td>
</xsl:when>
<xsl:otherwise>
<td>
<xsl:apply-templates select="*" mode="table"/>
</td>
</xsl:otherwise>
</xsl:choose>
...
Here's the final solution.
<xsl:template match="/root">
<xsl:apply-templates select="*" mode="table" />
</xsl:template>
<xsl:template match="*" mode="table">
<table border="1px">
<xsl:apply-templates select="." mode="table-row" />
</table>
</xsl:template>
<xsl:template match="node()" mode="table-row">
<tr>
<td>
<xsl:value-of select="local-name()"/>
</td>
<xsl:choose>
<xsl:when test="current() = text()">
<td>
<xsl:value-of select="." />
</td>
</xsl:when>
<xsl:otherwise>
<td>
<xsl:apply-templates select="*" mode="table"/>
</td>
</xsl:otherwise>
</xsl:choose>
</tr>
</xsl:template>
I'm using the mode
attribute on the template because an element is also a node in an XML document and <xsl:apply-templates select="*"/>
would match both <xsl:template match="*">
as well as <xsl:template match="node()">
. Using the mode
attribute removes the ambiguity.
ADDED FUNCTIONALITY FOR DISPLAYING ATTRIBUTES
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/*//*[1]">
<table border="1">
<xsl:call-template name="makeRow" />
</table>
</xsl:template>
<xsl:template match="*" name="makeRow">
<tr>
<td><xsl:value-of select="name()" /> <xsl:if
test="count(@*) > 0">
<table>
<xsl:apply-templates select="@*" />
</table>
</xsl:if></td>
<td><xsl:apply-templates select="node()[1]" /></td>
</tr>
<xsl:apply-templates select="following-sibling::node()[1]" />
</xsl:template>
<xsl:template match="/*">
<xsl:apply-templates select="node()[1]" />
</xsl:template>
<xsl:template match="@*">
<tr>
<td>@<xsl:value-of select="name()" /></td>
<td><xsl:value-of select="." /></td>
</tr>
</xsl:template>
</xsl:stylesheet>
精彩评论