Re: [xsl] One nodeset, multiple branches

Subject: Re: [xsl] One nodeset, multiple branches
From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx>
Date: Wed, 19 Dec 2001 17:44:43 +0000
Hi Eric,

> maybe Jeni will pop in and explain the deduplication process!

You rubbed my lamp...

Actually, this is another example where deduplication is not
particularly straight forward (the other one being deduplication
across multiple documents earlier today) :(

You only want to collect the right elements whose combination of
module and access attributes aren't the same as other right elements
that you're collecting.

You can put together a node set of the right elements for the user and
their groups as Wendell has shown you, so something like:

  <xsl:variable name="groups"
                select="/access/groups/group" />
  <xsl:variable name="user"
                select="/access/users/user[username = 'evitiello']" />
  <xsl:variable name="user-groups"
                select="$user/userGroups/group" />
  <xsl:variable name="rights"
    select="$user/rights/right |
            $groups/group[@name = $user-groups/@name]/rights/right" />

One method of doing getting the unique rights is to filter the right
elements you collect to only include the *first* right element that
you're interested in with a particular module+access combination.

Set up a key (with a top-level xsl:key element) that indexes all the
right elements according to their module+access combination. You can
create a 'value' for each right element, based on their module and
access, by concatenating the values of the two attributes (with a
separator just in case you have a module like 'newse' and a access
like 'dit', which could be confused with the module 'news' and access
'edit'):

<xsl:key name="rights" match="right"
         use="concat(@module, '+', @access)" />

You can call the key with the key() function - first argument is the
name of the key, second argument is the module+access combination. For
example:

  key('rights', 'news+add')

will retrieve *all* the right elements in the document with a module
attribute equal to 'news' and an access attribute equal to 'add'.
You're actually only interested in the ones that you've collected
together in the $rights variable, so you need to filter this set to
only include those that are in the $rights node set. You can do this
with set logic, as follows:

  key('rights', 'news+add')[count(.|$rights) = count($rights)]

Then you can find the first of these in document order with a
positional predicate at the end:

  key('rights', 'news+add')[count(.|$rights) = count($rights)][1]

That gives you the first 'right' element in the user's rights whose
module is 'news' and whose access is 'add'.

If you're looking at a right element whose module is 'news' and whose
access is 'add', you can work out whether that right element is the
same as the one you've just retrieved using set logic again:

  count(.|key('rights', 'news+add')[count(.|$rights) =
                                    count($rights)][1]) = 1

or:

  generate-id() =
  generate-id(key('rights', 'news+add')[count(.|$rights) =
                                        count($rights)])

The value that you use as the second argument to the key() function
could be based on the right element that you're looking at, so for any
right element, you could work out whether it was the first in the
document with that module and access using:

  count(.|key('rights', concat(@module, '+', @access))
            [count(.|$rights) = count($rights)][1]) = 1

So you could find all the unique right elements in the list using:

  <xsl:variable name="unique-rights"
    select="$rights
              [count(.|key('rights', concat(@module, '+', @access))
                         [count(.|$rights) = count($rights)][1]) = 1]" />

Since count($rights) is going to be the same each time, it might be
slightly more efficient to assign that to a variable at the start:

  <xsl:variable name="nrights" select="count($rights)" />
  <xsl:variable name="unique-rights"
    select="$rights
              [count(.|key('rights', concat(@module, '+', @access))
                         [count(.|$rights) = $nrights][1]) = 1]" />

If you find that XPath monstrous, you're not alone! :)

So alternatives are:

 - adapt the distinct template that I posted earlier today (polish my
   lamp again if you need help with that)
 - use saxon:distinct() if you're using Saxon (wipe Mike's lamp for
   that one)
 - use Dimitre's foldl generic template (buff up Dimitre's lamp for
   that)

Cheers,

Jeni (in panto mood)

---
Jeni Tennison
http://www.jenitennison.com/


 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread