xsl-list
[Top] [All Lists]

Re: Using XSL to Translate Repetitive-Node XML to Table with COLSPANs

2004-02-12 15:53:43
Leo,

Looking at your data again, I see you have the harder case:

At 03:49 PM 2/12/2004, you wrote:
                        <data columnid="01" name="Actual"/>
                        <data columnid="11" name="Budget"/>
                        <data columnid="21" name="Fav/(Unfav) $"/>
                        <data columnid="31" name="Fav/(Unfav) %"/>
                        <data columnid="41" name="Actual"/>
                        <data columnid="51" name="Budget"/>
                        <data columnid="61" name="Fav/(Unfav) $"/>
                        <data columnid="71" name="Fav/(Unfav) %"/>

...so things get trickier.

Comparing this with the last template I posted:

<xsl:template match="data">
  <xsl:variable name="thisname" select="@name"/>
  <!-- first be sure we emit cells only for the first data element
       with a given name -->
  <xsl:if test="not($thisname = preceding-sibling::data/@name)">
<!-- the colspan is the count of data children of the parent with the same name -->
    <td colspan="{count(../data[(_at_)name = $thisname)}">
      <xsl:apply-templates select="@name"/>
<!-- the built-in default template for attributes will emit its value -->
    </td>
  </xsl:if>
</xsl:template>

There are two problems. First, we emit cells not for the first data element with a given @name, but for any data element that doesn't have an immediately preceding sibling with the same name. That's not so hard:

<xsl:if test="not($thisname = preceding-sibling::data[1]/@name)">

The trickier bit is counting the following siblings with the same name that belong with this one. Really tricky, since the preceding-sibling and following-sibling axes look at *all* the siblings. Extra points to any XSLTer who can suggest how to do this count without a counter! (You're right, we don't like counters.)

We can implement a counter as follows, using a recursive "tiptoe forward" technique:

<xsl:template match="data" mode="gimme-count">
  <xsl:parameter name="so-far" select="1"/>
  <xsl:choose>
    <xsl:when test="not(following-sibling::data[1]/@name = current()/@name)">
      <!-- if our next data sibling doesn't have the same name, we're done -->
      <xsl:value-of select="$so-far"/>
    </xsl:when>
    <xsl:otherwise>
<!-- if it does, we apply this same template to it, incrementing our counter --> <xsl:apply-templates select="following-sibling::data[1]" mode="gimme-count">
        <xsl:with-param name="so-far" select="$so-far + 1"/>
      </xsl:apply-templates>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

You would invoke this by binding it to a variable in your template matching the data element:

<xsl:variable name="count">
  <xsl:apply-templates select="." mode="gimme-count"/>
</xsl:variable>

so, all together:

<xsl:template match="data">
  <xsl:if test="not(@name = preceding-sibling::data[1]/@name)">
    <xsl:variable name="count">
      <xsl:apply-templates select="." mode="gimme-count"/>
    </xsl:variable>
   <td colspan="{$count}">
      <xsl:apply-templates select="@name"/>
<!-- the built-in default template for attributes will emit its value -->
    </td>
  </xsl:if>
</xsl:template>

Note: untested!

I hope it works ...

Cheers,
Wendell



======================================================================
Wendell Piez                            
mailto:wapiez(_at_)mulberrytech(_dot_)com
Mulberry Technologies, Inc.                http://www.mulberrytech.com
17 West Jefferson Street                    Direct Phone: 301/315-9635
Suite 207                                          Phone: 301/315-9631
Rockville, MD  20850                                 Fax: 301/315-8285
----------------------------------------------------------------------
  Mulberry Technologies: A Consultancy Specializing in SGML and XML
======================================================================


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