Wrox Programmer Forums
Go Back   Wrox Programmer Forums > XML > XSLT
|
XSLT General questions and answers about XSLT. For issues strictly specific to the book XSLT 1.1 Programmers Reference, please post to that forum instead.
Welcome to the p2p.wrox.com Forums.

You are currently viewing the XSLT section of the Wrox Programmer to Programmer discussions. This is a community of software programmers and website developers including Wrox book authors and readers. New member registration was closed in 2019. New posts were shut off and the site was archived into this static format as of October 1, 2020. If you require technical support for a Wrox book please contact http://hub.wiley.com
 
Old September 8th, 2006, 12:02 PM
Friend of Wrox
 
Join Date: Oct 2003
Posts: 290
Thanks: 24
Thanked 0 Times in 0 Posts
Default UPDATE PARAMETER OR VARIABLE

I came up against a strange problem. How can I assign a value to a variable or parameter in a loop?

I need to check the value of a parameter or variable to decide whether to write a Cell or not like this:

<xsl:if test="$writeCell != 'no'">
    <Cell ss:MergeDown="number({$colgrps}-1)"/>
</xsl:if>

I only need to write it the first time in the loop(recursion). But when I go to the <xsl:otherwise> section of my code I need to pass a yes value so that the Col template can write this Cell instead of the curent template.

How can I do that in xslt 1 ?

Cheers

XSLT

<xsl:template match="ColGrp" mode="Header">
<xsl:param name="depth"/>

<xsl:if test=" ColGrp ">
  <xsl:param name="writeCell select="'yes'"/>
  <Row>

  <xsl:if test="$writeCell='yes'">
     <Cell ss:MergeDown="number({$colgrps}-1)"/>
  </xsl:if>

  <xsl:for-each select="//ColGrp[count(ancestor::ColGrp)=$depth]">
    <Cell ss:MergeAcross="{count(.//Col)*$msrs}" ss:StyleID="s24">
          <xsl:value-of select="@heading"/>
    </Cell>
  </xsl:for-each>
 </Row>
</xsl:if>
<xsl:choose>
 <xsl:when test="ColGrp">
   <xsl:apply-templates select="ColGrp[1]" mode="Header">
     <xsl:with-param name="depth" select="$depth+1"/>
     <xsl:with-param name="writeCell " select="'no'"/>
   </xsl:apply-templates>
 </xsl:when>
 <xsl:otherwise>
   <xsl:apply-templates select="Col[1]" mode="colHead">
     <xsl:with-param name="writeCell" select="'yes'"/>
   </xsl:apply-templates>
 </xsl:otherwise>
</xsl:choose>
</xsl:template>
 
Old September 8th, 2006, 12:12 PM
mhkay's Avatar
Wrox Author
 
Join Date: Apr 2004
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
Default

XSLT is a declarative or functional language, not a procedural language, so there is no defined order of execution, and therefore no update of variables. Seems strange at first and it requires a different way of thinking about your problem, but you end up writing much better code as a result.

Questions like this always remind me of the COBOL programmers who used to ask "how do I achieve this without a GOTO statement"? Once you start thinking in a declarative functional way, the question never occurs to you.

Rather than looking at your procedural code and trying to reverse engineer it, I would suggest you try describing your problem: what is the output to be, as a function of the input? Try to describe the relationship of one to the other without describing a procedure for doing the work. Then you should find that this description translates very naturally into a functional program.



Michael Kay
http://www.saxonica.com/
Author, XSLT Programmer's Reference and XPath 2.0 Programmer's Reference
 
Old September 8th, 2006, 03:37 PM
Friend of Wrox
 
Join Date: Oct 2003
Posts: 290
Thanks: 24
Thanked 0 Times in 0 Posts
Default

thanks again Michael,

Sorry if I did not explain the problem well. Also, I sort of understand what you are saying when you mention that xslt is a declarative or functional language but would appreciate if you could clarify that to me.

With that in mind I tried to use lots of templates in the stylesheet instead of having just one template with lots of <for-each> loops. This is what I have done:

This template is the start. It calls the Columns template like this: <xsl:apply-templates select="Columns"/>

<xsl:template match="Report">
  <Workbook>
   <xsl:variable name="colspanHead" select="number($msrs+$cols)"/>
   <xsl:variable name="rowspanFirstCell" select="number($msrs)-1"/>
   <Worksheet>
   <xsl:attribute name="ss:Name"><xsl:value-of select="'Audit'"/></xsl:attribute>
   <Table x:FullColumns="1" x:FullRows="1">
      <xsl:apply-templates select="Columns"/>
   </Table>
  </Worksheet>
</Workbook>
</xsl:template>

This template calls the ColGrp template with mode = "header" and passes a parameter depth. Most of the time this parameter will be 1.


<xsl:template match="Columns">
  <xsl:apply-templates select="ColGrp[1]" mode="Header">

  <xsl:with-param name="depth">
  <xsl:choose>
   <xsl:when test="$axisHeads='true'">
    <xsl:value-of select="0"/>
   </xsl:when>
   <xsl:otherwise>
     <xsl:value-of select="1"/>
   </xsl:otherwise>
</xsl:choose>
</xsl:with-param>
</xsl:apply-templates>
</xsl:template>

Here is where I will have to decide whether to add the first Cell or not.
first I check to see if I have a ColGrp child node <xsl:if test=" ColGrp "> then
I have to decide whether to add a Cell with a rowspan or not. The test should be something like this:
 <xsl:if test="insertCellWithRowSpan=true">
      <Cell ss:MergeDown="number({$colgrps}-1)"/>
 </xsl:if>

Then I check if I have another ColGrp child node <xsl:when test="ColGrp">
and call the same template again
<xsl:apply-templates select="ColGrp[1]" mode="Header">
<xsl:with-param name="depth" select="$depth+1"/>
<xsl:with-param name="insertCellWithRowSpan"/>

</xsl:apply-templates>

But this time I cannot output the first Cell with the rowspan again and I do not know how to achieve that. Also, if this test is not true <xsl:when test="ColGrp">
I have to call the Col template passing a parameter so that I can output the first Cell in that template.


<xsl:template match="ColGrp" mode="Header">
  <xsl:param name="depth"/>

  <xsl:param name="insertCellWithRowSpan"/>

  <xsl:if test=" ColGrp ">
  <Row>
    <xsl:if test="insertCellWithRowSpan='yes'">
      <Cell ss:MergeDown="number({$colgrps}-1)"/>
    </xsl:if>

    <xsl:for-each select="//ColGrp[count(ancestor::ColGrp)=$depth]">
    <Cell ss:MergeAcross="{count(.//Col)*$msrs}" ss:StyleID="s24">
      <xsl:value-of select="@heading"/>
    </Cell>
    </xsl:for-each>
</Row>
</xsl:if>
<xsl:choose>
<xsl:when test="ColGrp">
<xsl:apply-templates select="ColGrp[1]" mode="Header">
  <xsl:with-param name="depth" select="$depth+1"/>
  <xsl:with-param name="insertCellWithRowSpan" select="'no'"/>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:apply-templates select="Col[1]" mode="colHead">
  <xsl:with-param name="insertCellWithRowSpan" select="'yes'"/>
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:template>



<xsl:template match="Col" mode="colHead">
  <xsl:param name="insertCellWithRowSpan"/>
  <xsl:variable name="countRowSpan">
     <xsl:value-of select="number($msrs)-1"/>
  </xsl:variable>
<Row>
<xsl:for-each select="ancestor::Columns//Col">
  <xsl:if test="position()=1 and $insertCellWithRowSpan='yes'">
    <Cell ss:MergeDown="{$colgrps}"/>
  </xsl:if>
    <Cell ss:MergeAcross="{$countRowSpan}" ss:StyleID="s24">
      <xsl:value-of select="@heading"/>
    </Cell>
</xsl:for-each>
</Row>
<Row>
<xsl:for-each select="//Col">
<xsl:apply-templates select="//Measures">
  <xsl:with-param name="pos" select="position()"/>
</xsl:apply-templates>
</xsl:for-each>
</Row>
</xsl:template>


<xsl:template match="//Measures">
 <xsl:param name="pos"/>
  <xsl:choose>
   <xsl:when test="position()=1 and $pos=1">
     <Cell ss:Index="2" ss:StyleID="s27">
        <xsl:value-of select="@heading"/>
     </Cell>
   </xsl:when>
   <xsl:otherwise>
    <Cell ss:StyleID="s27" >
      <xsl:value-of select="@heading"/>
   </Cell>
  </xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>

However, I am stuck and would appreciate if you could help me to find a solution for that.

XML

<Report xmlns="">
<Measures>
  <Measure idx="1" heading="Total Pages"/>
  <Measure idx="2" heading="Cost"/>
</Measures>
  <Columns>
    <ColGrp heading="Company Name">
      <ColGrp heading="Test Corporation">
        <Col heading="Test A" />
      </ColGrp>
    </ColGrp>
  </Columns>
</Report>
 
Old September 9th, 2006, 04:30 PM
mhkay's Avatar
Wrox Author
 
Join Date: Apr 2004
Posts: 4,962
Thanks: 0
Thanked 292 Times in 287 Posts
Default

>Also, I sort of understand what you are saying when you mention that xslt is a declarative or functional language but would appreciate if you could clarify that to me.

Then you should read my book. I try very hard there to explain the fundamental concepts.

You've shown me a lot of code, which I don't really have time to read: but you haven't explained the problem you are trying to solve, which is what I asked for.

Michael Kay
http://www.saxonica.com/
Author, XSLT Programmer's Reference and XPath 2.0 Programmer's Reference
 
Old September 9th, 2006, 05:33 PM
Friend of Wrox
 
Join Date: Oct 2003
Posts: 290
Thanks: 24
Thanked 0 Times in 0 Posts
Default

Hi Michael,

I have already read your book. That is why I am using lots of templates instead of only using the <xsl:for-each> construction as I used to do in the past.

I have to create a padding cell only once either when I am in the ColGrp template or in the Col template.

If I create it in the ColGrp template then I cannot create it again in the Col template.

If there is only one CooGrp in the xml I jump straight to the Col template and therefore have to create the cell in there.

Also, If I create it in the ColGrp template and I have more than one ColGrp in the xml I cannot create it again when I recurse to the next ColGrp. I think I have to find a way to condition the caller.

I am trying to generate a 3 dimension table based on a xml that can change. I may have only one ColGrp or many ColGrp. My xslt works for when I have only one ColGrp.

Only one ColGrp

<?xml version="1.0"?>
<Report xmlns="">
  <Measures>
    <Measure idx="1" heading="Total Pages"/>
    <Measure idx="2" heading="Cost"/>
  </Measures>
  <Columns>
    <ColGrp heading="Year">
      <Col heading="2003"/>
      <Col heading="2004"/>
    </ColGrp>
  </Columns>
......

More than one ColGrp

<Report xmlns="">
<Measures>
  <Measure idx="1" heading="Total Pages"/>
  <Measure idx="2" heading="Cost"/>
</Measures>
  <Columns>
    <ColGrp heading="Company Name">
      <ColGrp heading="Test Corporation">
        <Col heading="Test A" />
      </ColGrp>
    </ColGrp>
  </Columns>
............
</Report>


                               2003 2004
                  Total Pages Cost Total Pages Cost
Gastroenterology 42.00 64,230.00
General Medicine 36.00 $222,490.00
Orthopedics 44.00 $107,400.00
Other Specialty 36.00 $35,820.00
Pharmacy 14.20 $128,030.00
Rheumatology 60.00 $125,520.00

Cheers





Similar Threads
Thread Thread Starter Forum Replies Last Post
Variable or parameter twice within the same temp Navy1991_1 XSLT 4 June 9th, 2008 02:29 PM
XSL Variable and/or Parameter Question ripple XSLT 9 September 1st, 2006 12:16 PM
XSLT Parameter to ASP.NET Variable kwilliams XSLT 9 February 9th, 2006 04:27 PM
How to Store Literal Value in Variable/Parameter kwilliams XSLT 5 August 31st, 2005 08:52 AM
ADO Update "Variable not found " dfaulks Classic ASP Databases 4 December 1st, 2003 12:51 PM





Powered by vBulletin®
Copyright ©2000 - 2020, Jelsoft Enterprises Ltd.
Copyright (c) 2020 John Wiley & Sons, Inc.