3
\$\begingroup\$

I've written some XSLT where I want to assign values to some variables.

Depending on whether or not a condition is met, I want the variable to be populated with a computed value and I want it to have a default value otherwise.

I've written the following code that appears to work, but I think that there may be a better way.

<xsl:variable name='HasValue' select='count($Catalog/lookup) &gt; 0' />
<xsl:variable name='Product' >
    <xsl:choose><xsl:when test='$HasValue'>
      <xsl:value-of select='$Catalog/lookup/Product' />
    </xsl:when><xsl:otherwise>UNK</xsl:otherwise></xsl:choose>
</xsl:variable>
<xsl:variable name='Area' >
    <xsl:choose><xsl:when test='$HasValue'>
      <xsl:value-of select='$Catalog/lookup/Area' />
    </xsl:when><xsl:otherwise>UNK</xsl:otherwise></xsl:choose>
</xsl:variable>

Yes, my XSLT processor uses XSLT 2.0

\$\endgroup\$
4
\$\begingroup\$

IMHO your code is fine. The "problem" with XSLT variables is that they are NOT variables but more resemble constants: once set they cannot be changed anymore. Also, their scope is restricted to anything that follows their definition until the closing tag. In particular they are NOT visible in any ancestor tag. The combination of both results in the type of constructs that you have used above.

What is most disturbing is the fact that you usually have to repeat the testing of the criterion again and again if several variables depend on the same criterion. Again there's nothing generic that you can do about this, but of course, you can always at least create another variable holding the result of the test and then use that several times.

Depending on the context of your XSLT I would suggest two alternatives which may or may not be more elegant:

a) works in XSLT 1.0 and 2.0: If you just define the variables to be used as parameters to a template-call consider writing the template-call twice once in the when as once in the otherwise portion. This way you will end up with just one <choose>.

b) Works only well in XSLT 2.0 (or a little awkward in XSLT with node-set extensions): move the check of the criterion and the selection of the definition values into a template and return the "set" of values to the assigned in a node-set. In a way you can regard this node-set as a record whose attributes have been set according to the criterion. See example below.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet 
    version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" />

  <xsl:template name="get_values">
    <xsl:param name="catalog"/>

      <xsl:choose>

        <xsl:when test="count($catalog/lookup) &gt; 0">
          <PRODUCT VALUE="{$catalog/lookup/Product}"/>
          <AREA VALUE="{$catalog/lookup/Area}"/>
        </xsl:when>

        <xsl:otherwise>
          <PRODUCT VALUE="UNK"/>
          <AREA VALUE="UNK"/> 
        </xsl:otherwise>

      </xsl:choose>

  </xsl:template>

  <xsl:template match="/">

    <xsl:variable name="Catalog" select="."/>

    <xsl:variable name="my_config">
      <xsl:call-template name="get_values">
        <xsl:with-param name="catalog" select="$Catalog"/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:message>
      The value of PRODUCT is <xsl:value-of select="$my_config/PRODUCT/@VALUE"/> 
      and the value of AREA is <xsl:value-of select="$my_config/AREA/@VALUE"/> 
    </xsl:message>

  </xsl:template>

</xsl:stylesheet>

This solution is definitely not shorter than yours but it resembles more other standard programming languages. The record type approach may come in especially handy if you have a lot of templates requiring the attributes in the record.

\$\endgroup\$
  • \$\begingroup\$ Would <xsl:apply-templates select="$my_config" /> work? If so, then <xsl:message> can be simplified. \$\endgroup\$ – Dave Jarvis Nov 1 '13 at 16:18
  • \$\begingroup\$ Yes, you can pass $my_config to apply-templates. My code above is just supposed to show that the dynamically built XML tree works just as though it were coming from the input file. So, basically there's no difference. \$\endgroup\$ – Marcus Rickert Nov 2 '13 at 1:38
2
\$\begingroup\$

Since you're using XSLT 2.0, you can use an if statement and drop the xsl:choose entirely. This will be more efficient because the processor won't need to create a new tree. It is also much less code...

<xsl:variable name="HasValue" select="count($Catalog/lookup) &gt; 0" />
<xsl:variable name="Product" select="if ($HasValue) then $Catalog/lookup/Product else 'UNK'"/>
<xsl:variable name="Area" select="if ($HasValue) then $Catalog/lookup/Area else 'UNK'"/>
\$\endgroup\$
  • \$\begingroup\$ Welcome to Code Review! Good job on your first answer. \$\endgroup\$ – SirPython Jan 5 '16 at 23:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.