So I just blogged about how I find XSLT somewhat frustrating. Admittedly some of the frustration is based on my relative lack of experience with XSLT. One area ripe with potential frustration is the concept of the node-set data type for which learning has caused me a bit of hair loss recently!
A typical scenario using XSLT is to apply an XSL file as a transform to an XML file to produce output, often HTML. Of course never one to be satisfied with the typical scenario, I found myself compelled to use numerous data sources and even embed XML in my XSL file (For those not familiar with how to pull XML from another XML file, you use the document() function.)
Anywho, while embedding XML, and even when copying subsets of XML documents into variables in my XSL file, I kept running into the dreaded "Expression must evaluate to a node-set" error while trying to access subnodes. For a long time I could not figure out how to get past it.
(BTW, the following assumes MSXML 4.0)
I finally figured out the pattern (I think :). Basically if you "select" a subset from a node-set, you get a node-set, like this:
<xsl:variable name="month-as-node-set" select="$issue-date-as-node-set/IssueDate/Month"/>
However, if you select from a node-set but instead use </xsl:value-of> as a subelement, you get a string, and you can’t later select directly from a string.
<xsl:variable name="month-as-string">
<xsl:value-of select="$issue-date-as-node-set/IssueDate/Month"/>
</xsl:variable>
Of course you can use the MSXML-specific node-set() function to convert back to a node-set from a string (As a side note: Why didn’t the XSLT standards committee include such a vital piece of functionality in the standard?!? And be sure to declare the namespace msxsl; for exactly how, see my last example at the bottom of this post):
<xsl:variable name="month-as-node-set" select="msxsl:node-set($month-as-string)"/>
Next, if you want to output the values contained in the XML elements but not any of the markup, any of these will do:
<xsl:value-of select="$month-as-node-set" />
<xsl:value-of select="$month-as-string" />
<xsl:copy-of select="$month-as-string" />
Of course it you actually want the markup output, you need to use <xsl:copy-of> like so (as example where you might want this is if you include HTML markup like < UL> and < LI> inside your XML elements and you want to copy it intact to your output):
<xsl:copy-of select="$month-as-node-set" />
Lastly, you can’t select sub-nodes using a string (which is the reason I was inspired to write this post.) If you try, you get "Expression must evaluate to a node-set.":
<!-- This CRASHES WITH: Expression must evaluate to a node-set. -->
<xsl:value-of select="$month-as-string/Name" />
Here’s a complete example which will work with (and otherwise ignore) any XML input file. Note it crashes if the last <xsl:value-of> is not removed:
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:variable name="issue-date-as-string">
<IssueDate>
<DayOfWeek>
<Name>Wednesday</Name>
<Number>4</Number>
</DayOfWeek>
<Month>
<Name>July</Name>
<Number>7</Number>
</Month>
<Day>14</Day>
<Year>2004</Year>
</IssueDate>
</xsl:variable>
<xsl:variable name="issue-date-as-node-set"
select="msxsl:node-set($issue-date-as-string)"/>
<xsl:variable name="month-as-string">
<xsl:value-of
select="$issue-date-as-node-set/IssueDate/Month"/>
</xsl:variable>
<xsl:variable name="month-as-node-set"
select="$issue-date-as-node-set/IssueDate/Month"/>
<xsl:template match="/">
<html>
<body>
<table border="1">
<tr>
<td>
<!-- This outputs just "July7" -->
<xsl:value-of select="$month-as-node-set" />
</td>
</tr><tr>
<td>
<!-- This outputs just "July7" -->
<xsl:value-of select="$month-as-string" />
</td>
</tr><tr>
<td>
<!-- This outputs just
"<Month><Name>July</Name><Number>7</Number></Month>"
-->
<xsl:copy-of select="$month-as-node-set" />
</td>
</tr><tr>
<td>
<!-- This outputs just "July7" -->
<xsl:copy-of select="$month-as-string" />
</td>
</tr><tr>
<td>
<!-- This outputs just "July" -->
<xsl:value-of select="$month-as-node-set/Name" />
</td>
</tr><tr>
<td>
<!--
This CRASHES WITH:
Expression must evaluate to a node-set.
-->
<xsl:value-of select="$month-as-string/Name" />
</td>
</tr>
</table>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
If you need more info than I’ve provided here, you can check out Using msxsl:node-set() to Process Result-Tree Fragments on Microsoft’s MSDN website.
I hope those who are stumped as was I by the "Expression must evaluate to a node-set" error are able to locate this post when googling and hence greatly reduce the amount of hair loss they experience while solving this problem!
Thanks – I located this post when googling and hence greatly reduced the amount of hair loss I experienced :)
Steve: And that’s exactly why I posted, to help others get past this same stupid problem. Glad it helped!
Thanks for the post, much appreciated, helped me out a lot on figuring out the error and most important, leading to find the solution.
Thank you – You pointed me in the right direction…
I added xmlns:msxml=”urn:schemas-microsoft-com:xslt” to my stylesheet declaration, and wrapped my variable with to convert my variable into a node-set.
Thanks for the post Mike, this was the first site I hit from google when I ran across this error. Great description of the problem, for my scenario I was just missing a * in my xpath query to return the nodes rather than the attributes.
Thanks Mike for compiling this. Very useful and informative.
Thanks a lot. I have already spent hours figuring out why it didn’t work. And now I must confess it isn’t even complicated when you got the difference.
Thanks a lot! This solved my problem as well :)
Just wanted to say thanks! I’m new at XSL, but I learned more from reading this article than I have in weeks! You the man!
@Alan Thompson – thanks for the comment.
That is too funny! I’ve not touched XSL and XSLT since 2006 when at the time I ran running and screaming from it, never to (hopefully) look back on it ever again!
If you are learning it because it’s a requirement of your job I lament on your behalf. If, however you are learning of you own volition I’d recommend you heed the sage advice of Monty Python on this video (specifically starting just after time 1:55):
-Mike
…and another thanks from me! This trouble still very popular :)
But about your side note:
>Why didn’t the XSLT standards committee include such a vital piece of functionality in the standard?!?
And why didn’t the MSXML treat such expressions as node-set by default without any piece of additional functionality? Isn’t it obvious that in ‘$some-variable/some-name’ first part is not a string?
Thank you again for this post!
@Serg Pogodin – Thanks for the comment. You are preaching to the choir! But then again I gave up on XSLT as pretty much a brain-dead technology about 5 years ago and haven’t looked back since. If others will do the same it will finally die it’s appropriate death and something that actually makes sense might take it’s place.
Hi Mike, I am facing a similar issue but not able to come up with a solution. My domain is not XSLT :( . My code is as below, the problem is with . how can i reformat it. Please help me. Thanks
Hi @Prajeesh,
Thanks for the comment, but your code did not make it through.
That said, I haven’t used XSLT since back in 2004, and even then I didn’t understand it well. So the chances I could help is slim to none.
Good luck finding your answer though.
-Mike
I am having an issue. Please help me