views:

226

answers:

4

Hi,

I have this XML file, from which I'd like to count the number of users referenced in it. But they can appear in more than one category, and I'd like these duplicates not to be taken into account.
In the example below, the query should return 3 and not 4. Is there a way in XPath to do so? Users are not sorted at all.

<list>
  <group name='QA'>
    <user name='name1'>name1@email</user>
    <user name='name2'>name2@email</user>
  </group>
  <group name='DEV'>
    <user name='name3'>name3@email</user>
    <user name='name2'>name2@email</user>
  </group>
</list>
A: 

You will need to use two functions.

count(distinct-values(//list/group/user))

First get the distinct values, then count them

Mitchel Sellers
Good, but have you tested this? Please, correct -- this is just a "minor" problem. :)
Dimitre Novatchev
Yes, this works
Mitchel Sellers
@Mitchel-Sellers: If "this works" then you have an incompliant XPath 2.0 engine! Obviously, you didn't test your proposed XPath expression at all! *Hint1*: There is no `COUNT()` function in XPath. *Hint2*: XPath is case-sensitive.
Dimitre Novatchev
Sorry about that Dimitre that was a copy/paste error.
Mitchel Sellers
+1  A: 

using the functions namespace http://www.w3.org/2005/xpath-functions you can use

distinct-values(//list/group/user)

UPDATE:

At the top of your xsl/xslt file you should have a stylesheet element, map the url above to the prefix fn as below...

<xsl:stylesheet version="1.0"
 xmlns:fn="http://www.w3.org/2005/xpath-functions"
 >

then you can use

select="fn:distinct-values(//list/group/user)"

this would assume you are doing this in templates and not in some xpathdocument object inwhich case you need to use a namespacemanager class.

links...

http://stackoverflow.com/questions/2686650/xslt-add-namespace-to-root-element

http://www.xqueryfunctions.com/xq/fn_distinct-values.html

http://msdn.microsoft.com/en-us/library/d6730bwt(VS.80).aspx

Otherwise try Dimitre Novatchev's answer.

runrunraygun
ok, let's go deal with namespaces. Er, better stab myself.
Antoine
The `distinct-values()` function is implemented only in XPath 2.0 and this means only in XSLT 2.0. The code in your answer doesn't work in XSLT 1.0. Please, correct.
Dimitre Novatchev
+1  A: 

Not sure if you could do it in XPath, but it could be done easily using System.Linq:

string xml = "<list><group name='QA'><user name='name1'>name1@email</user><user name='name2'>name2@email</user></group><group name='DEV'><user name='name3'>name3@email</user><user name='name2'>name2@email</user></group></list>";
        XElement xe = XElement.Parse(xml);
        int distinctCount = xe.Elements().Elements().Select(n => n.Value).Distinct().Count();

In this example, distinctCount will equal 3.

JSprang
Sounds right, but I'm working with an old version of the .net framework and I cant upgrade a production app this easily.
Antoine
Ah, sorry! I didn't realize you weren't on .Net 3.5.
JSprang
+2  A: 

A pure XPath 1.0 -- one-liner:

Use:

count(/*/group/user[not(. = ../following-sibling::group/user)])

Dimitre Novatchev
this will only work if the usernames are is order wont it? If this is the case (it's late and i've been drinking...) you could add [(not(. = ../following-sibling::group/user)) and (not(. = ../preceading-sibling::group/user))]
runrunraygun
but it is right so +1
runrunraygun
@runrunraygun why test for not equal with everything? testing for not having equal following siblings takes just one value from a group of equal values -- exactly what is wanted. :) Thanks for your appreciation.
Dimitre Novatchev
@runrunraygun: what you propose: `[(not(. = ../following-sibling::group/user)) and (not(. = ../preceading-sibling::group/user))]` -- does not solve the problem at all. this will select only *unique values*, not *distinct values*.
Dimitre Novatchev
Yep, you're right, I knew I was missing something.
runrunraygun
That seem to be working, now I have to understand what it does. Thx Dimitre.
Antoine