views:

37

answers:

2

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?

+2  A: 

This transformation:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <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"&gt;
 <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.

Dimitre Novatchev
hmmm... thanks for this!I will inject it into my code, see how it works and let you know.This is cool cuz it opens up more possibilities for me.
Jimmmy
@Jimmmy: I'm glad you found my answer useful. The SO way of expressing this is to accept the answer (by clicking on the check-mark picture next to the answer). :)
Dimitre Novatchev
@Dimitre: +1 for XSLT 1.0 and XSLT 2.0 solutions. Also, adding your tag `xslt-grouping`
Alejandro
Thank guys. I'm looking to see how the solution works for me in my actual case. I may have further questions which is why I haven't marked it as answered yet :) More to come!
Jimmmy
@Jimmmy: Further questions will be new, separate questions. You and we are *done* with this question.
Dimitre Novatchev
@Dimitre - Thanks! You got me set on the right path. The code you provided above wasn't quite what I was looking for, so I looked up the Muenchian Method and found a site http://www.jenitennison.com/xslt/grouping/muenchian.html that described it step by step and I was able to come up with a code that worked for my needs, which was a simple character deliniated peice of text that would support an AJAX operation. I have posted the code that worked for me. Thanks again. Without your help I would not have known what to look for!
Jimmmy
A: 

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"&gt;
<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|
Jimmmy