tags:

views:

23

answers:

2

I have the following schema:

<parent>
  <child id="1" name="Child 1 Version 1" />
</parent>
<parent>
  <child id="2" name="Child 2 Version 1" />
</parent>
<parent>
  <child id="1" name="Child 1 Version 2" />
</parent>

That I want to only handle the last node for each id. Below is what I have tried based on some reading:

  <xsl:for-each select="//parent/child">
    <xsl:sort select="@id"/>
    <xsl:if test="not(@id=following-sibling::*/@id)">
      <xsl:element name="child">
        <xsl:value-of select="@name"/>
      </xsl:element>
    </xsl:if>
  </xsl:for-each>

But it does not seem to work. My output still contains all three of the elements. Any ideas on what I can do to correct my issue?

+1  A: 

This stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:key name="kParentByChildId" match="parent" use="child/@id"/>
    <xsl:template match="node()|@*">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="parent[count(.|key('kParentByChildId',
                                            child/@id)[last()]) != 1]"/>
</xsl:stylesheet>

Output:

<root>
    <parent>
        <child id="2" name="Child 2 Version 1"></child>
    </parent>
    <parent>
        <child id="1" name="Child 1 Version 2"></child>
    </parent>
</root>

Note. Grouping by @id, selecting last of the group.

Edit: Just in case this is confusing. Above stylesheet means: copy everything execpt those child not having the last @id of the same kind. So, it's not selecting the last of the group, but as reverse logic, striping not last in the group.

Second. Why yours is not working? Well, because of the following-sibling axis. Your method for finding the first of a kind is from an old time where there was few processor implementing keys. Now those days are gone.

So, this stylesheet:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:output indent="yes"/>
    <xsl:template match="t">
        <xsl:for-each select="parent/child">
            <xsl:sort select="@id"/>
            <xsl:if test="not(@id=following::child/@id)">
                <xsl:element name="child">
                    <xsl:value-of select="@name"/>
                </xsl:element>
            </xsl:if>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

Output:

<child>Child 1 Version 2</child>
<child>Child 2 Version 1</child>

Note: following axis, because child elements have not siblings.

Alejandro
+1  A: 

That I want to only handle the last node for each id. Below is what I have tried based on some reading:

  <xsl:for-each select="//parent/child"> 
    <xsl:sort select="@id"/> 
    <xsl:if test="not(@id=following-sibling::*/@id)"> 
      <xsl:element name="child"> 
        <xsl:value-of select="@name"/> 
      </xsl:element> 
    </xsl:if> 
  </xsl:for-each> 

But it does not seem to work. My output still contains all three of the elements. Any ideas on what I can do to correct my issue?

The problem with this code is that even though the nodes are in a sorted node-set, their following-sibling s are still the ones in the document.

In order for this code to work, one would first create an entirely new document in which the nodes are sorted in the desired way, then (in XSLT 1.0 it is necessary to use the xxx:node-set() extension on the produced RTF to make it an ordinary XML document) on this document the nodes have their siblings as desired.

Solution:

This transformation presents one possible XSLT 1.0 solution that does not require the use of extension functions:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kchildById" match="child" use="@id"/>

 <xsl:template match="/*">
  <t>
    <xsl:apply-templates select=
    "*/child[generate-id()
            =
             generate-id(key('kchildById',
                             @id)[last()]
                         )
            ]
    "/>
  </t>
 </xsl:template>

 <xsl:template match="child">
  <child>
   <xsl:value-of select="@name"/>
  </child>
 </xsl:template>
</xsl:stylesheet>

when applied on the provided XML fragment (wrapped in a top element to become well-formed XML document and adding a second version for id="2"):

<t>
    <parent>
        <child id="1" name="Child 1 Version 1" />
    </parent>
    <parent>
        <child id="2" name="Child 2 Version 1" />
    </parent>
    <parent>
        <child id="1" name="Child 1 Version 2" />
    </parent>
    <parent>
        <child id="2" name="Child 2 Version 2" />
    </parent>
</t>

produces the wanted result:

<t>
   <child>Child 1 Version 2</child>
   <child>Child 2 Version 2</child>
</t>

Do note: the use of the Muenchian method for grouping.

Dimitre Novatchev
Thanks, thats perfect. But i'm still not sure why the transformation I provided did not work. Would you be able to point me to any resources as to where this went wrong?
Zenox
@Zenox: The reason your transformation doesn't work as you expect it to do is in your expectations. When you process nodes in a sorted node-set, the nodes still retain their sibling (and other axes neighbourhood) relationships for the current document. You have to create a completely new document where the nodes are sorted -- then their siblings will be what you want.
Dimitre Novatchev
@Zenox: I have updated my answer and it now starts with an explanation of the problem in your code.
Dimitre Novatchev
@Dimitre: That's not the problem. This was the old grouping techninc (before key where implemented in mostly every processor). The problem is the axis.
Alejandro