Reputation: 520
I have an XSLT that I have created to group data for a catalog of shoes. The shoes need to be grouped by "line" and by "brand". The Line and Brand titles need to appear at the beginning of each section. Each line has more than one brand. Each brand usually has more than one shoe in it.
Here is some sample XML data:
<?xml version="1.0" encoding="UTF-8"?>
<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<shoe>
<line>LINE 43: WOMENS BRANDED ATHLETIC</line>
<brand>WALKING</brand>
<style-name>NEW BALANCE-NB 475V2 (WIDE)</style-name>
<color>Black</color>
<price>67.99</price>
</shoe>
<shoe>
<line>LINE 43: WOMENS BRANDED ATHLETIC</line>
<brand>WALKING</brand>
<style-name>NEW BALANCE-496 (WIDE)</style-name>
<color>Grey/Pink</color>
<price>64.99</price>
</shoe>
<shoe>
<line>LINE 43: WOMENS BRANDED ATHLETIC</line>
<brand>CROSS TRANING</brand>
<style-name>FILA-MEMORY PANACHE</style-name>
<color>Black/Pink</color>
<price>69.99</price>
</shoe>
<shoe>
<line>LINE 43: WOMENS BRANDED ATHLETIC</line>
<brand>RUNNING</brand>
<style-name>FILA-VITALITY 2 TRAIL</style-name>
<color>Grey/Prpl/Turq</color>
<price>59.99</price>
</shoe>
<shoe>
<line>LINE 87: MENS BRANDED ATHLETIC</line>
<brand>CASUAL</brand>
<style-name>LEVI'S-HAMILTON BUCK HI</style-name>
<color>Black/Black</color>
<price>34.99</price>
</shoe>
<shoe>
<line>LINE 87: MENS BRANDED ATHLETIC</line>
<brand>CASUAL</brand>
<style-name>EVERLAST-EVAN SKATE</style-name>
<color>Black</color>
<price>29.99</price>
</shoe>
<shoe>
<line>LINE 87: MENS BRANDED ATHLETIC</line>
<brand>RUNNING</brand>
<style-name>SKECHERS-POWER SWITCH (WIDE)</style-name>
<color>Black/White</color>
<price>69.99</price>
</shoe>
<shoe>
<line>LINE 87: MENS BRANDED ATHLETIC</line>
<brand>RUNNING</brand>
<style-name>SKECHERS-EQUALIZER GEL TOP </style-name>
<color>Black</color>
<price>69.99</price>
</shoe>
</catalog>
Here is my XSLT:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8"/>
<xsl:key name="shoes-by-line" match="shoe" use="line" />
<xsl:key name="shoes-by-brand" match="shoe" use="concat(line,'#',brand )" />
<xsl:template match="catalog">
<catalog>
<shoes>
<xsl:for-each select="shoe[count(. | key('shoes-by-line', line)[1]) = 1]">
<line>
<xsl:value-of select="line"/>
</line>
<brands>
<brand>
<xsl:value-of select="brand"/>
</brand>
<xsl:for-each select="key( 'shoes-by-brand', concat(line,'#',brand ))">
<shoe>
<style-name>
<xsl:value-of select="style-name"/>
</style-name>
<color>
<xsl:value-of select="color"/>
</color>
<price>
<xsl:value-of select="price"/>
</price>
</shoe>
</xsl:for-each>
</brands>
</xsl:for-each>
</shoes>
</catalog>
</xsl:template>
</xsl:stylesheet>
I am getting 1 "line" and 1 "brand" but I'm not getting the additional brands in each line. There should be at least two to three brands in each line. In this XML there are 2 lines.
Here is the XML structure that I want:
<?xml version="1.0" encoding="UTF-8"?>
<catalog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<lines>
<line>LINE 43: WOMENS BRANDED ATHLETIC</line>
<brands>
<brand>WALKING</brand>
<shoes>
<shoe>
<style-name>NEW BALANCE-NB 475V2 (WIDE)</style-name>
<color>Black</color>
<price>67.99</price>
</shoe>
<shoe>
<style-name>NEW BALANCE-496 (WIDE)</style-name>
<color>Grey/Pink</color>
<price>64.99</price>
</shoe>
</shoes>
</brands>
</lines>
<lines>
<line>LINE 43: WOMENS BRANDED ATHLETIC</line>
<brands>
<brand>CROSS TRANING</brand>
<shoes>
<shoe>
<style-name>FILA-MEMORY PANACHE</style-name>
<color>Black/Pink</color>
<price>69.99</price>
</shoe>
<shoe>
<line>LINE 43: WOMENS BRANDED ATHLETIC</line>
<brand>RUNNING</brand>
<style-name>FILA-VITALITY 2 TRAIL</style-name>
<color>Grey/Prpl/Turq</color>
<price>59.99</price>
</shoe>
</shoes>
</brands>
</lines>
<lines>
<line>LINE 87: MENS BRANDED ATHLETIC</line>
<brands>
<brand>CASUAL</brand>
<shoes>
<shoe>
<style-name>LEVI'S-HAMILTON BUCK HI</style-name>
<color>Black/Black</color>
<price>34.99</price>
</shoe>
<shoe>
<style-name>EVERLAST-EVAN SKATE</style-name>
<color>Black</color>
<price>29.99</price>
</shoe>
</shoes>
</brands>
</lines>
<lines>
<line>LINE 87: MENS BRANDED ATHLETIC</line>
<brands>
<brand>RUNNING</brand>
<shoes>
<shoe>
<line>LINE 87: MENS BRANDED ATHLETIC</line>
<brands>
<brand>RUNNING</brand>
<shoes>
<shoe>
<style-name>SKECHERS-POWER SWITCH (WIDE)</style-name>
<color>Black/White</color>
<price>69.99</price>
</shoe>
<shoe>
<style-name>SKECHERS-EQUALIZER GEL TOP </style-name>
<color>Black</color>
<price>69.99</price>
</shoe>
</shoes>
</brands>
</lines>
</catalog>
Upvotes: 1
Views: 109
Reputation: 11416
With some adjustments to your XSLT the wanted result is generated:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8"/>
<xsl:key name="shoes-by-line" match="shoe" use="line" />
<xsl:key name="shoes-by-brand" match="shoe" use="concat(line,'#',brand )" />
<xsl:template match="catalog">
<catalog>
<xsl:for-each select="shoe[count(. | key('shoes-by-line', line)[1]) = 1]">
<lines>
<line>
<xsl:value-of select="line"/>
</line>
<brands>
<brand>
<xsl:value-of select="brand"/>
</brand>
<shoes>
<xsl:for-each select="key( 'shoes-by-brand', concat(line,'#',brand ))">
<shoe>
<style-name>
<xsl:value-of select="style-name"/>
</style-name>
<color>
<xsl:value-of select="color"/>
</color>
<price>
<xsl:value-of select="price"/>
</price>
</shoe>
</xsl:for-each>
</shoes>
</brands>
</lines>
</xsl:for-each>
</catalog>
</xsl:template>
</xsl:stylesheet>
Your XSLT already generated the desired output, it was only necessary to move some grouping elements to a different position:
Before:
<catalog>
<shoes>
<xsl:for-each select="shoe[count(. | key('shoes-by-line', line)[1]) = 1]">
<line>
<xsl:value-of select="line"/>
</line>
<brands>
<brand>
<xsl:value-of select="brand"/>
</brand>
<xsl:for-each select="key('shoes-by-brand',
concat(line,'#',brand ))">
<shoe>
...
Adjusted:
<catalog>
<xsl:for-each select="shoe[count(. | key('shoes-by-line', line)[1]) = 1]">
<lines>
<line>
<xsl:value-of select="line"/>
</line>
<brands>
<brand>
<xsl:value-of select="brand"/>
</brand>
<shoes>
<xsl:for-each select="key('shoes-by-brand',
concat(line,'#',brand ))">
<shoe>
...
Update: As noticed in the comment, above XSLT doesn't provide the correct output. Just as a different approach: following XSLT using xsl:for-each-group
instead of keys:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xsl:stylesheet>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8"/>
<xsl:template match="catalog">
<catalog>
<xsl:for-each-group select="shoe" group-by="line">
<xsl:for-each-group select="current-group()" group-by="brand">
<lines>
<line>
<xsl:value-of select="line"></xsl:value-of>
</line>
<brands>
<xsl:for-each-group select="current-group()" group-by="brand">
<brand>
<xsl:value-of select="current-grouping-key()"></xsl:value-of>
</brand>
<shoes>
<xsl:for-each select="current-group()">
<shoe>
<style-name>
<xsl:value-of select="style-name"/>
</style-name>
<color>
<xsl:value-of select="color"/>
</color>
<price>
<xsl:value-of select="price"/>
</price>
</shoe>
</xsl:for-each>
</shoes>
</xsl:for-each-group>
</brands>
</lines>
</xsl:for-each-group>
</xsl:for-each-group>
</catalog>
</xsl:template>
</xsl:stylesheet>
when applied to your input XML produces the following output:
<?xml version="1.0" encoding="UTF-8"?>
<catalog>
<lines>
<line>LINE 43: WOMENS BRANDED ATHLETIC</line>
<brands>
<brand>WALKING</brand>
<shoes>
<shoe>
<style-name>NEW BALANCE-NB 475V2 (WIDE)</style-name>
<color>Black</color>
<price>67.99</price>
</shoe>
<shoe>
<style-name>NEW BALANCE-496 (WIDE)</style-name>
<color>Grey/Pink</color>
<price>64.99</price>
</shoe>
</shoes>
</brands>
</lines>
<lines>
<line>LINE 43: WOMENS BRANDED ATHLETIC</line>
<brands>
<brand>CROSS TRANING</brand>
<shoes>
<shoe>
<style-name>FILA-MEMORY PANACHE</style-name>
<color>Black/Pink</color>
<price>69.99</price>
</shoe>
</shoes>
</brands>
</lines>
<lines>
<line>LINE 43: WOMENS BRANDED ATHLETIC</line>
<brands>
<brand>RUNNING</brand>
<shoes>
<shoe>
<style-name>FILA-VITALITY 2 TRAIL</style-name>
<color>Grey/Prpl/Turq</color>
<price>59.99</price>
</shoe>
</shoes>
</brands>
</lines>
<lines>
<line>LINE 87: MENS BRANDED ATHLETIC</line>
<brands>
<brand>CASUAL</brand>
<shoes>
<shoe>
<style-name>LEVI'S-HAMILTON BUCK HI</style-name>
<color>Black/Black</color>
<price>34.99</price>
</shoe>
<shoe>
<style-name>EVERLAST-EVAN SKATE</style-name>
<color>Black</color>
<price>29.99</price>
</shoe>
</shoes>
</brands>
</lines>
<lines>
<line>LINE 87: MENS BRANDED ATHLETIC</line>
<brands>
<brand>RUNNING</brand>
<shoes>
<shoe>
<style-name>SKECHERS-POWER SWITCH (WIDE)</style-name>
<color>Black/White</color>
<price>69.99</price>
</shoe>
<shoe>
<style-name>SKECHERS-EQUALIZER GEL TOP </style-name>
<color>Black</color>
<price>69.99</price>
</shoe>
</shoes>
</brands>
</lines>
</catalog>
Note that there's one difference from the requested output as given in the OP - the brand RUNNING
for the line LINE 43: WOMENS BRANDED ATHLETIC
is a separate brand instead of being in the CROSS TRAINING
brand, but maybe this is a little mistake in the wanted output. Otherwise it would be necessary to find a solution for getting RUNNING
and CROSS TRAINING
listed in the same brand.
As the shoes should be listed for each brand of each line, <xsl:for-each-group select="shoe" group-by="line">
selects first all shoes grouped by line. Then all brands in this group are selected by brand using <xsl:for-each-group select="current-group()" group-by="brand">
and all shoes of the brand processed in the loop <xsl:for-each select="current-group()">
.
Update: As mentioned in the comments, this is an XSLT 2.0 stylesheet. I've just corrected the version in above template. Though I've tested the template with an online XSLT Test Tool having it declared as version 1.0, it didn't throw any error (though it should have).
Upvotes: 1
Reputation: 520
I revisited the XSLT I believe I found the solution using XSLT 1.0
<?xml version="1.0" encoding="UTF-8"?><!-- DWXMLSource="D76-shoebook.xml" -->
<!DOCTYPE xsl:stylesheet>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" encoding="UTF-8"/>
<xsl:key name="shoes-by-line" match="shoe" use="line"/>
<xsl:key name="shoes-by-brand" match="shoe" use="concat(line, '|', brand)"/>
<xsl:template match="catalog">
<catalog>
<xsl:for-each select="shoe[generate-id() = generate-id(key('shoes-by-line', line)[1])]">
<lines>
<xsl:for-each select="key('shoes-by-line', line)[generate-id() = generate-id(key('shoes-by-brand', concat(line, '|', brand))[1])]">
<line><xsl:value-of select="line"/></line><xsl:text>
</xsl:text><brand><xsl:value-of select="brand"/></brand><xsl:text>
</xsl:text><shoes>
<xsl:for-each select="key('shoes-by-brand', concat(line, '|', brand))">
<shoe>
<style-name><xsl:value-of select="style-name" /></style-name>
<color><xsl:value-of select="color" /></color><xsl:text>
<price><xsl:value-of select="price" /></price><xsl:text>
</xsl:text>
</shoe>
</xsl:for-each>
</shoes>
</xsl:for-each>
</lines>
</xsl:for-each>
</catalog>
</xsl:template>
</xsl:stylesheet>
This produces the proper grouping and nesting from the source XML keying off LINE and then on BRAND.
Upvotes: 0