xarababe
xarababe

Reputation: 49

count how many tags follow a parent tag in xslt

I have to write an XSLT document which can be used with any XML document and display the results in an HTML table. The columns of the table all depend on how many tags there are in the XML document. I was trying to use

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

to count the number of tags.

But I want that if for instance i have:

  <film>
      <title></title>
      <year></year>
      <actor></actor>
  </film>
  <film>
      <title></title>
      <year></year>
      <actor></actor>
  </film>

the result would be 3 not 6 as it currently being displayed. Any solutions to this problem please?

Upvotes: 2

Views: 1858

Answers (3)

Michael Kay
Michael Kay

Reputation: 163322

Your document actually contains 16 tags: 8 start tags and 8 end tags. So I suspect that "counting the tags" is not your real requirement. Since your terminology is incorrect, we can only guess your true requirement. I suspect it might be to make a table with one row per distinct element name, containing the count of the number of elements with that name. This would be:

<table>
  <xsl:for-each-group select="//*" group-by="name()">
    <tr>
      <td><xsl:value-of select="current-grouping-key()"/></td>
      <td><xsl:value-of select="count(current-group()"/></td>
    </tr>
  </xsl:for-each-group>
</table>

Upvotes: 0

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243449

I. Supposing:

  1. That you had a well-formed XML document like:

. . . .

<films>
  <film>
    <title/>
    <year/>
    <actor/>
  </film>
  <film>
    <title/>
    <year/>
    <actor/>
  </film>
</films>

and:

.2. The film elements all have the same number of same-named columns, then you simply need:

 count(/*/*[1]/*)

This produces the number of children elements of the first child -element of the top element of the XML document:

3

II. Another possibility: different film elements have different number of children and one of them has all children that determine the total number of columns for the table.

<films>
  <film>
    <title/>
    <year/>
    <actor/>
  </film>
  <film>
    <title/>
    <year/>
    <actor/>
    <rating/>
    <director/>
  </film>
  <film>
    <title/>
    <year/>
    <actor/>
    <rating/>
  </film>
</films>

Then, using XPath 2.0, use:

max(/*/*/count(*))

This produces the maximum number of children that a child of the top element of the XML document has:

5

III. Finally, if it is possible that none of the film elements contain all the elements for the columns, then use (what ABach already proposed):

count(distinct-values(/*/*/*/name()))

Upvotes: 1

ABach
ABach

Reputation: 3738

I. XSLT 1.0/XPath 1.0

I don't believe there is a way to do this with pure XPath 1.0 (since you are looking for distinct element names that are, I assume, not in any particular order [at any rate, I don't like to rely on there being some sort of order]).

That said, you can use a key-based XSLT 1.0 solution to solve the problem.

When this XSLT 1.0 document:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output omit-xml-declaration="no" indent="yes" method="text"/>
  <xsl:strip-space elements="*"/>

  <xsl:key name="kFilmChildren" match="film/*" use="name()"/>

  <xsl:template match="/">
    <xsl:value-of
      select="count(//film/*[
                generate-id() = 
                generate-id(key('kFilmChildren', name())[1])
      ])"/>
  </xsl:template>
</xsl:stylesheet>

...is applied against your sample XML (wrapped in a root element):

<films>
  <film>
    <title/>
    <year/>
    <actor/>
  </film>
  <film>
    <title/>
    <year/>
    <actor/>
  </film>
</films>

...the wanted result is produced:

3

If we apply the same stylesheet against a slightly modified XML:

<films>
  <film>
    <title/>
    <year/>
    <actor/>
    <test/>
  </film>
  <film>
    <title/>
    <year/>
    <actor/>
    <test/>
    <test2/>
  </film>
</films>

...again, the correct answer is produced:

5

NOTE: given that you did not show a complete document, I made up for that lacking knowledge by using the // expression. That, however, can be an expensive operation near the root of very large trees; you would do well to make yours more specific.


II. XSLT 2.0/XPath 2.0

Pure XPath 2.0 can be used to obtain the correct answer by utlizing the distinct-values expression:

count(distinct-values(//film/*/name()))

XSLT Verification:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
  <xsl:output omit-xml-declaration="no" indent="yes" method="text" />
  <xsl:strip-space elements="*" />

  <xsl:template match="/">
     <xsl:value-of select="count(distinct-values(//film/*/name()))" />
  </xsl:template>

</xsl:stylesheet>

Result:

3

Upvotes: 0

Related Questions