I would like to categorize results from an XPath under headings by element name (and then by same attribute names). Note: XML data could be inconsistent and some elements with t
This transformation doesn't make any assumptions about the sets having the same number of attributes -- no assumptions at all.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:key name="kAnimalByProperties" match="animal"
use="concat(@atype, .)"/>
<xsl:variable name="vrtfNewDoc">
<xsl:apply-templates select="/pets/*">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</xsl:variable>
<xsl:template match="pets/*">
<animal atype="{name()}">
<xsl:copy-of select="@*"/>
<xsl:for-each select="@*">
<xsl:sort select="name()"/>
<attrib>|<xsl:value-of select="name()"/>|</attrib>
</xsl:for-each>
</animal>
</xsl:template>
<xsl:template match="/">
<xsl:for-each select="ext:node-set($vrtfNewDoc)">
<xsl:for-each select=
"*[generate-id()
=generate-id(key('kAnimalByProperties',
concat(@atype, .)
)[1]
)
]">
<table border="1">
<tr>
<td>Element Name</td>
<xsl:for-each select="*">
<td><xsl:value-of select="translate(.,'|','')"/></td>
</xsl:for-each>
</tr>
<xsl:for-each select=
"key('kAnimalByProperties', concat(@atype, .))">
<xsl:variable name="vcurAnimal" select="."/>
<tr>
<td><xsl:value-of select="@atype"/></td>
<xsl:for-each select="*">
<td>
<xsl:value-of select=
"$vcurAnimal/@*[name()=translate(current(),'|','')]"/>
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
<p/>
</xsl:for-each>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
When applied on the provided XML document:
<pets>
<dog name="Frank" cute="yes" color="brown" type="Lab"/>
<cat name="Fluffy" cute="yes" color="brown"/>
<cat name="Lucy" cute="no" color="brown"/>
<dog name="Spot" cute="no" color="brown"/>
<dog name="Rover" cute="yes" color="brown"/>
<dog name="Rupert" cute="yes" color="beige" type="Pug"/>
<cat name="Simba" cute="yes" color="grey"/>
<cat name="Princess" color="brown"/>
</pets>
the wanted, correct result is produced:
<table border="1">
<tr>
<td>Element Name</td>
<td>color</td>
<td>cute</td>
<td>name</td>
</tr>
<tr>
<td>cat</td>
<td>brown</td>
<td>yes</td>
<td>Fluffy</td>
</tr>
<tr>
<td>cat</td>
<td>brown</td>
<td>no</td>
<td>Lucy</td>
</tr>
<tr>
<td>cat</td>
<td>grey</td>
<td>yes</td>
<td>Simba</td>
</tr>
</table>
<p/>
<table border="1">
<tr>
<td>Element Name</td>
<td>color</td>
<td>name</td>
</tr>
<tr>
<td>cat</td>
<td>brown</td>
<td>Princess</td>
</tr>
</table>
<p/>
<table border="1">
<tr>
<td>Element Name</td>
<td>color</td>
<td>cute</td>
<td>name</td>
<td>type</td>
</tr>
<tr>
<td>dog</td>
<td>brown</td>
<td>yes</td>
<td>Frank</td>
<td>Lab</td>
</tr>
<tr>
<td>dog</td>
<td>beige</td>
<td>yes</td>
<td>Rupert</td>
<td>Pug</td>
</tr>
</table>
<p/>
<table border="1">
<tr>
<td>Element Name</td>
<td>color</td>
<td>cute</td>
<td>name</td>
</tr>
<tr>
<td>dog</td>
<td>brown</td>
<td>no</td>
<td>Spot</td>
</tr>
<tr>
<td>dog</td>
<td>brown</td>
<td>yes</td>
<td>Rover</td>
</tr>
</table>
<p/>
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="ByName-AttNum" match="*/*[@color='brown']" use="concat(name(),'++',count(@*))"/>
<xsl:template match="/">
<html>
<xsl:apply-templates/>
</html>
</xsl:template>
<xsl:template match="*/*[generate-id(.) = generate-id(key('ByName-AttNum',concat(name(),'++',count(@*)))[1])]">
<table>
<tr>
<th>ElementName</th>
<xsl:apply-templates select="@*" mode="headers">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</tr>
<xsl:apply-templates select="key('ByName-AttNum',concat(name(),'++',count(@*)))" mode="list"/>
</table>
</xsl:template>
<xsl:template match="*" mode="list">
<tr>
<td>
<xsl:value-of select="name()"/>
</td>
<xsl:apply-templates select="@*" mode="list">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</tr>
</xsl:template>
<xsl:template match="@*" mode="headers">
<th>
<xsl:value-of select="name()"/>
</th>
</xsl:template>
<xsl:template match="@*" mode="list">
<td>
<xsl:value-of select="."/>
</td>
</xsl:template>
</xsl:stylesheet>
Result:
<html>
<table>
<tr>
<th>ElementName</th>
<th>color</th>
<th>cute</th>
<th>name</th>
<th>type</th>
</tr>
<tr>
<td>dog</td>
<td>brown</td>
<td>yes</td>
<td>Frank</td>
<td>Lab</td>
</tr>
</table>
<table>
<tr>
<th>ElementName</th>
<th>color</th>
<th>cute</th>
<th>name</th>
</tr>
<tr>
<td>cat</td>
<td>brown</td>
<td>yes</td>
<td>Fluffy</td>
</tr>
<tr>
<td>cat</td>
<td>brown</td>
<td>no</td>
<td>Lucy</td>
</tr>
</table>
<table>
<tr>
<th>ElementName</th>
<th>color</th>
<th>cute</th>
<th>name</th>
</tr>
<tr>
<td>dog</td>
<td>brown</td>
<td>no</td>
<td>Spot</td>
</tr>
<tr>
<td>dog</td>
<td>brown</td>
<td>yes</td>
<td>Rover</td>
</tr>
</table>
<table>
<tr>
<th>ElementName</th>
<th>color</th>
<th>name</th>
</tr>
<tr>
<td>cat</td>
<td>brown</td>
<td>Princess</td>
</tr>
</table>
</html>
Note: This assumes that all elements having the same number of attributes have also the same attribute's name (like in your input sample).
EDIT: Better ouput markup.
EDIT 2: Another kind of solution: one header with all posible attribute (like CSV pattern) and order element by attribute count and name.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="attrByName" match="pets/*/@*" use="name()"/>
<xsl:variable name="attr" select="/pets/*/@*[count(.|key('attrByName',name())[1])=1]"/>
<xsl:template match="pets">
<html>
<table>
<tr>
<th>ElementName</th>
<xsl:apply-templates select="$attr" mode="headers">
<xsl:sort select="name()"/>
</xsl:apply-templates>
</tr>
<xsl:apply-templates select="*[@color='brown']">
<xsl:sort select="count(@*)" order="descending"/>
<xsl:sort select="name()"/>
</xsl:apply-templates>
</table>
</html>
</xsl:template>
<xsl:template match="pets/*">
<tr>
<td>
<xsl:value-of select="name()"/>
</td>
<xsl:apply-templates select="$attr" mode="list">
<xsl:sort select="name()"/>
<xsl:with-param name="node" select="."/>
</xsl:apply-templates>
</tr>
</xsl:template>
<xsl:template match="@*" mode="headers">
<th>
<xsl:value-of select="name()"/>
</th>
</xsl:template>
<xsl:template match="@*" mode="list">
<xsl:param name="node"/>
<td>
<xsl:value-of select="$node/@*[name()=name(current())]"/>
</td>
</xsl:template>
</xsl:stylesheet>
Result:
<html>
<table>
<tr>
<th>ElementName</th>
<th>color</th>
<th>cute</th>
<th>name</th>
<th>type</th>
</tr>
<tr>
<td>dog</td>
<td>brown</td>
<td>yes</td>
<td>Frank</td>
<td>Lab</td>
</tr>
<tr>
<td>cat</td>
<td>brown</td>
<td>yes</td>
<td>Fluffy</td>
<td></td>
</tr>
<tr>
<td>cat</td>
<td>brown</td>
<td>no</td>
<td>Lucy</td>
<td></td>
</tr>
<tr>
<td>dog</td>
<td>brown</td>
<td>no</td>
<td>Spot</td>
<td></td>
</tr>
<tr>
<td>dog</td>
<td>brown</td>
<td>yes</td>
<td>Rover</td>
<td></td>
</tr>
<tr>
<td>cat</td>
<td>brown</td>
<td></td>
<td>Princess</td>
<td></td>
</tr>
</table>
</html>
Note: This runs through the tree twice but without extension. Exact match for desired output without extensions would require to mimic key mechanism like this: run through the tree adding new keys (name of element plus attributes' names) to a param, then again for every key run through the tree filtering node by key (could be a little optimization keeping a node set for non matching elements...). Worst case (every node with distinc key) will pass trough a node: N (for key building) + (N + 1) * N / 2