Getting Past the XSLT Error: “Expression must evaluate to a node-set.”

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!

Tags: , ,

Ranting about XSLT’s verbosity

I’ve been trying to learn XSLT for a few projects, and I find it both a fascinating and extremely frustrating language! I read about the designers goals (from Michael Kay’s XSLT: Programmer’s Reference) and just shake my head, i.e. : "XSL stylesheets should be human-legible and reasonably clear" and in the next sentence "Terseness in XSL markup is of minimal importance." It seems to be because terseness wasn’t a goal, they decided to see just exactly how verbose they could make it! This IMO directly counters the goal of making it human-legible and reasonably clear!

For example, I wanted to be able to display days as "3rd" and "10th" without having to store the "rd" and the "th" in my data so I decided to write a function (template) called "suffix-day" to do so:

<xsl:template name="suffix-day">
   <xsl:param name="day" />
   <xsl:variable name="last-digit">
      <xsl:value-of select="$day mod 10" />
   </xsl:variable>
   <xsl:value-of select="$day" />
   <sup>
   <xsl:choose>
      <xsl:when test="$last-digit=1">st</xsl:when>
      <xsl:when test="$last-digit=2">nd</xsl:when>
      <xsl:when test="$last-digit=3">rd</xsl:when>
      <xsl:otherwise>th</xsl:otherwise>
   </xsl:choose>
   </sup>
</xsl:template>

Of course to call "suffix-day" the XSTL designers wouldn’t have been so forward thinking as to allow syntax such as this:

<xsl:call select="suffix-day($issue-day)" />

Or even this to allow expliticly specifying the parameter name:

<xsl:call select="suffix-day(day:$issue-day)" />

Noooooo, that will not do! It must be VERBOSE or it is not in keeping with the lofty aspirations of XSLT! Instead, we must call it like so!:

 <xsl:call-template name="suffix-day">
   <xsl:with-param name="day">
      <xsl:value-of select="$issue-date" />
   </xsl:with-param>
 </xsl:template>

Or even better, what if we have a template that needs five parameters? Oooh. So instead of:

<xsl:call select="this-little-piggy($market,
   $stayed-home,$roast-beef,
   $none,$wee-wee-wee)"
/>

We’ll get to bask in verbosity with this!:

   <xsl:call-template name="this-little-piggy">
      <xsl:with-param name="piggy1">
         <xsl:value-of select="$market" />
      </xsl:with-param>
      <xsl:with-param name="piggy2">
         <xsl:value-of select="$stayed-home" />
      </xsl:with-param>
      <xsl:with-param name="piggy3">
         <xsl:value-of select="$roast-beef" />
      </xsl:with-param>
      <xsl:with-param name="piggy4">
         <xsl:value-of select="$none" />
      </xsl:with-param>
      <xsl:with-param name="piggy5">
         <xsl:value-of select="$wee-wee-wee" />
      </xsl:with-param>
   </xsl:template>

Now now, doesn’t that feel more righteous? I’m just warm and fuzzy, and tingly all over right now. :-)

Jeesh.


Okay, I’m probably dead wrong with my criticsm of XSLT. There’s probably many great reasons why it should be verbose and I’m just being ignorant and short-sighted. Normally I never want to come across ignorant or short-sighted and I normally want to be objective and willing to acknowledge when I’m in the wrong.

But not today. I just wanted to vent. No, actually I just want to rant! Rant, rant, rant! So I don’t want to hear it from you why I’m wrong. Go away and leave me alone. Sue me if you don’t like it. :)

Tags: ,

It’s Always Something (#1): Wrestling with System.Xml.Xsl.XslTransform and the Document() function

It’s always something!

At Xtras, we have a rather sophisticated email broadcast system that we developed internally. Our system uses a set of tables in our SQL Server database that models the type of email, who it should go to, the mailing lists, etc. It loads each newsletter subscriber’s name and email address using a FOR XML EXPLICIT query from our SQL Server. It uses .XSL files to contain the HTML and/or Text content for each email, and then it uses a System.Xml.Xsl.XslTransform object to generate each HTML/Text email using a transform of the subscriber’s .XML fragment and the current newsletter’s .XSL file.  Once generated, it attempts to send using the SMTP component from /n software’s IP*Works!, and logs the success of failure into our database.

It is actually quite an elaborate and powerful system, and my hat’s off to Bill who developed it. However, it isn’t perfect. First, there is very little user interface; "the cobbler’s children go barefoot" as they say.  The second is our infrastructure doesn’t contemplate organization of .XSL files; there are literally hundreds all in the same directory.  But probably the biggest problem with it has been our approach to creating new email newsletters; we literally copy the XSL file from last newsletter issue and edit it, updating it for the current issue. 

Data driven?  Automated?  Modularized?  Yes it is, at least the parts that had to be are data-driven, automated, and modularized.  Like inserting the recipient’s email address and sending the emails.  But all other parts are manual!  Maybe you’ve gotten emails from us in the past where we’ve made errors like having two different dates on the same email in different places?  Now you know why…

Recently I grabbed a book on XSLT (Michael Kay’s XSLT: Programmer’s Reference; it’s actually quite good even though it’s four years old by now) and started learning how to write modular XSLT.  Of course it has taken longer than I planned so our next newsletter is late (our June newsletter for XDN; anyone notice its really late? Sorry…)  I managed to prepare a really elaborate stylesheet and pulled almost all of the content of the newsletter from data in our SQL Server database.  I then got the newsletter ready to go and passed it on to Bill to have him send it, and guess what?  It blew up his email system, giving code access security errors! 

Now you have to know Bill. He is very focused on completing his current projects, so Bill was not happy that my new .XSL blew up his "working" system!  But he understood that it would probaby take longer to rewrite than to solve the problem, and it is something we need to solve anyway.

The code access security error took us down the wrong path.  What I had added to the XSL file was a call to the XSLT document() function to load a second XML document (the newsletter content) from a URL, and we both thought XslTransform was telling us we needed to resolve a security problem in accessing a URL via HTTP.  Well, that wasn’t it; it was actually quite simple.  We needed an XmlResolver. More specifically, we needed an XmlUrlResolver.

So the moral of this long-winded story is, if you are using an XslTransform() and you add a reference to the document() function in  your .XSL file, you’ll need to add an XmlUrlResolver for it to work.  What follows is a self-contained simple example; copy into a class can call that class’ Exec method on a form click event to test (oh, and you’ll need to put an .XML and .XSL file at http://localhost/xslt/ for it to work):

Imports System.XmlImports System.Xml.XPathImports System.Xml.XslImports System.Text.EncodingPublic Class Test   Private Const outFile As String = "XsltOutput.html"   Public Sub Exec()      Dim xslt As New XslTransform      xslt.Load(FileUrl("XdnJun2004.xsl"))      Dim data As New XPathDocument(FileUrl("Subscriber.xml"))      Dim writer As New XmlTextWriter(outFile, UTF8)      Dim xmlr As New XmlUrlResolver      xslt.Transform(data, Nothing, writer, xmlr)   End Sub   Private Function FileUrl(ByVal filename As String) As String      Dim baseUrl As String = "http://localhost/xslt/"      Return CType(baseUrl & filename, String)   End FunctionEnd Class

I hope this post finds its way into Google’s index in hopes others with the same problem don’t have to spend as much time as we did trying resolving this problem.

Good XSLT Tutorial

I did a web search for an XSLT tutorial a few minutes ago and came across a really nice one I want to remember at topxml.com.