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!

8 comments ↓
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 :)
Leave a Comment