Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XSL: Counting Previous Unique Siblings

Tags:

xml

xslt

xpath

OK, I want to apply a XSL style sheet that counts the previous unique "ROLE" nodes and spits out the following output format of @name the number of unique ROLE nodes prior to the current nodes. I've wasted several hours on what should be an easy thing to implement. I have tried to implement this in several ways including the Muenchian Method, if/with variables (Can't increment a variable), applying templates to templates etc to no avail.

I have the following XML:

<ROLEACTIONINFO>
  <ROLE name="TESTER" /> 
  <ROLE name="PARENT1"/>
  <ROLE name="PARENT1"/>
  <ROLE name="PARENT1"/>
  <ROLE name="PARENT2"/>
  <ROLE name="PARENT2"/>
  <ROLE name="PARENT3"/>
  <ROLE name="PARENT4"/>
  <ROLE name="TESTROLE"/>
</ROLEACTIONINFO>

OUTPUT EXAMPLE:

TESTER  1
PARENT1 2
PARENT1 2
PARENT1 2
PARENT2 3
PARENT2 3
PARENT3 4
PARENT4 5
TESTROLE  6

Getting the count of the unique preceeding nodes is my problem. Any help would be appreciated

like image 271
Jay Avatar asked Jun 02 '09 20:06

Jay


1 Answers

This can be solved pretty easily using XPath. Here's the expression you're looking for: count((.|preceding-sibling::ROLE)[not(@name = preceding-sibling::ROLE/@name)])

This can be broken down to make it more readable, as I've done in the following XSLT 1.0 stylesheet:

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

  <xsl:output method="text"/>

  <!-- don't copy whitespace -->
  <xsl:template match="text()"/>

  <xsl:template match="ROLE">
    <xsl:variable name="roles-so-far" select=". | preceding-sibling::ROLE"/>
    <!-- Only select the first instance of each ROLE name -->
    <xsl:variable name="roles-so-far-unique"
                  select="$roles-so-far[not(@name = preceding-sibling::ROLE/@name)]"/>
    <xsl:apply-templates select="@name"/>
    <xsl:text> </xsl:text>
    <xsl:value-of select="count($roles-so-far-unique)"/>
    <xsl:text>&#xA;</xsl:text> <!-- linefeed -->
  </xsl:template>

</xsl:stylesheet>

Here's an alternative implementation, using the Muenchian method. First, declare a key:

<xsl:key name="roles" match="ROLE" use="@name"/>

Then, replace the definition of $roles-so-far-unique with something like this:

<!-- Among all the ROLEs having one of the names so far,
     select only the first one for each name -->
<xsl:variable name="roles-so-far-unique"
              select="../ROLE[@name = $roles-so-far/@name]
                             [generate-id(.) = generate-id(key('roles',@name)[1])]"/>

This code, of course, is more complicated. Unless you have a large data set requiring you to speed up processing using the Muenchian method (even then I would test to make sure it buys you anything), you might as well stick with the simpler version above.

Finally, in XSLT 2.0, it's much easier. Simple replace the $roles-so-far-unique definition with the following:

<!-- Return a list of distinct string values, with duplicates removed -->
<xsl:variable name="roles-so-far-unique"
              select="distinct-values($roles-so-far/@name)"/>

I hope this has helped you identify where you went wrong in the various attempts that you mentioned.

like image 80
Evan Lenz Avatar answered Nov 15 '22 07:11

Evan Lenz