Re: [xsl] Finding the maximun number of nodes

Subject: Re: [xsl] Finding the maximun number of nodes
From: Dimitre Novatchev <dnovatchev@xxxxxxxxx>
Date: Fri, 5 Jan 2001 03:43:05 -0800 (PST)
Thanks to Jeni and Mike for pointing out the error in my "solution".

Now here's what seems to be still the simplest of the proposed so far
solutions (and at last it works... :o) ):

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

<xsl:template match="/">
	
  <xsl:variable name="maxNumbers">
    <xsl:for-each select="//table[@ID='2']/tr">
      <xsl:if test="count(//table[@ID='2']/tr[count(td) >
count(current()/td)]) = 0">
        <xsl:value-of select="count(td)"/>:
      </xsl:if>
    </xsl:for-each>
  </xsl:variable>
	
  <xsl:value-of select="substring-before($maxNumbers,':')"/>
</xsl:template>
</xsl:stylesheet>

Dimitre.



Jeni Tennison wrote:

Dimitre wrote:
> Michael Lee wrote:
>> Therefore, I must be able to determine the maximum number of
>> cells in the rows and use it as the value for the "columns"
>> attribute.
>
> Use:
>
> <xsl:key name="numCells" match="tr" use="count(td)"/>
>
> <xsl:template name="maxCols">
>   <xsl:for-each select="//tr[not(key('numCells', count(td) + 1))]">
>             <xsl:value-of select="count(td)"/>
>   </xsl:for-each>               
> </xsl:template>

This only works if there are now rows that have more than one cell
more than any other.  In these cases, the key won't return any nodes
for some of the shorter rows, and therefore the number of cells in
these rows will be given.  For example:

<table>
   <tr><td>(1,1)</td></tr>
   <tr><td>(2,1)</td><td>(2,2)</td><td>(2,3)</td></tr>
   <tr><td>(3,1)</td><td>(3,2)</td></tr>
  
<tr><td>(4,1)</td><td>(4,2)</td><td>(4,3)</td><td>(4,4)</td><td>(4,5)</td></tr>
</table>

means that the maxCols template returns '35' - a '3' from the length
of the second row, and a '5' from the length of the fourth.

Better is:

<xsl:template name="maxCols">
  <xsl:for-each select="//tr">
    <xsl:sort select="count(td)" order="descending" />
    <xsl:if test="position() = 1">
      <xsl:value-of select="." />
    </xsl:if>
  </xsl:for-each>
</xsl:template>

This sorts all the rows by the number of cells they have in them, in
descending order, then selects the first from that list (the one with
the most cells) and tells you how many cells there are in it.

Alternatively you can step through the rows recursively.  This is more
efficient if the table is a long one.  There are lots of ways to step
through recursively.

Here's one that applies templates to the first row in a 'maxCols' mode.
Within the template, if a row finds a following row that has more
cells in it, then it applies templates to that row in 'maxCols' mode.
If it can't find one with more cells in, then it knows its the
maximum, and tells you how many cells its got.

<xsl:template name="maxCols">
  <xsl:apply-templates select="//tr[1]" mode="maxCols" />
</xsl:template>

<xsl:template match="tr" mode="maxCols">
  <xsl:variable
    name="next"
    select="following-sibling::tr[count(td) &gt;
                                  count(current()/td)][1]" />
  <xsl:choose>
    <xsl:when test="$next">
      <xsl:apply-templates select="$next" mode="maxCols" />
    </xsl:when>
    <xsl:otherwise><xsl:value-of select="count(td)" /></xsl:otherwise>
  </xsl:choose>
</xsl:template>

In this version, again the templates are applied to only the first
row in the table.  Each row works out the maximum of the rest of the
rows by applying templates to the next row in the table.  If this
maximum is more than the number of cells in it, then it tells you the
maximum from the rest of the rows; if it has more cells itself, then
it tells you how many cells it has.

<xsl:template name="maxCols">
  <xsl:apply-templates select="//tr[1]" mode="maxCols" />
</xsl:template>

<xsl:template match="tr" mode="maxCols">
  <xsl:variable name="max">
     <xsl:apply-templates select="following-sibling::tr[1]"
mode="maxCols" />
  </xsl:variable>
  <xsl:choose>
    <xsl:when test="$max &gt; count(td)">
       <xsl:value-of select="$max" />
    </xsl:when>
    <xsl:otherwise><xsl:value-of select="count(td)" /></xsl:otherwise>
  </xsl:choose>
</xsl:template>

This version uses a parameter to store the rows that have to be looked
at.  This is set to all the rows at the beginning.  If there's only
one row, it tells you how many cells there are in that row.  If there
are more, it works out the maximum number of cells in all the rows
aside from the first one (by calling itself with a parameter with all
the rows aside from the first), and then compares that number with the
number of cells in the first row in the list.  If the first row in the
list has more cells, it tells you that number.  If the maximum from
the rest of the rows was more, it tells you that.

<xsl:template name="maxCols">
  <xsl:param name="rows" select="//tr" />
  <xsl:choose>
     <xsl:when test="count($rows) = 1">
        <xsl:value-of select="count(td)" />
     </xsl:when>
     <xsl:otherwise>
        <xsl:variable name="max">
           <xsl:call-template name="maxCols">
              <xsl:with-param name="rows" select="$rows[position() &gt;
1]" />
           </xsl:call-template>
        </xsl:variable>
        <xsl:choose>
          <xsl:when test="$max &gt; count($rows[1]/td)">
             <xsl:value-of select="$max" />
          </xsl:when>
          <xsl:otherwise>
             <xsl:value-of select="count($rows[1]/td)" />
          </xsl:otherwise>
        </xsl:choose>
     </xsl:otherwise>
  </xsl:choose>
</xsl:template>

Which of these is best depends quite a lot on the optimisations that
are built into your XSLT processor.

I hope that helps,

Jeni

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




__________________________________________________
Do You Yahoo!?
Yahoo! Photos - Share your holiday photos online!
http://photos.yahoo.com/

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


Current Thread