1

I am trying to get value-of $deal/total_price. In the first block, I am able to get value and everything works great. In the second block where I am using value-of to set the variable named deal, I get an error when trying to display $deal/total_price. How can I return $deal/total_price using setup in second block?

Works:

<xsl:variable name="deal" select="/webpage/results/cars/*[partner_name = $company_name and vehicle_class_description = $vehicle_class_description_full]" />        
<xsl:value-of select="$deal/total_price"/>

Does Not Work :

<xsl:variable name="deal">
    <xsl:value-of select="/webpage/results/cars/*[partner_name = $company_name and vehicle_class_description = $vehicle_class_description_full]"/>
</xsl:variable>
<xsl:value-of select="$deal/total_price"/>

I am receiving the following errors/warnings:

Warning: XSLTProcessor::transformToXml(): Invalid type
Warning: XSLTProcessor::transformToXml(): runtime error:
Warning: XSLTProcessor::transformToXml(): XPath evaluation returned no result
zx485
  • 28,498
  • 28
  • 50
  • 59
Michael
  • 403
  • 1
  • 9
  • 28
  • Perhaps you should explain the *real* problem here - i.e. what exactly you want to accomplish here that cannot be accomplished using either `select` directly of `xsl:copy-of`. – michael.hor257k May 04 '16 at 15:33
  • My ultimate goal is to change the value of variable deal depending on different flags. Example: if(pay_now == true) else – Michael May 04 '16 at 15:42
  • Using XSL 1.0, what would be the best way to change value of variable, using conditionals, without throwing the error variable not declared? – Michael May 04 '16 at 15:44
  • I am afraid you are making a mistake by explaining **how** you want to do something, instead of **what** you want to do. A variable is never a goal in itself, only a means. -- P.S. Please don't post code in comments, it's practically unreadable. – michael.hor257k May 04 '16 at 15:51

3 Answers3

2

With <xsl:variable name="deal" select="/webpage/results/cars/*[partner_name = $company_name and vehicle_class_description = $vehicle_class_description_full]" /> the variable type is defined by evaluating the select XPath expression which returns a node-set in XSLT/XPath 1.0. You can then do XPath navigation on the node-set, such as selecting child nodes.

With

<xsl:variable name="deal">
    <xsl:value-of select="/webpage/results/cars/*[partner_name = $company_name and vehicle_class_description = $vehicle_class_description_full]"/>
</xsl:variable>

the variable type is a result tree fragment containing a text node with the string value of the first node selected by the inner value-of. With a variable of type result tree fragment you can't do any XPath navigation, you can output its string value using value-of or its tree fragment using copy-of. If you want to do XPath navigation then you first need to use exsl:node-set or similar to convert the result tree fragment into a node-set, but even if you do that for your second sample you would get with exsl:node-set($deal) a node-set with a document node containing a text node. Thus if you want to have a variable containing nodes in XSLT 1.0 you need to use

<xsl:variable name="deal-rtf">
  <foo>
    <bar>...</bar>
  </foo>
</xsl:variable>
<xsl:variable name="deal" select="exsl:node-set($deal-rtf)" xmlns:exsl="http://exslt.org/common"/>

<xsl:value-of select="$deal/foo/bar"/>

Some XSLT 1.0 processors (notably the various MSXML versions as used by IE or Edge and XslTransform in the .NET framework) do not support exsl:node-set but rather a similar function in a proprietary namespace (i.e. <xsl:variable name="deal" select="ms:node-set($deal-rtf)" xmlns:ms="urn:schemas-microsoft-com:xslt"/>).

Inside of an xsl:variable you can of course use xsl:choose, e.g.

<xsl:variable name="deal-rtf">
  <xsl:choose>
     <xsl:when test="...">
       <xsl:copy-of select="/webpage/results/cars/*[partner_name = $company_name and vehicle_class_description = $vehicle_class_description_full and pay_now = 'Y']"/>
     </xsl:when>
     <xsl:otherwise>
        <xsl:copy-of select="webpage/results/cars/*[partner_name = $company_name and vehicle_class_description = $vehicle_class_description_full]"/>
     </xsl:otherwise>
  </xsl:choose>
</xsl:variable>

<xsl:variable name="deal" select="exsl:node-set($deal-rtf)" xmlns:exsl="http://exslt.org/common"/>

<xsl:value-of select="$deal/total_price"/>
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
0

The problem is that the second version returns a RTF(Resulting Tree Fragment) and not a node-set like the first version. An XPath query cannot be applied to a RTF, it can only be applied to a node-set.

An explanation of the difference can be found here at Oracle.

In XSLT-1.0 you cannot avoid that, like it is explained here at StackOverflow.

I quote from the Oracle link:

A result tree fragment is equivalent to a node-set that contains just the root node.
You cannot apply operators like "/", "//" or predicate on a result tree fragments. They are only applicable for node-set datatypes.

The (probably easiest) solution would be using XSLT-2.0, because in XSLT-2.0 all variables are node-sets and RTFs have been extinguished.

Community
  • 1
  • 1
zx485
  • 28,498
  • 28
  • 50
  • 59
  • Is there a way to change the value of variable using if/else statements? Example set variable to true else set variable to false – Michael May 04 '16 at 15:21
0

It is as others have said, a usable node set is only produced by using the inline select attribute in the xsl:variable tag.

However, you do not have to use later XSLT versions or specific XSLT extensions, which may not be possible on shared hosting.

To cater for alternate node-sets, the trick is to use the | union operator to join each alternate node-set, but ensure that the conditionals inside the [ ] for the unwanted alternates do not return any nodes.

Tests don't have to be dependent upon the element to which the [ ] are attached, but can use whatever variables and literals it takes to ensure that the alternative only produces a node-set with its own unique criteria, and never for the others.

To illustrate with a variation of your example in your comment above:

<xsl:value-of select="/webpage/results/cars/*[($access_type = 'web') and (partner_name = $company_name) and (vehicle_class_description = $vehicle_class_description_full) and (pay_now = 'Y')]|/webpage/results/cars/*[($access_type = 'phone') and (partner_name = $company_name) and (vehicle_class_description = $vehicle_class_description_full)]"/>

where $web_access is a variable that is not directly part of the XML data being tested.

It can become unwieldy if there are too many selections, but that can be mitigated by testing each alternate to make sure it only produces a node-set when required, and empty otherwise. Then simply plonk them together in the select with a | between them.

Patanjali
  • 893
  • 13
  • 17