Reputation:
I realize you can't use arrays in xsl and normally to do the task below it would take an array. Here's what I need...
Sample xml code...
<products>
<product>
<productNumber>1</productNumber>
<productType>TypeA</productType>
</product>
<product>
<productNumber>2</productNumber>
<productType>TypeB</productType>
</product>
<product>
<productNumber>3</productNumber>
<productType>TypeA</productType>
</product>
<product>
<productNumber>4</productNumber>
<productType>TypeC</productType>
</product>
<product>
<productNumber>5</productNumber>
<productType>TypeA</productType>
</product>
</products>
Above is a listing of unique "products" and each product is assigned a "productType" that could be repeated several times throughout the xml. I would like the xsl to pull a single entry for each "productType" without repeats.
The end result of above would be something like...
TypeA
TypeB
TypeC
And not ....
TypeA
TypeB
TypeA
TypeC
TypeA
I can't be the only one that has looked for this kind of functionality.
thoughts?
Upvotes: 1
Views: 181
Reputation:
Dimitre got me going on the right path. Here is the code that I came up with that worked for my needs, a simple deliniated stream of output to support an AJAX call...
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="utf-8"/>
<xsl:key name="kProdByBrand" match="Products/Product" use="Brand"/>
<xsl:template match="Products">
<xsl:for-each
select="Product
[generate-id() = generate-id(key('kProdByBrand', Brand)[1])]"><xsl:sort
select="Brand" /><xsl:value-of select="Brand" />|</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Given an xml that has many many members that look something like this...
<Product>
<Brand>Brand</Brand>
<OldPN>myCompany Part Number</OldPN>
...
</Product>
The output looks like this...
Brand|Brand1|Brand2|Brand3|
Upvotes: 0
Reputation: 243599
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:key name="kProdByType"
match="product" use="productType"/>
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/products">
<products>
<xsl:apply-templates select=
"product[generate-id()
=
generate-id(key('kProdByType', productType)[1])
]
"/>
</products>
</xsl:template>
<xsl:template match="product">
<productType value="{productType}">
<xsl:apply-templates mode="copy"
select="key('kProdByType', productType)"/>
</productType>
</xsl:template>
<xsl:template match="product" mode="copy">
<xsl:call-template name="identity"/>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<products>
<product>
<productNumber>1</productNumber>
<productType>TypeA</productType>
</product>
<product>
<productNumber>2</productNumber>
<productType>TypeB</productType>
</product>
<product>
<productNumber>3</productNumber>
<productType>TypeA</productType>
</product>
<product>
<productNumber>4</productNumber>
<productType>TypeC</productType>
</product>
<product>
<productNumber>5</productNumber>
<productType>TypeA</productType>
</product>
</products>
produces the wanted, correct grouping:
<products>
<productType value="TypeA">
<product>
<productNumber>1</productNumber>
<productType>TypeA</productType>
</product>
<product>
<productNumber>3</productNumber>
<productType>TypeA</productType>
</product>
<product>
<productNumber>5</productNumber>
<productType>TypeA</productType>
</product>
</productType>
<productType value="TypeB">
<product>
<productNumber>2</productNumber>
<productType>TypeB</productType>
</product>
</productType>
<productType value="TypeC">
<product>
<productNumber>4</productNumber>
<productType>TypeC</productType>
</product>
</productType>
</products>
Do note: This is an example of the well-known Muenchian method for grouping, which is the fastest known gouping technique in XSLT 1.0.
XSLT 2.0 solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|@*" name="identity">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/products">
<xsl:for-each-group select="product"
group-by="productType">
<productType value="{productType}">
<xsl:apply-templates select="current-group()"/>
</productType>
</xsl:for-each-group>
</xsl:template>
</xsl:stylesheet>
when this XSLT 2.0 transformation is applied on the provided XML document, exactly the same, correctly-grouped result is produced.
Upvotes: 1