XSLT, counting

Counting

1. counting
2. How to count the number of nodes whose text is true.
3. How to count leaf nodes
4. Counting children
5. How to calculate tree depth
6. Dynamically counting an attribute
7. Count of elements in a document
8. Counting the number of elements in a document
9. Counting all nodes
10. Counting words in attributes
11. Counting nodes efficiently
12. Select first 5 nodes
13. Counting related nodes
14. The right way to count?
15. Sum. An approach
16. Count occurences of one string in another
17. Count preceding siblings, and cousins
18. Count distinct values
19. counting, how it works

1.

counting

David Carlisle



> Does the count function, only count things that are from differents files?

The count function just reports the size of the set and sets, by definition, never have duplicates.

If count(x) is 5 then count(x|x|x) is also 5.

2.

How to count the number of nodes whose text is true.

Nicolas DELAHAYE



> <Whatever>
> <Choice>
> true
> </Choice>
> <Choice>
> false
> </Choice>
> <Choice>
> true
> </Choice>
> <Choice>
> false
> </Choice>
>
> ... you get the idea ... :-)
>


Just an example :

<xsl:value-of select="count(Whatever[normalize-space(Choice)='true'])"/>

            

3.

How to count leaf nodes

Mike Ball

<xsl:value-of select="count(descendant::*[not(child::*)])"/>

gives the count of leaf elements below the current element. Or

<xsl:value-of select="count(descendant::element-name[not(child::*)])"/>

if you only want to count the number of elements named 'element-name'

Mike Brown adds a shorter form

If a leaf is defined as being an element with no element children:

<xsl:value-of select="count(//*[not(*)])"/>

You can substitute * with node() if you really mean leaf nodes of any type.

4.

Counting children

Phil Lanch


Q expansion:



Q expansion:

I'm having trouble counting (not numbering) a given node's
children.  Given the following XML --

<xml>
<list>
        <item>Item1</item>
        <item>Item2</item>
        <item>Item3</item>
</list>
</xml>

I'd like to produce a statement of the number of items in
the list followed by the actual list, something like the
following HTML --

<HTML>
There are 3 items in the list.<BR>
<UL>
<LI>Item1
<LI>Item2
<LI>Item3
</UL>
</HTML>


<xsl:output method="html"/>

<xsl:template match="xml">
  <HTML>
    <xsl:apply-templates/>
  </HTML>
</xsl:template>

<xsl:template match="list">
  There are <xsl:value-of select="count(item)"
              /> items in the list.<BR/>
  <UL>
    <xsl:apply-templates/>
  </UL>
</xsl:template>

<xsl:template match="item">
  <LI><xsl:apply-templates/></LI>
</xsl:template>
<xsl:template match="xml">
  <HTML>
    <xsl:apply-templates/>
  </HTML>
</xsl:template>

<xsl:template match="list">
  There are <xsl:value-of select="count(item)"
          /> items in the list.<BR/>
  <UL>
    <xsl:apply-templates/>
  </UL>
</xsl:template>

<xsl:template match="item">
  <LI><xsl:apply-templates/></LI>
</xsl:template>


            

5.

How to calculate tree depth

David Carlisle


Michael Kay will have fits as this is order n for some
suitably large n, but..

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

<xsl:output method="xml" indent="yes"/>

<xsl:template match="/">
<xsl:for-each select="//*[not(*)]">
 <xsl:sort select="count(ancestor::*)"/>
  <xsl:if test="position()=last()">
    <xsl:value-of select="count(ancestor::*)"/>
  </xsl:if>
</xsl:for-each>
</xsl:template>

</xsl:stylesheet>

Which for

<menu>
    <entry>
        <name>one</name>
        <entry>
            <name>oneone</name>      
        </entry>
        <entry>
            <name>onetwo</name>     
        </entry>
    </entry>
    <entry>
        <name>two</name>
    </entry>
</menu>

gives a depth of 3 for element 'name'.
 
 

6.

Dynamically counting an attribute

Jeni Tennison

I have the following question. Assume that I have the following XML file, ("........" represents some removed irrelevant lines):

<CPU partNum="1345"> 
.............
<CPU partNum="15678">
............
<CPU partNum="1345">
..............
<CPU partNum="11111">
..............
<CPU partNum="11111">
............
<CPU partNum="1345">
..........
<CPU partNum="11111">
.........

and I want to write a stylesheet such that after parsing this XML file, it counts the number of those elements which have the same partNum and report that in some way, say in a table (in HTML) like the following:

partNum     Qty
1345        3
15678       1
11111       3

This is a grouping problem: you want to group the parts together, and count the number of items in each group. Grouping problems are currently (and using basic XSLT without any processor extensions) best solved using the Muenchian method, which involves defining a key that does the grouping quickly for you.

When you design a key to help you group things together, you have three variables to set:

* name - a name for the key, anything you like: 'parts' in this case
* match - the things that you want to group: elements with 'partNum'
attributes in this case
* use - the thing that defines the groups: the number of the part in this case

So, the key that you want looks like:

<xsl:key name="parts" match="*[@partNum]" use="@partNum" />

This element is a top-level element: it goes right underneath the xsl:stylesheet element. Note that I have assumed within the 'match' expression that different elements, with different names, might all have 'partNum' attributes, so as well as:

<CPU partNum="1345" />

you might have:

<HDD partNum="5437" />

If the elements that you're interested in are *all* CPUs, then you could use:

<xsl:key name="CPUs" match="CPU" use="@partNum" />

instead. Indeed, you could define several different keys for each of the elements that have partNums, if you know those in advance.

Retrieving the groups involves using the key() function. The first argument is the name of the key (so 'parts' in this case) and the second argument is the value of the thing that was used to group the things you're grouping, so a part number in this case, something like:

  key('parts', '1345')

You can dynamically decide what the part number (or even key name) is.

Getting a list of the part numbers is the slightly tricky bit. You need to identify one of the group for each of the groups that you've defined in the key. You do this by comparing a node (a 'CPU' element) with the first node that you get when you use the key value for that node to index into the key. So, if the first element that you get when you use the value '1345' to index into your 'parts' key is the same as the current element, then you know that it's the first one to appear in the list. To compare nodes, you use the generate-id() function. So, a template matching on the parent of the CPU elements should look something like:

<xsl:template match="parts">
  <table>
    <tr><th>partNum</th><th>Qty</th></tr>
    <xsl:apply-templates
      select="*[@partNum and
                generate-id(.)=generate-id(key('parts', @partNum))]" />
  </table>
</xsl:template>

This guarantees that the only parts that have templates applied to them are those that occur first in the list of parts with that particular part number. You can then have a template that matches on them and outputs the rows of your table. To count the number of parts with that particular part number, you count the number of elements that are retrieved when you use that part number to index into the key that you've used to group your elements:

<xsl:template match="*[@partNum]">
  <tr>
    <!-- first column is the value of the partNum attribute -->
    <td><xsl:value-of select="@partNum" /></td>
    <!-- second column is the number of parts with that partNum -->
    <td><xsl:value-of select="count(key('parts', @partNum))" /></td>
  </tr>
</xsl:template>

This is tested and works in SAXON. The important things to take out of this example are how to use the key to group the elements that you're interested in:

<xsl:key name="parts" match="*[@partNum]" use="@partNum" />

how to retrieve them and count them:

  count(key('parts', @partNum))

and how to retrieve the unique elements so that you can identify what part numbers are used in your file:

  *[@partNum and
    generate-id(.)=generate-id(key('parts', @partNum))]

>The difficulty that I have is the following: I have different XML files 
>(with similar structure)
>in which, these partNum are different , and may even change in the future, 

I'm not sure whether this means you want to summarise the content of all these different files at the same time. If you do, you should take note that the key() function only indexes nodes in the documents that you are currently working on. You can set the current documents using the document() function, but that's another question :)

7.

Count of elements in a document

Sebastian Rahtz

I want a list of all the elements used in my document, with a count of how many times they occur. My Muenchian solution is appended,

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">
<xsl:key name="gis" match="*" use="name()"/>
<xsl:template match="/">
 <xsl:apply-templates/>
<xsl:message>done reading</xsl:message>
<xsl:for-each select="//*[generate-id(.)=generate-id(key('gis',name(.))[1])]">
 <xsl:sort select="name()"/>
     <xsl:value-of select="name(.)"/>
     <xsl:text>  </xsl:text>
      <xsl:value-of select="count(key('gis',name(.)))"/>
<xsl:text>
</xsl:text>
</xsl:for-each>
</xsl:template>

<xsl:template match="text()"/>

</xsl:stylesheet>

8.

Counting the number of elements in a document

Mike Kay

(Saxon solution)

<?xml version="1.0"?> 

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

<!-- Test saxon:distinct() -->

<xsl:output indent="yes"/>

<xsl:variable name="nameexp" select="saxon:expression('name()')"/>
<xsl:variable name="attnames" select="saxon:distinct($allatts, $nameexp)"/>
<xsl:variable name="elnames" select="saxon:distinct($allelements,
$nameexp)"/>
<xsl:variable name="allatts" select="//@*"/>
<xsl:variable name="allelements" select="//*"/>

<xsl:template match="/">

<table>
<tr><td>Element</td><td>Total</td>
<xsl:for-each select="$attnames">
   <td><xsl:value-of select="name()"/></td>
</xsl:for-each>
</tr>
<xsl:for-each select="$elnames">
<tr>
  <xsl:variable name="elname" select="name()"/>
  <td><xsl:value-of select="$elname"/></td>
  <td><xsl:value-of select="count($allelements[name()=$elname])"/></td>
  <xsl:for-each select="$attnames">
     <xsl:variable name="attname" select="name()"/>
     <td><xsl:value-of 
           select="count($allatts[name()=$attname and
name(..)=$elname])"/></td>
  </xsl:for-each>
</tr>
</xsl:for-each>
</table>

</xsl:template>
 
</xsl:stylesheet>

When applied to itself the output is:

<?xml version="1.0" encoding="utf-8"?>
<table xmlns:saxon="http://icl.com/saxon">
   <tr>
      <td>Element</td>
      <td>Total</td>
      <td>version</td>
      <td>name</td>
      <td>select</td>
      <td>match</td>
   </tr>
   <tr>
      <td>xsl:stylesheet</td>
      <td>1</td>
      <td>1</td>
      <td>0</td>
      <td>0</td>
      <td>0</td>
   </tr>
   <tr>
      <td>xsl:variable</td>
      <td>7</td>
      <td>0</td>
      <td>7</td>
      <td>7</td>
      <td>0</td>
   </tr>
   <tr>
      <td>xsl:template</td>
      <td>1</td>
      <td>0</td>
      <td>0</td>
      <td>0</td>
      <td>1</td>
   </tr>
   <tr>
      <td>table</td>
      <td>1</td>
      <td>0</td>
      <td>0</td>
      <td>0</td>
      <td>0</td>
   </tr>
   <tr>
      <td>tr</td>
      <td>2</td>
      <td>0</td>
      <td>0</td>
      <td>0</td>
      <td>0</td>
   </tr>
   <tr>
      <td>td</td>
      <td>6</td>
      <td>0</td>
      <td>0</td>
      <td>0</td>
      <td>0</td>
   </tr>
   <tr>
      <td>xsl:value-of</td>
      <td>4</td>
      <td>0</td>
      <td>0</td>
      <td>4</td>
      <td>0</td>
   </tr>
   <tr>
      <td>xsl:for-each</td>
      <td>3</td>
      <td>0</td>
      <td>0</td>
      <td>3</td>
      <td>0</td>
   </tr>
</table>

In standard XSLT, it's much harder, though no doubt possible.

9.

Counting all nodes

Chris Maden



 >Is the following expression legal?
 >count(//|//@*)
 

No.

 >I've noticed that different processers handle it 
 >differently, the main
 >stumbling block being the "//" by itself.

They should all stumble on it. Have any accepted it?

I've tried reading the XPath spec and the XSLT Programmers Reference (2nd Ed), but I can't arrive at a conclusive decision. They appear to imply that I can't use the // by itself, needing to follow it with a node set (i.e. //* (which won't help, btw, as it doesn't count the text nodes) ), but there are sections which suggest that I can use it as a node set (e.g. AbbreviatedRelativeLocationPath, p354 of above book).

The implications are right. AbbreviatedRelativeLocationPath in the XPath spec is:

RelativeLocationPath '//' Step

and neither Step may not be the null string. To count *all* of the nodes in a document, try count(/descendant-or-self::node() | //@*). That will count the root node itself, and all text, element, PI, comment, and attribute nodes. If you don't want to count the root node itself, try count(//node() | //@*).

10.

Counting words in attributes

Mike Kay



>   <a>
>    <b attr="w1 w3 w6">...</b>
>    <b attr="w2 w12 w3 w7">...</b>
>    ... more <b>s here ...
>   </a>

> when processing an <a> element, i need to calculate the 
> maximum number of whitespace-separated words for any 
> "b/@attr" attribute. so just what you see above, the value 
> would be 4, based on that second <b> child of <a>.

You can count the words without recursive processing:

$x := normalize-space(@attr)
$y := translate(@attr, ' ', '')
$wc := string-length($x) - string-length($y) +1

Ednote: Now isn't that sneaky/clever :-)

11.

Counting nodes efficiently

Dimitre Novatchev



> >  I've been using Jeni's method from the XSLT FAQ to assign  unique 
> > id's to  nodes. In order to speed things up, can anyone think of a 
> > way  that I could  store the running totals for the different nodes, 
> > rather than  having to  call the count() function repeatedly? A 
> > generalized method  would obviously  be the best, so that it could 
> > be applied to any arbitrary set  of nodes,  but I don't know if this 
> > is even possible.
> >  
> >  <xsl:template match="*">
> >     <xsl:variable name="name" select="name()" />
> >     <xsl:element name="{name()}">
> >       <xsl:attribute name="id">
> >         <xsl:value-of select="concat($name, '-',
> >         count(preceding::*[name()= $name]) +
> >         count(ancestor::*[name()= $name]))" />
> >       </xsl:attribute>
> >       <xsl:apply-templates />
> >     </xsl:element>
> >  </xsl:template>

> 3 ways:

> 1.  Create a node-set by selecting all the elements you wish to count 
> and numbering them using position().  You can then query into this 
> node-set using the generate-id() function to get the correct number 
> for the element you're processing.  This only requies one pass of the 
> data so its quite efficient.

> 2.  Write a SAX Filter in java that numbers the elements on their way 
> into the transform.  You can then select this number as if it was 
> already in the data.

> 3.  If you are using saxon, you can substring the value returned from
> generate-id() after the 'e', as the generated id's take form 'dxxeyy'
> where d is the document number and e is the element number.

Just using generate-id() probably solves the original problem.

I will show here one solution to the problem, because it is interesting, simple and uses an important xslt design pattern.

In this and similar problems one can re-use the identity transformation, which processes strictly one node at a time (would a native English speaker, please, suggest a good name? The best I could arrive at was "one node at a time identity transformation"):

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes"/>

  <xsl:template match="@* | node()">
    <xsl:copy>
      <xsl:apply-templates select="@*"/>
      <xsl:apply-templates select="node()[1]"/>
    </xsl:copy>
    <xsl:apply-templates select="following-sibling::node()[1]"/>
  </xsl:template>

</xsl:stylesheet>

This transformation produces the same results as the more well-known identity rule.

The difference is that we now have the finest possible grain-level of controll as every xsl:apply-templates instruction above always selects at most one node.

It is trivial to add parameters and to override the one-at-a-time identity for elements. Thus we finally have:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes"/>
 
  <xsl:template match="@* | node()">
    <xsl:param name="pnAncestors" select="0"/>
    <xsl:param name="pnPreceding" select="0"/>
    <xsl:copy>
      <xsl:apply-templates select="@*"/>
      <xsl:apply-templates select="node()[1]">
        <xsl:with-param name="pnAncestors" 
                        select="$pnAncestors+1"/>
        <xsl:with-param name="pnPreceding"
                        select="$pnPreceding"/>
      </xsl:apply-templates>
    </xsl:copy>
    <xsl:apply-templates select="following-sibling::node()[1]">
      <xsl:with-param name="pnAncestors" 
                      select="$pnAncestors"/>
      <xsl:with-param name="pnPreceding"
                      select="$pnPreceding+1"/>
          
    </xsl:apply-templates>
  </xsl:template>
  
    <xsl:template match="*">
      <xsl:param name="pnAncestors" select="0"/>
      <xsl:param name="pnPreceding" select="0"/>
      
      <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:attribute name="_id">
          <xsl:value-of select=
                      "concat(name(), '_',
                              $pnAncestors, '_', 
                              $pnPreceding 
                              )"/>
        </xsl:attribute>
        <xsl:apply-templates select="node()[1]">
          <xsl:with-param name="pnAncestors" 
                          select="$pnAncestors+1"/>
          <xsl:with-param name="pnPreceding"
                          select="$pnPreceding"/>
        </xsl:apply-templates>
      </xsl:copy>
      <xsl:apply-templates select="following-sibling::node()[1]">
        <xsl:with-param name="pnAncestors" 
                        select="$pnAncestors"/>
        <xsl:with-param name="pnPreceding"
                        select="$pnPreceding+1"/>
            
      </xsl:apply-templates>
      
    </xsl:template>


</xsl:stylesheet>

This transformation is not long, it can be written almost mechanically, it makes exactly one pass over the tree and it has a linear time complexity (checked with inputs of different size).

12.

Select first 5 nodes

Richard Tobi



>I want to make a XPath expression that finds the 5 first nodes that have a 
>attribute "name" thats equals "Tomas". Is it possible?
>
>I suppose it should be something like this:
>//[@name="Tomas"] [position()<6]

Something like that!

You forgot the * after //, but you also have the problem that position() will return the position of the node amongst its matching siblings, not amongst all the matching nodes. You need to parenthesize the first part of the expression to get the effect you want:

  (//*[@name="Tomas"]) [position()<6]

13.

Counting related nodes

Mukul Gandhi



>  given the xml
>  <vs>
>  <ve pos="1"></ve>
>  <ve pos="1.1"></ve>
>  <ve pos="1.1.1"></ve>
>  <ve pos="1.1.1.1"></ve>
>  <ve pos="1.1.1.2"></ve>
>  <ve pos="1.1.1.3"></ve>
>  <ve pos"1.2"></ve>
>  <ve pos="1.2.1"></ve>
>  <<ve pos="1.2.1.1"></ve>
>  <ve pos="2"></ve>
>  <ve pos="2.1"></ve>
>  <ve pos="2.1.1"></ve>
>  <ve pos="2.1.1.1"></ve>
>  </vs>
>  
>  and given that i am starting on a node with pos=1
>  (or 2 or 3....), how do i 
>  count the nodes which have position with 3 dots only
>  (e.g. pos=1.1.2.1?) and 
>  start with the current node position.

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xalan="http://xml.apache.org/xalan"> 
<xsl:output method="xml" version="1.0"
encoding="UTF-8" indent="yes"/> 

<xsl:variable name="startpos">1</xsl:variable>
	
<xsl:template match="/vs"> 
   <xsl:variable name="dots"> 
      <xsl:for-each select="ve"> 
	<xsl:variable name="val"
select="substring(@pos,1,1)"/> 
	<xsl:if test="($val = $startpos) or ($val &gt;
$startpos)"> 
	    <xsl:call-template name="countdots"> 	     
<xsl:with-param name="stringparam" select="@pos"/> 
	      <xsl:with-param name="noofdots" select="0"/> 
	    </xsl:call-template> 
	</xsl:if> 
     </xsl:for-each> 
   </xsl:variable> 
		
   Answer - <xsl:value-of
select="count(xalan:nodeset($dots)/a)" /> 
</xsl:template> 
	
<xsl:template name="countdots"> 
  <xsl:param name="stringparam"/> 
  <xsl:param name="noofdots"/> 
  
  <xsl:variable name="sbst"
select="substring-after($stringparam, '.')"/> 
  <xsl:if test="$sbst != '' "> 
     <xsl:call-template name="countdots"> 
	<xsl:with-param name="stringparam" select="$sbst"/> 
	<xsl:with-param name="noofdots" select="$noofdots +
1"/> 
     </xsl:call-template> 
  </xsl:if> 
  <xsl:if test="$sbst = '' "> 
     <xsl:if test="$noofdots = 3"> 
	<a/> 
     </xsl:if> 
  </xsl:if> 
</xsl:template> 

</xsl:stylesheet> 

At the begining of XSL, a variable is declared to mention the starting position; <xsl:variable name="startpos">1</xsl:variable>

The XSL uses recursion to find the count of the desired nodes. The above XSL creates a tag <a/> each time it encounters a node in original XML having 3 dots (whose position is, on or after the "startpos" value node). A variable "dots" ( declared as -- <xsl:variable name="dots"> )holds these <a/> tags (which is a RTF -- result tree fragment).

I convert the RTF into a nodeset, and calculate the count of <a/> tags, *which is the answer*.

14.

The right way to count?

Wendell Piez


>I have an xsl which is something like this:
>
><root>
><a><b>lots of stuff</b></a>
><a><b>lots of stuff</b></a>
><a><b>lots of stuff</b></a>
><a><b>lots of stuff</b></a>
><a><b>lots of stuff</b></a>
><a><b>lots of stuff</b></a>
><a><b>lots of stuff</b></a>
></root>
>
>and I have a template
>
><xsl:template match="a">
>
><!--Show which element I on using-->
>
><!--display lots of stuff-->
>
></xsl:template>
>
>For the <!--Show which element I on using--> I am trying various 
>different combinations of <xsl:number value="something">

Take the @value attribute off and see what happens. You do not want to provide a value to the xsl:number instruction; without it, it will calculate a value on its own (which is what you want).

Any good XSLT reference book will tell you what the @value, @count, @level and @format attributes on xsl:number will do, but from your description it appears you want the default behavior.

>I have tried counting preceding siblings, position and several other 
>things, and I only ever get a 1.

We'd have to see a little more before we could diagnose why that was happening.

>I would be really grateful if you could tell me What is the right way 
>to show 1,2,3,4,5 and then contents of each 'a´ element within the 
>template.

D'you know how to apply templates?

<xsl:template match="a">
  xsl:number   <xsl:number format="1 "/> <br />
count  (preceding-sibling::a)+1: <xsl:value-of select="count(preceding-sibling::a)+1"/> <br />
position <xsl:value-of select="position()"/>
   <xsl:apply-templates/>
</xsl:template>

output

 xsl:number 1
count (preceding-sibling::a)+1: 1
position 2 xsl:number 2
count (preceding-sibling::a)+1: 2
position 4 xsl:number 3
count (preceding-sibling::a)+1: 3
position 6 xsl:number 4
count (preceding-sibling::a)+1: 4
position 8 xsl:number 5
count (preceding-sibling::a)+1: 5
position 10 xsl:number 6
count (preceding-sibling::a)+1: 6
position 12 xsl:number 7
count (preceding-sibling::a)+1: 7
position 14

15.

Sum. An approach

Michael Kay


> I tried
> select="sum(number(colspec[contains(@colwidth,'*')]/substring-
> before(@colwidth,'*')))" 
> instead, but this does not work either. 

The number() function returns a single number. Summing over a single number obviously isn't going to do any good. You're thrashing around rather than thinking about the problem clearly.

Your problem has the general form:

1) select a set of nodes

2) for each node, compute a number

3) find the sum of the numbers

This is often referred to as the "sum of price times quantity" problem, though in your case the computation in step 2 is slightly different. Let's solve the general problem by saying that in step 2, we want to call a function f(node)->number. Having got a general solution, it's then easy to apply it to your case and to many other similar cases just by changing the function f.

In XPath 2.0 the solution can be written

  sum(for $n in $nodes return f($n))

or more concisely

  sum($nodes/f(.))

You've been offered two solutions for XSLT/XPath 1.0, and I'll add a third.

The first solution is recursion. Cutting out the XSLT noise, the solution can be expressed:

total($nodes):
choose
 when count($nodes)=0
   return 0
 otherwise
   return f($nodes[1]) + total($nodes[position()>1])

The second solution is Dimitre's functional programming approach. This is the one that's closest to the way I expressed the problem above. Note that I described a three-stage approach in which stage 2 involves computing some function. This is a classic higher-order function. The three-step algorithm is a function that takes two arguments: the sequence of nodes, and the function to be computed in step 2. Dimitre's FXSL library allows you to write the solution in this form. It takes some getting used to, but it's the most economical way of solving the problem; and although it involves calling the FXSL library, that's written in pure XSLT 1.0 so it should be completely portable.

The third solution uses the pipeline design pattern mentioned in my mail earlier today. Here you do the job in two steps. The output of the first step is a tree in which the elements carry an attribute that's the computed number (the value of f(node)). The second step uses the sum() function to sum over these attributes.

16.

Count occurences of one string in another

Mukul Gandhi

<?xml version="1.0"?> 
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
 
<xsl:output method="text" /> 
 
<xsl:variable name="string" select="'Hello World.
Hello World. Hello World. Jack and Jill went up the
hill.'" />
<xsl:variable name="token" select="'Hello'" />
 
<xsl:template match="/">
   <xsl:call-template name="countTokens">
     <xsl:with-param name="string" select="$string" />
     <xsl:with-param name="token" select="$token" />
     <xsl:with-param name="result" select="0" />
   </xsl:call-template>
 </xsl:template>
 
<xsl:template name="countTokens">
   <xsl:param name="string" />
   <xsl:param name="token" />
   <xsl:param name="result" />
   
   <xsl:choose>
     <xsl:when test="contains($string, $token)">
       <xsl:call-template name="countTokens">
         <xsl:with-param name="string"
select="substring-after($string, $token)" />
         <xsl:with-param name="token" select="$token"/>
         <xsl:with-param name="result" select="$result + 1"/>
       </xsl:call-template>  
     </xsl:when>
     <xsl:otherwise>
        <xsl:value-of select="$result" />
     </xsl:otherwise>
   </xsl:choose>   
   
</xsl:template>
 
</xsl:stylesheet> 

17.

Count preceding siblings, and cousins

Mike Kay





count(preceding-sibling::*) + count(../preceding-sibling::*/*)


(change "*" to "node()" if you want all nodes, rather than just element
nodes)

18.

Count distinct values

Andrew Welch


> I need to count the distinct <Country> values from this XML and this to be
>  assigned to the 'size'; dattribute of a <Box> element in X3D

>  The XML (fragment example)/////////////////////////////////////////

>  <Artists_by_Countries>

>    <Artist_by_Country>
>        <Location_ID>62</Location_ID>
>        <Artist_ID>212</Artist_ID>
>        <Country>Argentina</Country>
>    </Artist_by_Country>

>     <Artist_by_Country>
>        <Location_ID>4</Location_ID>
>        <Artist_ID>108</Artist_ID>
>        <Country>Australia</Country>
>    </Artist_by_Country>

>     <Artist_by_Country>
>        <Location_ID>4</Location_ID>
>        <Artist_ID>111</Artist_ID>
>        <Country>Australia</Country>
>    </Artist_by_Country>

>    <Artist_by_Country>
>        <Location_ID>12</Location_ID>
>        <Artist_ID>78</Artist_ID>
>        <Country>Germany</Country>
>    </Artist_by_Country>

>  </Artists_by_Countries>

The "Muenchian Method" gets distinct values by ignoring all values unless they are the first occurrence of that value. So to get a distinct list of countries you need to do:

select="Artist_by_Country[generate-id(.) =
generate-id(key('artists-by-country', Country)[1])]"

This looks a bit mad, but it's really simple - the main concept to get is the call to the key:

key('artists-by-country', Country)

The <Country> is passed to the key, say "Australia", which returns two nodes as there are two <Artist_by_Country> elements with a country of Australia.

key('artists-by-country', Country)[1]

The [1] picks the first of the list

generate-id(.) = generate-id(...)

The generate-id(.) = generate-id(...) part is testing to see if the nodes are the same nodes, as in the same individual node in the input tree. Nodes with the same name and value would not pass this test. In 2.0 the "is" operator can be used instead, which is a little clearer.

So altogether:

Artist_by_Country[generate-id(.) =
generate-id(key('artists-by-country', Country)[1])]

For the all the various values of <Country> the above XPath will give the first occurrence of each, which is your list of distinct values.

>                <xsl:attribute name ='size'>
>                                    <xsl:value-of=" KEY COUNT OF DISTINCT
>  COUNTRY VALUES>

I'm not too sure what you want here, but if it's say 2 for "Australia" in your example then you just need:

count(key('artists-by-country', Country))

19.

counting, how it works

Ken Holman



> I've been searching for a solution to what I thought was a simple
> problem. Given the input XML:
>
> ----------------------------------
> <forest>
> <monkey name="Joe" />
> <tree><monkey name="Sam" /></tree>
> <tree><monkey name="George" /></tree>
> <tree><monkey name="Frank" /></tree>
> <tree><treehouse><monkey name="Phil" /></treehouse></tree>
> <tree><monkey name="Hans" /></tree>
> </forest>
> ----------------------------------
>
> I want to get the name of the fifth monkey (Phil). I thought this would do it:
>
> ----------------------------------
> <xsl:template match="forest">
> <xsl:text>The fifth monkey's name is: </xsl:text><xsl:value-of
> select="//monkey[5]/@name"/>
> </xsl:template>
> ----------------------------------
>
> But this doesn't seem to work.

Correct, because you've asked for all occurrences where there are five monkey siblings and you want the name attribute of the fifth one of those.

> It does work fine if all the monkeys
> are children of the root element, but I can't count on that always
> being the case. Can anyone help me with my monkey trouble?

You want the name of the fifth of all monkeys, not all "fifth monkey sibling" elements.

  <xsl:value-of select="(//monkey)[5]/@name"/>
t:\ftemp>type mike.xml
<forest>
<monkey name="Joe" />
<tree><monkey name="Sam" /></tree>
<tree><monkey name="George" /></tree>
<tree><monkey name="Frank" /></tree>
<tree><treehouse><monkey name="Phil" /></treehouse></tree>
<tree><monkey name="Hans" /></tree>
</forest>

t:\ftemp>type mike.xsl
<?xml version="1.0" encoding="US-ASCII"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version="1.0">

<xsl:output method="text"/>

<xsl:template match="forest">
<xsl:text>The fifth monkey's name is: </xsl:text><xsl:value-of
select="(//monkey)[5]/@name"/>
</xsl:template>

</xsl:stylesheet>
t:\ftemp>xslt mike.xml mike.xsl con
The fifth monkey's name is: Phil
t:\ftemp>