I have an xml datasource which looks like this:
You haven't shown your desired output. But in XSLT 2.0, you could do something like this:
<xsl:template match="Rows">
<xsl:for-each-group select="Row" group-by="@region">
<region name="{current-grouping-key()}">
<xsl:for-each-group select="current-group()" group-by="@dept">
<dept name="{current-grouping-key()}">
<xsl:for-each select="current-group()">
<state name="{@state}"/>
</xsl:for-each>
</dept>
</xsl:for-each-group>
</region>
</xsl:for-each>
</xsl:template>
The following stylesheet shows a general approach to grouping at multiple levels:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="byRegion" match="Row" use="@region" />
<xsl:key name="byRegionState"
match="Row" use="concat(@region, '|', @state)" />
<xsl:key name="byRegionStateDept"
match="Row" use="concat(@region, '|', @state, '|', @dept)" />
<xsl:template
match="Row[generate-id() = generate-id(key('byRegion', @region)[1])]">
<region name="{@region}">
<xsl:apply-templates
select="key('byRegion', @region)
[generate-id() =
generate-id(key('byRegionState',
concat(@region, '|', @state))[1])]"
mode="states" />
</region>
</xsl:template>
<xsl:template match="Row" mode="states">
<state name="{@state}">
<xsl:apply-templates
select="key('byRegionState', concat(@region, '|', @state))
[generate-id() =
generate-id(key('byRegionStateDept',
concat(@region, '|', @state, '|', @dept))[1])]"
mode="dept" />
</state>
</xsl:template>
<xsl:template match="Row" mode="dept">
<dept><xsl:value-of select="@dept" /></dept>
</xsl:template>
</xsl:stylesheet>
It produces the following output on your input:
<region name="East">
<state name="NY">
<dept>HR</dept>
<dept>SD</dept>
<dept>MM</dept>
</state>
<state name="NJ">
<dept>HR</dept>
<dept>SD</dept>
</state>
</region>
<region name="West">
<state name="CO">
<dept>SD</dept>
<dept>MM</dept>
</state>
</region>
For XSLT 1.0 you can use the "Muenchian Method".
You would create a key to index the Row
elements by a combination of @region
and @dept
values.
Then you would get the first occurrence of that region/department combination (that has the wanted region ($DeptQS
)) and output the department (dept
).
Here's an example stylesheet outputting <test>
elements to show the context:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes" method="xml" omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kDept" match="Department/Rows/Row" use="concat(@region,'|',@dept)"/>
<xsl:param name="DeptQS">East</xsl:param>
<xsl:template match="/*">
<xsl:for-each select="Department/Rows/Row[count(.|key('kDept',
concat($DeptQS,'|',@dept))[1])=1]">
<test>Current dept: <xsl:value-of select="@dept"/></test>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Here's the output using your input XML:
<test>Current dept: HR</test>
<test>Current dept: SD</test>
<test>Current dept: MM</test>
how can I write out the department for the region only once?
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="pRegion" select="'East'"/>
<xsl:key name="kRowByDept-Region" match="Row"
use="concat(@dept,'++',@region)"/>
<xsl:template match="Rows">
<xsl:apply-templates select="Row[generate-id()
= generate-id(
key('kRowByDept-Region',
concat(@dept,'++',$pRegion)
)[1]
)
]"/>
</xsl:template>
<xsl:template match="Row">
<xsl:value-of select="concat(@dept,'
')"/>
</xsl:template>
</xsl:stylesheet>
Output:
HR
SD
MM