xsl-list
[Top] [All Lists]

RE: [xsl]: XSL processes XML incorrectly when uneven number of values returned in array elements

2006-03-10 07:24:12
I think I've solved your problem. I used Saxon 8.7J to process the XSLT.

I cut down your XML so that I could work only on the part that was presenting 
the challenge. I retained the orginal root element, but excised all other 
elements but <free-xml> and its children. When you adapt my solution to the 
original document you will want to pay attention to the XPath expressions to 
insure that you point to the correct elements.

The entire stylesheet is posted at the end of this message, but I will 
highlight sections of it here for exposition.

The first problem we had to solve was how to handle the irregular and unknown 
number of rows to be produced. Since all variables in XSLT are static, the 
correct approach was to call a named template recursively.

First I calculate the maximum number of rows by creating a variable that 
contains an XML fragment holding the set of <optionListx> elements sorted in 
descending order by the count of the number of <option> children.:

  <xsl:template match="free-xml">

    <xsl:variable name="list-with-max-option-children">
      <xsl:for-each select="*">
        <xsl:sort select="count(option)" data-type="number" order="descending" 
/>
        <xsl:copy-of select="." />
      </xsl:for-each>
    </xsl:variable>
    ... The balance of the template goes here ...
  </xsl:template >

Then, by taking the count of the <option> children in the first <optionListx> 
element in the sorted fragment, I know the maximum number of rows to be 
produced and we begin the recursive calls to the template that will produce the 
rows of your table.

  <xsl:template match="free-xml">

    <xsl:variable name="list-with-max-option-children">
      <xsl:for-each select="*">
        <xsl:sort select="count(option)" data-type="number" order="descending" 
/>
        <xsl:copy-of select="." />
      </xsl:for-each>
    </xsl:variable>
    
    <table>
      <xsl:call-template name="table-row">
        <xsl:with-param name="max-rows" 
select="count($list-with-max-option-children/*[1]/option)" />
        <xsl:with-param name="current-row" select="1" />
      </xsl:call-template>
    </table>
  </xsl:template>

Note that we pass two parameters, "max-rows" which sets the stopping point for 
the recursive calls to the template named "table-rows", and "current-row" that 
selects the position of the set of <option> elements whose values will be 
placed in the <td> elements in the row.

Most of the work is done in the template named "table-row":

<xsl:template name="table-row">
  <xsl:param name="max-rows" />
  <xsl:param name="current-row" />
    
       ... The first thing the template does is check to see if the maximum 
number of rows has been reached, it aborts if this is true. ...

  <xsl:if test="$current-row &lt;= $max-rows">
    <tr>
      <xsl:choose>
        <xsl:when test="/enquiry-data/free-xml/*[1]/option[$current-row]">
          <td><xsl:value-of 
select="/enquiry-data/free-xml/*[1]/option[$current-row]" /></td>
        </xsl:when>
        <xsl:otherwise><td /></xsl:otherwise>
      </xsl:choose>

.. Most of the template looks like this. There are known to be exactly seven 
columns (which translates to seven <td> elements in each row, so the bulk of 
the template consists of <xsl:choose> elements like this, one for each column. 

 The value in the first set of square brackets here, 
"*[1]/option[$current-row]" increments for each <xsl:choose> so you get values 
from 1 through 7. 

The test looks to see if there is an <option> element at that position, and if 
so, it places its value in the <td>, otherwise an empty <td /> is emitted. 

After processing all <xsl:choose> statments we make a recursive call to the 
same template. ...

 </tr>
      <xsl:call-template name="table-row">
        <xsl:with-param name="max-rows" select="$max-rows" />
        <xsl:with-param name="current-row" select="$current-row + 1" />
      </xsl:call-template>

    ... The trick here is to add one to the value of $current-row and pass it 
along to the next call of the template. Although this may look like we are 
changing the value of a variable (not permitted in XSLT), we really aren't. We 
are creating a new variable with the same name in a different context.

I've been looking for a new job this week so I've had plenty of time on my 
hands to work your problem. Not everyone can spend this kind of time on a 
challenge, so the next time you have one, you may or may not get this much 
attention. Good luck.

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

  <xsl:template match="/">
    <xsl:apply-templates />
  </xsl:template>

  <xsl:template match="enquiry-data">
    <xsl:apply-templates />
    </xsl:template>

  <xsl:template match="free-xml">

    <xsl:variable name="list-with-max-option-children">
      <xsl:for-each select="*">
        <xsl:sort select="count(option)" data-type="number" order="descending" 
/>
        <xsl:copy-of select="." />
      </xsl:for-each>
    </xsl:variable>
    
    <table>
      <xsl:call-template name="table-row">
        <xsl:with-param name="max-rows" 
select="count($list-with-max-option-children/*[1]/option)" />
        <xsl:with-param name="current-row" select="1" />
      </xsl:call-template>
    </table>
  </xsl:template>
  
  <xsl:template name="table-row">
    <xsl:param name="max-rows" />
    <xsl:param name="current-row" />
    
    <xsl:if test="$current-row &lt;= $max-rows">
      <tr>
        <xsl:choose>
          <xsl:when test="/enquiry-data/free-xml/*[1]/option[$current-row]">
            <td><xsl:value-of 
select="/enquiry-data/free-xml/*[1]/option[$current-row]" /></td>
          </xsl:when>
          <xsl:otherwise><td /></xsl:otherwise>
        </xsl:choose>
        <xsl:choose>
          <xsl:when test="/free-xml/*[2]/option[$current-row]">
            <td><xsl:value-of 
select="/enquiry-data/free-xml/*[2]/option[$current-row]" /></td>
          </xsl:when>
          <xsl:otherwise><td /></xsl:otherwise>
        </xsl:choose>
        <xsl:choose>
          <xsl:when test="/enquiry-data/free-xml/*[3]/option[$current-row]">
            <td><xsl:value-of 
select="/enquiry-data/free-xml/*[3]/option[$current-row]" /></td>
          </xsl:when>
          <xsl:otherwise><td /></xsl:otherwise>
        </xsl:choose>
        <xsl:choose>
          <xsl:when test="/enquiry-data/free-xml/*[4]/option[$current-row]">
            <td><xsl:value-of 
select="/enquiry-data/free-xml/*[4]/option[$current-row]" /></td>
          </xsl:when>
          <xsl:otherwise><td /></xsl:otherwise>
        </xsl:choose>
        <xsl:choose>
          <xsl:when test="/enquiry-data/free-xml/*[5]/option[$current-row]">
            <td><xsl:value-of 
select="/enquiry-data/free-xml/*[5]/option[$current-row]" /></td>
          </xsl:when>
          <xsl:otherwise><td /></xsl:otherwise>
        </xsl:choose>
        <xsl:choose>
          <xsl:when test="/enquiry-data/free-xml/*[6]/option[$current-row]">
            <td><xsl:value-of 
select="/enquiry-data/free-xml/*[6]/option[$current-row]" /></td>
          </xsl:when>
          <xsl:otherwise><td /></xsl:otherwise>
        </xsl:choose>
        <xsl:choose>
          <xsl:when test="/enquiry-data/free-xml/*[7]/option[$current-row]">
            <td><xsl:value-of 
select="/enquiry-data/free-xml/*[7]/option[$current-row]" /></td>
          </xsl:when>
          <xsl:otherwise><td /></xsl:otherwise>
        </xsl:choose>
      </tr>
      <xsl:call-template name="table-row">
        <xsl:with-param name="max-rows" select="$max-rows" />
        <xsl:with-param name="current-row" select="$current-row + 1" />
      </xsl:call-template>
    </xsl:if>
  </xsl:template>
  </xsl:stylesheet>

-- 
Charles Knell
cknell(_at_)onebox(_dot_)com - email


--~------------------------------------------------------------------
XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list
To unsubscribe, go to: http://lists.mulberrytech.com/xsl-list/
or e-mail: <mailto:xsl-list-unsubscribe(_at_)lists(_dot_)mulberrytech(_dot_)com>
--~--