RE: [xsl] local extremums

Subject: RE: [xsl] local extremums
From: "Lars Huttar" <lars_huttar@xxxxxxx>
Date: Wed, 19 Mar 2003 12:22:39 -0600
Ragulf wrote:

> If there is something wrong with this code, I would like to 
> hear it myself.
>
> <xsl:template
>   match="Range[number(@Cnt)&gt;number(./previous-sibling/@Cnt) and
>   number(@Cnt)&lt;value-of(./next-sibling/@Cnt)]">
>   <xsl:call-template name="IsLocalMaximum"/>
> </xsl:template>
> 
> <xsl:template
>   match="Range[not(number(@Cnt)&gt;number(./previous-sibling/@Cnt)
>   and number(@Cnt)&lt;value-of(./next-sibling/@Cnt))]">
>   <xsl:choose>
>   [snip...]

A few comments...

1) Since you're looking for local maxima, I think that instead
 of Cnt1 > Cnt0 and Cnt1 < Cnt2 (pardon my simplified references),
 you want Cnt1 > Cnt0 and Cnt1 > Cnt2 (note the second comparison
 changes the < to >).  In other words you want to match Range elements
 whose @Cnt is greater than that of both its adjacent siblings, not
 between them.

2) A style/maintainability/performance issue...
 Since the second template matches every Range element not matched
 by the first, you don't need to include that huge complex [not(...)]
 predicate.
 Just use <xsl:template match="Range">, which will by default have
 a lower priority than the first template, and will "catch" every
 Range element the first one doesn't match.

3) "previous-sibling", without the ::, means "a child element named
  'previous-sibling'".  Obviously this isn't what you want.
  Also, the axis is called preceding-sibling::.
  I would rework your match pattern as:
   
    match="Range[@Cnt &gt; preceding-sibling::Range[1]/@Cnt and
                 @Cnt &gt; following-sibling::Range[1]/@Cnt]"

(Note, number() is not really necessary as the < > operators convert
their operands to numbers; all number() does here is make sure that
only the *first*node* of its argument is considered.  I used [1]
to take care of that aspect since it's clearer.)


Evgenia, here's a stylesheet that I think does what you want.
It outputs a table showing each Range as a row, with three columns
for the three attributes.  The Ranges are in document order.
Each Range that is a local maximum (i.e. @Cnt is greater than that of
the row immediately before or after) is styled in bold.
I treated first and last Range elements as local maxima if
they were greater than their only adjacent sibling.
The table is divided into countries with a row-wide "Country {@ID}"
cell.

Hopefully this is the kind of thing you were looking for...

Blessings,
Lars



<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
                version="1.0">

  <xsl:template match="/">
    <html>
      <head>
        <style type="text/css">
          .localmax {
             font-weight: bold;
          }

          td {
             text-align: right;
          }
        </style>
      </head>
      <body>
        <xsl:apply-templates select="PoketTourList"/>
      </body>
    </html>
  </xsl:template>

  <xsl:template match="PoketTourList">
    <table border="1">
      <xsl:apply-templates select="Country" />
    </table>
  </xsl:template>

  <xsl:template match="Country">
    <tr><th colspan="3">Country <xsl:value-of select="@ID" /></th></tr>
    <tr>
      <th>@PriceFrom</th>
      <th>@PriceTo</th>
      <th>@Cnt</th>
    </tr>
    <xsl:apply-templates select="Range" />
  </xsl:template>

  <xsl:template match="Range">
    <xsl:variable name="IsLocalMax"
      select="(position() = 1 or @Cnt &gt; preceding-sibling::Range[1]/@Cnt)
              and (position() = last()
                   or @Cnt &gt; following-sibling::Range[1]/@Cnt)" />
    <tr>
      <xsl:if test="$IsLocalMax">
        <xsl:attribute name="class">localmax</xsl:attribute>
      </xsl:if>
      <td><xsl:value-of select="@PriceFrom" /></td>
      <td><xsl:value-of select="@PriceTo" /></td>
      <td><xsl:value-of select="@Cnt" /></td>
    </tr>
  </xsl:template>

</xsl:stylesheet>


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


Current Thread