I think this simple example might ask the question a lot more clearly.

I have an input file with multiple products. There are different types of product (2 types with 2 product IDs is fine enough for this example), but the input will have many more.

I only want to output the info for the first product of each type that I encounter in the for each Product loop. (I'm outputting info for the lowest priced product, so the first one will be the lowest price because I sort by Rate first.)

So I want to read in each product, but only output the product's info if I haven't already output a product with that same ID. I have a variable for the IDArray, and I want to check if each product inside the for each product loop has an ID that is already in that IDArray - if not, continue, if it is already in the array, skip everything and just loop to the next.

I couldn't figure out how to get the child element to be a node from IDArray with the value of each CurrentID. It keeps adding that value as a node to CurrentID which is only in the scope of each product, not the whole product group. I know the following code does not work, but it illustrates the idea and gives me a place to start:

<?xml version="1.0" encoding="utf-8"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="xml" encoding="UTF-8" indent="yes" cdata-section-elements="prod_name adv_notes"/>
      <xsl:template match="/">
      <List>
         <xsl:for-each select="ProductGroup">
         <xsl:sort select="Product/Rate"/>
         <xsl:variable name="IDarray">
         <xsl:for-each select="Product">
            <xsl:variable name="CurrentID">
               <xsl:call-template name="processID">
                  <xsl:with-param name="ProductCode" select="ProductCode" />
                </xsl:call-template>
            </xsl:variable>
            <xsl:if test="not(contains($IDarray, $CurrentID))">
               <child elem="{@elem}">
                 <xsl:select value-of="$CurrentID" />
               </child>
              <Product>
               <xsl:attribute name="ID">
                 <xsl:select value-of="$CurrentID" />
               </xsl:attribute>
            <prod_name>
               <xsl:value-of select="ProductName"/>
            </prod_name>
            <rate>
               <xsl:value-of select="Rate"/>
            </rate>
        </Product>
        </xsl:if>
        </xsl:for-each>
        </xsl:variable>
        </xsl:for-each>
      </List>
    </xsl:template>


    <xsl:template name="processID">
     <xsl:param name="ProductCode"/>
     <xsl:choose>
       <xsl:when test="starts-with($ProductCode, '515')">5</xsl:when>
       <xsl:when test="starts-with($ProductCode, '205')">2</xsl:when>
     </xsl:choose>
    </xsl:template>

An input would look like this:

 <ProductGroup>
      <Product>
        <ProductCode>
           5155
        </ProductCode>
        <ProductName>
           House
        </ProductName>
        <Rate>
           3.99
        </Rate>
        </Product>
        <Product>
        <ProductCode>
           5158
        </ProductCode>
        <ProductName>
           House
        </ProductName>
        <Rate>
           4.99
        </Rate>
        </Product>
       </ProductGroup>
            <ProductGroup>
      <Product>
        <ProductCode>
           2058
        </ProductCode>
        <ProductName>
           House
        </ProductName>
        <Rate>
           2.99
        </Rate>
        </Product>
        <Product>
        <ProductCode>
           2055
        </ProductCode>
        <ProductName>
           House
        </ProductName>
        <Rate>
           7.99
        </Rate>
        </Product>
       </ProductGroup>

Output would be this for only that simple input file:

<List>
 <Product ID=5>
  <prod_name>
    House
  </prod_name>
  <rate>
    3.99
  </rate>
 </Product>
 <Product ID=2>
  <prod_name>
    House
  </prod_name>
  <rate>
    2.99
  </rate>
 </Product>
</List>

Implementing a Muenchian grouping is tough here because my actual dataset is huge. I made it simple to ask a question. If I can just get that array method working, the rest of my huge project will work.

Thanks so much! I know some of the awesome programmers here can help! :)

-Holly

Hi there Holly,

Quick question, where does the value in the output for describing the ID attribute of <Product> come from? It looks like it is the first digit of the ProductCode, can you confirm this?

Re-writing your stylesheet and will explain why after but stumped as to where that is coming from :)

Edited 4 Years Ago by Mike Askew

Assuming my assumptions are correct.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" encoding="UTF-8" indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:template match="/">
    <xsl:element name="List">
      <xsl:apply-templates select="//ProductGroup" />
    </xsl:element>
  </xsl:template>

  <xsl:template match="//ProductGroup">
    <xsl:element name="Product">
      <xsl:attribute name="ID">
        <xsl:value-of select="substring(./Product[1]/ProductCode, 10, 1)"/>
      </xsl:attribute>
      <xsl:element name="prod_name">
        <xsl:value-of select="./Product[1]/ProductName"/>
      </xsl:element>
      <xsl:element name="rate">
        <xsl:value-of select="./Product[1]/Rate"/>
      </xsl:element>
    </xsl:element>
  </xsl:template>

</xsl:stylesheet>

When run on

<Data>
  <ProductGroup>
    <Product>
      <ProductCode>
        5155
      </ProductCode>
      <ProductName>
        House
      </ProductName>
      <Rate>
        3.99
      </Rate>
    </Product>
    <Product>
      <ProductCode>
        5158
      </ProductCode>
      <ProductName>
        House
      </ProductName>
      <Rate>
        4.99
      </Rate>
    </Product>
  </ProductGroup>
  <ProductGroup>
    <Product>
      <ProductCode>
        2058
      </ProductCode>
      <ProductName>
        House
      </ProductName>
      <Rate>
        2.99
      </Rate>
    </Product>
    <Product>
      <ProductCode>
        2055
      </ProductCode>
      <ProductName>
        House
      </ProductName>
      <Rate>
        7.99
      </Rate>
    </Product>
  </ProductGroup>
</Data>

Produces

<?xml version="1.0" encoding="utf-8"?>
<List>
  <Product ID="5">
    <prod_name>
        House
      </prod_name>
    <rate>
        3.99
      </rate>
  </Product>
  <Product ID="2">
    <prod_name>
        House
      </prod_name>
    <rate>
        2.99
      </rate>
  </Product>
</List>

I rewrote your stylesheet as you were using XSLT programatically, ie. with the for-each which you most definately don't need :)

XSLT will match all nodes that match to the criteria you give it, for example making a template that matches Product will be run against all Product elements within the selection your working on.

It is worth noting on the line <xsl:value-of select="substring(./Product[1]/ProductCode, 10, 1)"/> we are looking at the 10th character for the number not the first, this is because your xml is quite nastily laid out with all this new line shiz and spaces. If you sort it into a proper formatted XML document with no new-lines within element values you will need to modify this figure accoridngly.

Hi Mikey!

Actually, my problem is a whole lot more complicated, so I tried to make a simple example that I could ask a question with.

The reality is that I'm translating it for another company that has their own IDs I have to assign based on 3 or 4 parameters. So the substring won't actually work.

I really needed to figure out the way where I'd add each product ID that I assign to an array so the next time I come across that type, I just skip that loop because that type of product was already done. It just has to be the first one I encounter because I've already sorted it so that the first is the best.

Any idea how to make that node-set method work? I really appreciate the help!

Thanks,
Holly

This article has been dead for over six months. Start a new discussion instead.