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

Subject: RE: RE: [xsl]: XSL processes XML incorrectly when uneven number of values returned in array elements
From: "Cave, Neil" <Neil.Cave@xxxxxxxxxxxxxx>
Date: Thu, 16 Mar 2006 14:47:19 +0100
Thanks again Charles

Will check it out

-----Original Message-----
From: cknell@xxxxxxxxxx [mailto:cknell@xxxxxxxxxx]
Sent: 16 March 2006 03:22 PM
To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
Subject: RE: RE: [xsl]: XSL processes XML incorrectly when uneven number
of values returned in array elements

Check out the extension functions to XSLT 1.0 here
http://www.exslt.org/exsl/exsl.html. I'm not intimately familiar with
the library, but I expect you could use the node-set() function to
replicate what I did with the variable in the XSLT 2.0 stylesheet I
offered. Many users on this list know far more about EXSLT than I do. In
fact, I'll be surprised if the driving force behind it doesn't chime in
soon.

I checked, and Xalan will work with EXSLT.
--
Charles Knell
cknell@xxxxxxxxxx - email



-----Original Message-----
From:     Cave, Neil <Neil.Cave@xxxxxxxxxxxxxx>
Sent:     Thu, 16 Mar 2006 14:11:40 +0100
To:       <xsl-list@xxxxxxxxxxxxxxxxxxxxxx>
Subject:  RE: [xsl]:  XSL processes XML incorrectly when uneven number
of values returned in array elements

<Msg v.2>

Looks like the front-end application is tied exclusively to Xalan.
I'll be unable to use <change>"Saxon"</change>

<thanks/>

-----Original Message-----
From: cknell@xxxxxxxxxx [mailto:cknell@xxxxxxxxxx]
Sent: 10 March 2006 04:24 PM
To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
Subject: RE: [xsl]: XSL processes XML incorrectly when uneven number of
values returned in array elements

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 <= $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 <= $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@xxxxxxxxxx - email

Current Thread