Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Variables in XSLT 1

Status
Not open for further replies.

tshad

Programmer
Jul 15, 2004
386
0
0
US
I need a way to carry a value from one template to another in my XSLT form.

I thought I could use a variable but that doesn't work as after I assign it in one template the value is gone as soon as a leave the template.

If I do:

<xsl:stylesheet
xmlns:xsl=" version="1.0">

<xsl:eek:utput method="xml" indent="yes"/>
<xsl:param name="mainForm" select="Testing"/>

The value seems to not really be there but if I set it in a template:

<xsl:template match="REPORT">
<xsl:variable name="mainForm" select="@AppraisalFormType"/>
@mainForm = <xsl:value-of select="$mainForm"/>
@AppraisalFormType = <xsl:value-of select="@AppraisalFormType"/>
</xsl:template>

It is set inside the template but will be gone when I leave.

I need to get a value that I can get from one template and then use it in other templates.

Is there a way to do this in XSLT?

Thanks,

Tom
 
Here is an example that I put together to show the issue. I need to find a way to carry a value I get from a template to another template. Both templates are on the same level. I thought I could use variable or param to do this but I cannot make it work. I tried finding a way using Google but nothing has helped. It seems so simple.

If I have the following xml:
Code:
<RESPONSE MISMOVersionID="2.6">
  <REPORT _ID="Major" MajorFormType="Form102">
    <FORM _ID="Minor" MinorFormType="Form240">
      <IMAGE _ID="Im2"/>
    </FORM>
  </REPORT>
  <METHODS Description="THIS IS SUMMARY">
    <COMPARISON ID="241" ComparisonAmount="352,000">
      <SALE SequenceId="0">
        <LOCATION Address="9107 Forest Ct" Address2="Franklin, CT"/>
        <ADJUSTMENT Type="Concessions" _Description="N/A"/>
      </SALE>
    </COMPARISON>
  </METHODS>
</RESPONSE>

I need to get the MajorFormType which in this case is "Form102" and use it in my next templates. Using the following template should show what I am trying to do:
Code:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="[URL unfurl="true"]http://www.w3.org/1999/XSL/Transform"[/URL]
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
  <xsl:output method="xml" indent="yes"/>
  <xsl:param name="mainForm" select="Testing"/>

  <xsl:template match="/*">
    <xsl:copy>
      <xsl:apply-templates select ="REPORT"/>
      <xsl:apply-templates select="METHODS/COMPARISON/SALE"/>
    </xsl:copy>
  </xsl:template>

  <!-- Now we handle the other sections after the DATA section -->

  <xsl:template match="REPORT">
    <xsl:variable name="mainForm" select="@MajorFormType"/>
  </xsl:template>

  <xsl:template match="SALE/*">
    <xsl:choose>
      <xsl:when test="name() = 'ADJUSTMENT'">
        <form>
          <section>
            <xsl:value-of select="../@SequenceId"/>
          </section>
          <formName>
            <xsl:value-of select="$mainForm"/>
          </formName>
          <tagName>
            <xsl:value-of select="@Type"/>
          </tagName>
          <value>
            <xsl:value-of select="@Description"/>
          </value>
        </form>
      </xsl:when>
      <xsl:when test="name() = 'LOCATION'">
        <xsl:if test="@Address">
          <form>
            <section>
              <xsl:value-of select="../@SequenceId"/>
            </section>
            <formName>
              <xsl:value-of select="$mainForm"/>
            </formName>
            <tagName>
              <xsl:text>Address</xsl:text>
            </tagName>
            <value>
              <xsl:value-of select="@Address"/>
            </value>
          </form>
        </xsl:if>
        <xsl:if test="@Address2">
          <form>
            <sectionNumber>
              <xsl:value-of select="../@SequenceId"/>
            </sectionNumber>
            <formName>
              <xsl:value-of select="$mainForm"/>
            </formName>
            <tagName>
              <xsl:text>Address2</xsl:text>
            </tagName>
            <value>
              <xsl:value-of select="@Address2"/>
            </value>
          </form>
        </xsl:if>
      </xsl:when>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

$mainForm is set in REPORT and then when leaving REPORT gone.

The results I get are:
Code:
<?xml version="1.0" encoding="utf-8"?>
<RESPONSE>
  <form>
    <section>0</section>
    <formName></formName>
    <tagName>Address</tagName>
    <value>9107 Forest Ct</value>
  </form>
  <form>
    <sectionNumber>0</sectionNumber>
    <formName></formName>
    <tagName>Address2</tagName>
    <value>Franklin, CT</value>
  </form>
  <form>
    <section>0</section>
    <formName></formName>
    <tagName>Concessions</tagName>
    <value></value>
  </form>
</RESPONSE>

As you can see mainForm didn't work - <formName><formName> should be <formName>Form102<formName>.

Is there a way to accomplish this?

Thanks,

Tom
 
Tom,

One way you can do an assignment in XSLT is by passing a parameter. Parameters have some similarities to variables. Here is how I might do what you are showing in your example (typed, not tested):

In the template matching "/*"
Code:
  <xsl:template match="/*">
    <xsl:copy>
      <xsl:apply-templates select="METHODS/COMPARISON/SALE">
         <xsl:with-param name="reportName" select="REPORT/@MajorFormType"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>

Then add a template matching "SALE":

Code:
<xsl:template match="SALE">
   <xsl:param name="reportName"/>
   <xsl:apply-templates>
       <xsl:with-param name="reportName" select="$reportName"/>
   </xsl:apply-templates>
</xsl:template>


Then modify templates matching the children of SALE:

Code:
   <xsl:template match="ADJUSTMENT">
   <xsl:param name="reportName"/>
        <form>
          <section>
            <xsl:value-of select="../@SequenceId"/>
          </section>
          <formName>
            <xsl:value-of select="$reportName"/>
          </formName>
          <tagName>
            <xsl:value-of select="@Type"/>
          </tagName>
          <value>
            <xsl:value-of select="@Description"/>
          </value>
        </form>
 </xsl:template>

  <xsl:template match="LOCATION">
   <xsl:param name="reportName"/>
        <xsl:if test="@Address">
          <form>
            <section>
              <xsl:value-of select="../@SequenceId"/>
            </section>
            <formName>
              <xsl:value-of select="$reportName"/>
            </formName>
            <tagName>
              <xsl:text>Address</xsl:text>
            </tagName>
            <value>
              <xsl:value-of select="@Address"/>
            </value>
          </form>
        </xsl:if>
        <xsl:if test="@Address2">
          <form>
            <sectionNumber>
              <xsl:value-of select="../@SequenceId"/>
            </sectionNumber>
            <formName>
              <xsl:value-of select="$reportName"/>
            </formName>
            <tagName>
              <xsl:text>Address2</xsl:text>
            </tagName>
            <value>
              <xsl:value-of select="@Address2"/>
            </value>
          </form>
        </xsl:if>
  </xsl:template>

There are circumstances where my example will not produce identical results Your XSLT is relying on built-in templates in a manner slightly different from my example. You are also matching all children of SALE (match="SALE/*") and then potentially ignoring all that are not LOCATION or ADJUSTMENT. But my purpose is to illustrate the use of parameters to solve your issue.

Tom Morrison
Micro Focus
 
Your solution is working but only if I add yours to mine.

If I change my template (SALE/*) to mimic yours, I get:
Code:
  <xsl:template match="SALE/*">
    <xsl:param name="reportName"/>
    <xsl:apply-templates>
      <xsl:with-param name="reportName" select="$reportName"/>
    </xsl:apply-templates>
    <xsl:choose>
      <xsl:when test="name() = 'ADJUSTMENT'">
        <form>
          <section>
...
This gives me the same results I show in my earlier post, which is still missing the formName. If I look at $reportName, it is blank.

If I then add your code in, then it works. What happens is it jumps to your code (SALE) and then to mine (SALE/*), which now shows the $reportName as "Form102".

Why didn't mine work with the same code you had in yours?

Also, I noticed that in my watch window, I see $reportName as {Dimension:[1]} when it was working and as "" when I leave your code out.

I now changed the code as:

Code:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="[URL unfurl="true"]http://www.w3.org/1999/XSL/Transform"[/URL]
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
  <xsl:output method="xml" indent="yes"/>
 
  <xsl:template match="/*">
    <xsl:copy>
      <xsl:apply-templates select="METHODS/COMPARISON/SALE">
        <xsl:with-param name="reportName" select="REPORT/@MajorFormType"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>

  <!-- Now we handle the other sections after the DATA section -->

  
  <xsl:template match="SALE/*">
    <xsl:param name="reportName"/>
    <xsl:apply-templates>
      <xsl:with-param name="reportName" select="$reportName"/>
    </xsl:apply-templates>
    <xsl:choose>
      <xsl:when test="name() = 'ADJUSTMENT'">
        <form>
          <section>
            <xsl:value-of select="../@SequenceId"/>
          </section>
          <formName>
            <xsl:value-of select="$reportName"/>
          </formName>
          <tagName>
            <xsl:value-of select="@Type"/>
          </tagName>
          <value>
            <xsl:value-of select="@Description"/>
          </value>
        </form>
      </xsl:when>
      <xsl:when test="name() = 'LOCATION'">
        <xsl:if test="@Address">
          <form>
            <section>
              <xsl:value-of select="../@SequenceId"/>
            </section>
            <formName>
              <xsl:value-of select="$reportName"/>
            </formName>
            <tagName>
              <xsl:text>Address</xsl:text>
            </tagName>
            <value>
              <xsl:value-of select="@Address"/>
            </value>
          </form>
        </xsl:if>
        <xsl:if test="@Address2">
          <form>
            <sectionNumber>
              <xsl:value-of select="../@SequenceId"/>
            </sectionNumber>
            <formName>
              <xsl:value-of select="$reportName"/>
            </formName>
            <tagName>
              <xsl:text>Address2</xsl:text>
            </tagName>
            <value>
              <xsl:value-of select="@Address2"/>
            </value>
          </form>
        </xsl:if>
      </xsl:when>
    </xsl:choose>
  </xsl:template>

  <xsl:template match="SALE">
    <xsl:param name="reportName"/>
    <xsl:apply-templates>
      <xsl:with-param name="reportName" select="$reportName"/>
    </xsl:apply-templates>
  </xsl:template>

</xsl:stylesheet>

This one works but if you take out or comment out your template section, the reportName is blank.

The results I get are:

Code:
<?xml version="1.0" encoding="utf-8"?>
<RESPONSE>
  <form>
    <section>0</section>
    <formName>Form102</formName>
    <tagName>Address</tagName>
    <value>9107 Forest Ct</value>
  </form>
  <form>
    <sectionNumber>0</sectionNumber>
    <formName>Form102</formName>
    <tagName>Address2</tagName>
    <value>Franklin, CT</value>
  </form>
  <form>
    <section>0</section>
    <formName>Form102</formName>
    <tagName>Concessions</tagName>
    <value></value>
  </form>
</RESPONSE>

Not sure why it uses the built in code.

I am using the (SALE/*) to make sure I get the children. If I didn't do that it only seemed to get me the SALE tag.

Thanks,

Tom
 
It turns out I didn't need the xsl:apply-templates in the SALE\* section. So I can change:

Code:
  <xsl:template match="SALE/*">
    <xsl:param name="reportName"/>
    <xsl:apply-templates>
      <xsl:with-param name="reportName" select="$reportName"/>
    </xsl:apply-templates>
    <xsl:choose>
      <xsl:when test="name() = 'ADJUSTMENT'">
        <form>

To

Code:
  <xsl:template match="SALE/*">
    <xsl:param name="reportName"/>
    <xsl:choose>
      <xsl:when test="name() = 'ADJUSTMENT'">
        <form>

So is the SALE section just to define the parameter?

Thanks,

Tom
 
Tom,

Not sure if you also put in my templates to match ADJUSTMENT and LOCATION. (I must admit that I am not very fond of doing a xsl:choose based on the element name, since that is essentially what template matching will do for you.)

If you understand built-in templates, you will understand why the report name fails to get passed along. Built-in templates are designed to keep the whole recursive ball rolling, but since they are what they are, they are unaware that there need to have any parameters passed along.

So, if you want to pass parameter(s) along as you recurse through the document, you need to match all the levels and pass the parameter. One of the built-in templates is:
Code:
<xsl:template match="*">
   <xsl:apply-templates/>
</xsl:template>

So you can create a template (at the bottom of your XSLT) which matches all the elements and passes your parameter:
Code:
<xsl:template match="*">
   <xsl:param name="reportName"/>
   <xsl:apply-templates>
       <xsl:with-param name="reportName" select="$reportName"/>
   </xsl:template>
</xsl:template>

Almost any tutorial (book or web) will explain built-in rules more thoroughly than I can here.

Also, I tend to stay away from matching things like "SALE/*" unless I really, really need to do so. Perhaps it is just my style, but I find it more difficult to read and understand the XSLT.


Tom Morrison
Micro Focus
 
I hadn't looked closely at your section with the templates that match ADJUSTMENT and LOCATION. I may change mine to mimic yours. But mine do essentially the same thing by doing the SALE/*. I am just testing them in the template instead of create a particular template for each. This was the way I did it in 2 other sheets I had created. This one has a different format, so I was trying to get it started and then look at it more closely when I got some results. Not sure which is better.

I did look at my books explanation of the built-ins and have a better understanding of it.

Actually playing with this example helped me understand better what was going on. I couldn't figure out why I couldn't get my attributes from the SALE node directly (before I added your example in). Now I think I am getting a better hang of it.

If I did:
Code:
  <xsl:template match="/*">
    <xsl:copy>
      <xsl:apply-templates select="METHODS/COMPARISON/SALE">
        <xsl:with-param name="reportName" select="REPORT/@MajorFormType"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="SALE/*">
      something
  </xsl:template>

I would get all the CHILDREN of SALE

If I did:

Code:
  <xsl:template match="/*">
    <xsl:copy>
      <xsl:apply-templates select="METHODS/COMPARISON/SALE">
        <xsl:with-param name="reportName" select="REPORT/@MajorFormType"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="SALE">
      something
  </xsl:template>

I would get only the SALE Node.

If I did:

Code:
  <xsl:template match="/*">
    <xsl:copy>
      <xsl:apply-templates select="METHODS/COMPARISON/SALE">
        <xsl:with-param name="reportName" select="REPORT/@MajorFormType"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="SALE">
      something
  </xsl:template>

  <xsl:template match="SALE/*">
      something
  </xsl:template>

Here I would also get the SALE node alone and not the children as the first and most specific template that matches the apply-templates would be processed. And since SALE is more specific than SALE/* for METHODS/COMPARISON/SALE, it is processed and the children are not handled.

But when I do:

Code:
  <xsl:template match="/*">
    <xsl:copy>
      <xsl:apply-templates select="METHODS/COMPARISON/SALE">
        <xsl:with-param name="reportName" select="REPORT/@MajorFormType"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="SALE">
    <xsl:apply-templates>
      <xsl:with-param name="reportName" select="$reportName"/>
    </xsl:apply-templates>
      something
  </xsl:template>

  <xsl:template match="SALE/*">
      something
  </xsl:template>

The second apply-templates now passes through the original apply-templates to the next specific template that matches, in this case SALE/*. Now the SALE node is processed and the children are also processed (SALE/*).

I was confused as to why the apply-templates with no "select" worked and I guessed that it would use the previous one. Could be wrong here but that seems to be what is happening.

Thanks,

Tom
 
Tom,

You are making good progress and learning some crucial things about XSLT.

The default selection for <xsl:apply-templates/> is to select all child elements.

If you match SALE with a template and fail to apply-templates in that template, then the recursion stops. This may be the reason you did not see any children of SALE being matched.

Tom Morrison
Micro Focus
 
Earlier you had mentioned that you would stay away from "SALE/*" to get the children of SALE. I think you said that that was what caused the built-in templates.

You also said: Not sure if you also put in my templates to match ADJUSTMENT and LOCATION. (I must admit that I am not very fond of doing a xsl:choose based on the element name, since that is essentially what template matching will do for you.) I assume this is related to not using SALE/*.

So would you then eliminate the SALE/* section above and do it like so:

Code:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="[URL unfurl="true"]http://www.w3.org/1999/XSL/Transform"[/URL]    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
  <xsl:output method="xml" indent="yes"/>
  <xsl:template match="/*">
    <xsl:copy>
      <xsl:apply-templates select="METHODS/COMPARISON/SALE">
        <xsl:with-param name="reportName" select="REPORT/@MajorFormType"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>

  <!-- Now we handle the other sections after the DATA section -->

  <xsl:template match="SALE">
    <xsl:param name="reportName"/>
    <xsl:apply-templates>
      <xsl:with-param name="reportName" select="$reportName"/>
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="LOCATION">
    <xsl:param name="reportName"/>
    <xsl:apply-templates>
      <xsl:with-param name="reportName" select="$reportName"/>
    </xsl:apply-templates>
      <xsl:if test="@Address">
        <form>
          <section>
            <xsl:value-of select="../@SequenceId"/>
          </section>
          <formName>
            <xsl:value-of select="$reportName"/>
          </formName>
          <tagName>
            <xsl:text>Address</xsl:text>
          </tagName>
          <value>
            <xsl:value-of select="@Address"/>
          </value>
        </form>
      </xsl:if>
      <xsl:if test="@Address2">
        <form>
          <sectionNumber>
            <xsl:value-of select="../@SequenceId"/>
          </sectionNumber>
          <formName>
            <xsl:value-of select="$reportName"/>
          </formName>
          <tagName>
            <xsl:text>Address2</xsl:text>
          </tagName>
          <value>
            <xsl:value-of select="@Address2"/>
          </value>
        </form>
      </xsl:if>
  </xsl:template>
  
  <xsl:template match="ADJUSTMENT">
    <xsl:param name="reportName"/>
    <xsl:apply-templates>
      <xsl:with-param name="reportName" select="$reportName"/>
    </xsl:apply-templates>
      <form>
        <section>
          <xsl:value-of select="../@SequenceId"/>
        </section>
        <formName>
          <xsl:value-of select="$reportName"/>
        </formName>
        <tagName>
          <xsl:value-of select="@Type"/>
        </tagName>
        <value>
          <xsl:value-of select="@Description"/>
        </value>
      </form>
  </xsl:template>
</xsl:stylesheet>

Thanks,

Tom
 
Yes, I definitely like that much better.

Tom Morrison
Micro Focus
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top