Re: [xsl] Find elements whose ancestors are the same

Subject: Re: [xsl] Find elements whose ancestors are the same
From: Wendell Piez <wapiez@xxxxxxxxxxxxxxxx>
Date: Mon, 26 Sep 2005 17:00:37 -0400
Ted,

In XSLT 2.0, this is much easier with a key:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"; version="2.0">

<xsl:key name="folder-by-ancestry" match="folder" use="string-join(ancestor-or-self::*/@name,'#')"/>

<xsl:template match="folder">
<xsl:copy-of select="key('folder-by-ancestry',string-join(ancestor-or-self::*/@name,'#'))"/>
</xsl:template>
</xsl:stylesheet>


(# is used as a delimiter on the assumption it's not allowed inside names.)

Unfortunately, XSLT/XPath 1.0 give no function that allows you to stitch together all the names you need into a single key value. Accordingly I'd probably use two passes to do this in 1.0: in the first pass, annotate all the elements with a code (created by walking through the tree and collecting @name attributes); in the second, retrieve elements with the same code.

Pass 1:

<xsl:template match="folder">
  <xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:attribute name="ancestry">
      <xsl:for-each select="ancestor-or-self::*/@name">
        <xsl:value-of select="."/>
        <xsl:text>#</xsl:text>
      </xsl:for-each>
    </xsl:attribute>
  </xsl:copy>
</xsl:template>

Pass 2

<xsl:key name="folder-by-ancestry" match="folder" use="@ancestry"/>

<xsl:template match="folder">
  <xsl:variable name="ancestry">
    <xsl:for-each select="ancestor-or-self::*/@name">
      <xsl:value-of select="."/>
    </xsl:for-each>
  </xsl:variable>
  <xsl:copy-of select="key('folder-by-ancestry',$ancestry)"/>
</xsl:template>

Cheers,
Wendell

I'd At 04:38 PM 9/26/2005, you wrote:
Thanks again to everyone who has replied to these messages. I had no idea how hard this would be. It seemed so simple when I explained it to the client ;-)

I'm looking for an XPath expression that will find all elements whose ancestors are the same. By "same" I mean their @name is the same at each level of ancestry (and they appear in the same order).

For example, this expression would return a count of 2 for the following XML when filtered on the "daughter" folder (or "mother" or "grandfather" for that matter).

<root>
    <folder name="grandfather">
        <folder name="mother">
            <folder name="daughter" />
        </folder>
    </folder>
    <folder name="grandfather">
        <folder name="mother">
            <folder name="daughter" />
        </folder>
    </folder>
    <folder name="grandmother">
        <folder name="father">
            <folder name="son" />
        </folder>
    </folder>
</root>

I know it's going to be something like this, but I just can't get my head around it!

<xsl:copy-of select="/root//folder[@name = following-sibling::*/@name and ancestor-or-self::*/@name = following-sibling::*/@name/ancestor-or-self::*/@name" />


======================================================================
Wendell Piez                            mailto:wapiez@xxxxxxxxxxxxxxxx
Mulberry Technologies, Inc.                http://www.mulberrytech.com
17 West Jefferson Street                    Direct Phone: 301/315-9635
Suite 207                                          Phone: 301/315-9631
Rockville, MD  20850                                 Fax: 301/315-8285
----------------------------------------------------------------------
  Mulberry Technologies: A Consultancy Specializing in SGML and XML
======================================================================

Current Thread