Skip to content

Getting a distinct list of values in XSL from a substring of data in a node

by Cory Foy on September 12th, 2007

Today’s post comes from a tutorial I’m working on for a customer who still does a lot of XML/XSL stuff. One of the more challenging things to do in XSL1.0 (which is what most browsers support) was getting a distinct list of values from a set of nodes. Exslt has a good article on the subject, including this link to Jeni Tenison’s XSLT Template. In the back of my mind, I kept wondering if there was some way to do this with keys, and the customer I’m working with showed a way they do it.

Let’s say you have an XML document like:

<addresses>
  </addresses></font><address>
    <state>FL</state>
  </address>
  <address>
    <state>GA</state>
  </address>
  <address>
    <state>MN</state>
  </address>
  <address>
    <state>FL</state>
  </address>

And you want to output something like:

<states>
  <state>FL</state>
  <state>GA</state>
  <state>MN</state>
</states>

The way to do this with keys is to define an xsl:key like this:

<xsl:key name=”distinctState” match=”Addresses/Address” use=”./State”></xsl:key>

This let’s us set up a key to access Address nodes with. We can then get the distinct list in an xsl:for-each node by combining this with the generate-key function:

<xsl:for-each select=”Addresses/Address[generate-id() = generate-id(key('distinctState', ./State))"></xsl:for-each>

generate-id "generates a key that uniquely identifies a specified node", in this case, basically creating a node-set of the state nodes and returning the first node for each distinct value. So our full xsl would look something like:

<xsl:key name="distinctState" match="Addresses/Address" use="./State">
<xsl:template match="/">
<states>
<font face="Courier New"><xsl:for-each select="Addresses/Address[generate-id() = generate-id(key('distinctState', ./State))"></xsl:for-each></font>
  <state><xsl:value-of select="."></xsl:value-of></state>

</states></xsl:template></xsl:key>

Which is all well and good. However, in the tutorial example, state isn't in its own node - it's embedded in the Address like:

<address>123 Sample Way, Tampa, FL</address>

Which is a tad trickier. In the tutorial, we've allowed the assumption that the state will always be the last 2 characters of the Address field. So how can we get a distinct list of states with data like this?

Turns out that we can do it in a very similar (if complex) way. We start off by specifying the key:

<xsl:key name="distinctState" match="/Customers/Customer" use="substring(Address, string-length(Address)-1)"></xsl:key>

Here, our Address node is a child of Customer, which is a child of Customers - the root node. So we are matching Customers/Customer, and using the value of the last 2 characters of the Address. We then need to do the same for our for-each loop:

<xsl:for-each select="Customers/Customer[generate-id() = generate-id(key('distinctState', substring(Address, (string-length(Address)-1))))]“>
  <xsl:call-template name=”AggregateForState”>
    <xsl:with-param>
       </xsl:with-param></xsl:call-template></xsl:for-each>
name=”state”
       select=”substring(Address, (string-length(Address)-1))”/>
  

So we are doing the unique select on the state value. However, this returns the matching node – which is an Address node, so to use the value (in our example here as a parameter to a named template) we have to still substring it out.

Of course, this would probably be a better time to either see if you can get State into its own node, or do some sort of pre-processing to do that, but when you have neither of those options, this will work.

Thanks to Len and the SR team for the initial key idea

From → Uncategorized

7 Comments
  1. Anonymous permalink

    Or you could just put a bullet in your head.

  2. Nicky permalink

    pls put ] at the end of xsl:for-each
    before closing xsl:for-each
    Else everything workig fine.
    Thanks a lot.
    Karishma

  3. Anonymous permalink

    A whole year after this post, and the information is very helpful. Thanks very much.

    Carl.

  4. Anonymous permalink

    two years, still helpful

  5. Joe Schmoe permalink

    I agree with the first commenter. Seems like that would be much quicker and less painful.

  6. San permalink

    I am looking for some thing on this lines, but the code lines are missing, can you please update it.

    Cheers,
    San

  7. Kevin permalink

    Please update the code lines! Without, the tut is not really a tut.

    Thanks,
    Kevin

Leave a Reply

Note: XHTML is allowed. Your email address will never be published.

Subscribe to this comment feed via RSS