Re: [xsl] ordering and iteration problem

Subject: Re: [xsl] ordering and iteration problem
From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx>
Date: Fri, 16 Nov 2001 10:40:18 +0000
Hi Hector,

> How would you modify your solution to accomodate Mark's XML file
> modified as follows:
[snip]
> ..the difference is that now there could be a nested table of
> Circuit Breakers. Using the "key" function in your stylesheet does
> not provide that kind of output. I'm looking to still do recursion
> within a cell to look for a nested table there (N levels down)

If I understand you correctly, each circuit-breaker-panel creates its
own table containing just its child circuit-breaker elements. Some of
the cells in that table might contain other tables for the nested
circuit-breaker-panels.

As you've seen, the keys will access *all* the circuit-breaker
elements, no matter what circuit-breaker-panel they're on, so you need
to change the keys so that you can quickly access only those within a
particular circuit-breaker-panel. Since the circuit-breaker-panel
elements don't have any kind of ID that you could use to identify them
with, you have to make up an ID for them, which you can do with the
generate-id() function.

So I'd adapt the keys to include information about the parent
circuit-breaker-panel of the circuit-breaker, as follows:

<xsl:key name="breakers-by-column" match="b:circuit-breaker"
         use="concat(generate-id(parent::b:circuit-breaker-panel),
                     ':', @column)" />
<xsl:key name="breakers" match="b:circuit-breaker"
         use="concat(generate-id(parent::b:circuit-breaker-panel),
                     @row, ':', @column)" />

If a circuit-breaker-panel was given an ID of 'abc' then you could get
the circuit breaker in the first cell in the first row with:

  key('breakers', 'abc:1:1')

Then I'd change the arrangement of the templates slightly so that the
template matched the circuit-breaker-panel in order to create the
table, and so that it uses the new keys:

<xsl:template match="b:circuit-breaker-panel">
  <!-- store the ID of the panel -->
  <xsl:variable name="panel" select="generate-id()" />
  
  <!-- work out the maximum rows for this table -->
  <xsl:variable name="max-rows">...</xsl:variable>
  <!-- work out the maximum columns for this table -->
  <xsl:variable name="max-cols">...</xsl:variable>
  <!-- store the right number of nodes for the rows in a variable -->
  <xsl:variable name="rows"
                select="$random-nodes[position() &lt;= $max-row]" />
  <!-- store the right number of nodes for the columns in a variable
       -->
  <xsl:variable name="columns"
                select="$random-nodes[position() &lt;= $max-col]" />

  <!-- create the table -->
  <table>
  
    <!-- iterate over the right set of nodes to get the rows -->
    <xsl:for-each select="$rows">
      <!-- store the row number -->
      <xsl:variable name="row" select="position()" />
      <!-- create the row -->
      <tr>

        <!-- iterate over the right set of nodes to get the columns
             -->
        <xsl:for-each select="$columns">
          <!-- store the column number -->
          <xsl:variable name="col" select="position()" />

          <!-- change the current node so that the key works -->
          <xsl:for-each select="$data">
            <!-- identify the relevant circuit breaker -->
            <xsl:variable name="breaker"
               select="key('breakers',
                           concat($panel, ':', $row, ':', $col))" />
            <xsl:choose>
              <!-- if there is one, apply templates to get the table
                   cell -->
              <xsl:when test="$breaker">
                <xsl:apply-templates select="$breaker" />
              </xsl:when>
              <xsl:otherwise>
                <!-- find other breakers that start higher in the
                     column -->
                <xsl:variable name="column-breakers"
                  select="key('breakers-by-column',
                              concat($panel, ':', $col))
                            [@row &lt; $row]" />
                <!-- output an empty cell if there isn't one that
                     overlaps -->
                <xsl:if test="not($column-breakers
                                    [@row + @height > $row])">
                  <td />
                </xsl:if>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:for-each>
        </xsl:for-each>
      </tr>
    </xsl:for-each>
  </table>
</xsl:template>

The template that gives the cell for the circuit breaker also needs to
be different. I don't know whether you still want it to contain the
value of the amps child of the circuit-breaker, but it needs to go on
to process the circuit-breaker-panel child to create the nested table:

<!-- template to give the cell for the circuit breaker -->
<xsl:template match="b:circuit-breaker">
  <td rowspan="{@height}">
    <xsl:value-of select="b:amps" />
    <!-- generate the nested table -->
    <xsl:apply-templates select="b:circuit-breaker-panel" />
  </td>
</xsl:template>

I hope that helps,

Jeni

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


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


Current Thread