tags:

views:

180

answers:

3

Given the following xml:

<container>
    <val>2</val>
    <id>1</id>
</container>
<container>
    <val>2</val>
    <id>2</id>
</container>
<container>
    <val>2</val>
    <id>3</id>
</container>
<container>
    <val>4</val>
    <id>1</id>
</container>
<container>
    <val>4</val>
    <id>2</id>
</container>
<container>
    <val>4</val>
    <id>3</id>
</container>

I'd like to return something like

2 - 1
2 - 3
4 - 1
4 - 3

Using a nodeset I've been able to get the last occurrence via:

exsl:node-set($list)/container[not(val = following::val)]

but I can't figure out how to get the first one.

+1  A: 

Basic XPath:

//container[position() = 1]  <- this is the first one
//container[position() = last()]  <- this is the last one

Here's a set of XPath functions in more detail.

Welbog
+1, beat me to it :) Convenience: "//container[1]" and "//container[last()]"
Tomalak
I'm trying to find the start and end ids for all vals. The first occurrence of a given val might not be container[1]. The example xml I provided is a slimmed down version of my actual xml.
steve
That shorthand is nice and all, but I think `position() =` is clearer for people who are new to XPath and XSL.
Welbog
@Welbog: I just wanted to mention it. No implications made. :)
Tomalak
+3  A: 
Tomalak
Now it's my turn to +1 you, Tomalak.
Welbog
+1 for the solution and guessing capabilities. I only understood what the OP wanted when reading his last comment to weblog's answer. However... Please, make it at least well-formed, if not complete. Also, why the "//" ? Very bad example for novices.
Dimitre Novatchev
I know, but my psychic powers don't reach that far. ;-) I wanted something that at least works when it is tried out. I'll add a warning.
Tomalak
+1  A: 

I. XSLT 1.0

Basically the same solution as the one by Tomalak, but more understandable Also it is complete, so you only need to copy and paste the XML document and the transformation and then just press the "Transform" button of your favourite XSLT IDE:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output method="text"/>

    <xsl:key name="kContByVal" match="container"
     use="val"/>

    <xsl:template match="/*">
      <xsl:for-each select=
       "container[generate-id()
                 =
                  generate-id(key('kContByVal',val)[1])
                 ]
       ">

       <xsl:variable name="vthisvalGroup"
        select="key('kContByVal', val)"/>

       <xsl:value-of select=
        "concat($vthisvalGroup[1]/val,
              '-',
              $vthisvalGroup[1]/id,
              '&#xA;'
              )
      "/>
       <xsl:value-of select=
        "concat($vthisvalGroup[last()]/val,
              '-',
              $vthisvalGroup[last()]/id,
              '&#xA;'
              )
        "/>
      </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

when this transformation is applied on the originally-provided XML document (edited to be well-formed):

<t>
    <container>
     <val>2</val>
     <id>1</id>
    </container>
    <container>
     <val>2</val>
     <id>2</id>
    </container>
    <container>
     <val>2</val>
     <id>3</id>
    </container>
    <container>
     <val>4</val>
     <id>1</id>
    </container>
    <container>
     <val>4</val>
     <id>2</id>
    </container>
    <container>
     <val>4</val>
     <id>3</id>
    </container>
</t>

the wanted result is produced:

2-1
2-3
4-1
4-3

Do note:

  1. We use the Muenchian method for grouping to find one container element for each set of such elements that have the same value for val.
  2. From the whole node-list of container elements with the same val value, we output the required data for the first container element in the group and for the last container element in the group.

II. XSLT 2.0

This transformation:

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"&gt;
    <xsl:output method="text"/>

    <xsl:template match="/*">
      <xsl:for-each-group select="container"
           group-by="val">
        <xsl:for-each select="current-group()[1], current-group()[last()]">
          <xsl:value-of select=
           "concat(val, '-', id, '&#xA;')"/>
        </xsl:for-each>
    </xsl:for-each-group>
    </xsl:template>
</xsl:stylesheet>

when applied on the same XML document as above, prodices the wanted result:

2-1
2-3
4-1
4-3

Do note:

  1. The use of the <xsl:for-each-group> XSLT instruction.

  2. The use of the current-group() function.

Dimitre Novatchev
+1 from me. I suspect your XSLT 1.0 solution is a little bit more efficient than mine. At least mine is shorter. ;-)
Tomalak
@Tomalak Thanks, I just wanted the solution to be understandable. In this respect the XSLT 2.0 solution helps, because it shows in more understandable form the same steps that are in the XSLT 1.0 solution. I think we are doing a good job by making comments on our solutions which then are taken into account and result in even more improved solutions.
Dimitre Novatchev