Re: [xsl] Implementing a (fairly) complex business rule

Subject: Re: [xsl] Implementing a (fairly) complex business rule
From: Wendell Piez <wapiez@xxxxxxxxxxxxxxxx>
Date: Tue, 30 Sep 2008 10:39:25 -0400
Peter,

At 09:07 AM 9/30/2008, you wrote:
      <xsl:if test="string-length(s0:POSTCODE/text() = 0)">
        <xsl:attribute name="ReasonForNull">
          <xsl:text>1</xsl:text>
        </xsl:attribute>
      </xsl:if>

This won't work as a test for whether s0:POSTCODE has a null value. For one thing, "null value" is undefined in XSLT. Null values in your XML in any number of ways.


More seriously, however, you have

string-length(s0:POSTCODE/text() = 0)

This measures the length of the string returned by casting the expression "sO:POSTCODE/text()=0". This is a numeric comparison, returning true if you have <s0:POSTCODE>0</s0:POSTCODE> (or some other lexical representation of 0, such as "0.0"), and false otherwise. Cast to a string, this is either "true" or "false". (In XPath 1.0. In XPath 2.0 you would get an error for trying to measure the string length of a Boolean.) The string length of "false" is 5, "true" is 4. Either 4 or 5 always tests true. So you will always get your @ReasonForNull[.='1'].

So you are looking at one of those unhelpful occasions where XPath silently errors on you, without telling you so, by faithfully executing what you have asked for.

Now, I'm guessing you meant

string-length(s0:POSTCODE/text()) = 0

This looks like it should work: it measures the length of s0:POSTCODE's text node, and returns true if it is 0.

However, I wouldn't recommend this either. It will test true when s0:POSTCODE has no value at all, since in that case, it has no text node, and the string length of a node that does not exist is zero.

But it's fairly brittle, since it will fail if your input, for example, has

<s0:POSTCODE> </s0:POSTCODE>

(i.e. whitespace-only content but not zero-length)

which comes back to my opening remark that the notion of "null value" has to be defined before we can write the expression properly.

This is not an infrequent problem. Often a defensive coder will define "null value" as "no value or whitespace-only value". In this case, a good test is:

<xsl:if test="not(normalize-space(s0:POSTCODE))">
... </xsl:if>

The normalize-space() function collapses a whitespace-only value into an empty string. The string value is cast to a Boolean for the not() function, by the rule that empty strings are false and non-empty strings are true. not(), of course, inverts this Boolean value, so we get true when s0:POSTCODE has no value (except possibly for whitespace).

Saying s0:POSTCODE/text() isn't necessary, since the string value of the element itself will do. Indeed, if any comments or processing instructions are present, looking at the first text node (in XSLT 1.0) won't do: we want the value of s0:POSTCODE itself.

So the problems here reduce to (a) having the parethesis misplaced, and then potentially (b) writing a test which doesn't necessarily perform the test you actually want. We see expressions like (b) since they are common in other languages. But in XSLT (or actually, here, XPath), other idioms frequently turn out to be both cleaner and more robust (while demanding a bit of an education to learn).

Then too, sometimes the definition of "null value" includes values like "0" or "NULL". Which is a different story altogether.

Cheers,
Wendell



======================================================================
Wendell Piez                            mailto:wapiez@xxxxxxxxxxxxxxxx
Mulberry Technologies, Inc.                http://www.mulberrytech.com
17 West Jefferson Street                    Direct Phone: 301/315-9635
Suite 207                                          Phone: 301/315-9631
Rockville, MD  20850                                 Fax: 301/315-8285
----------------------------------------------------------------------
  Mulberry Technologies: A Consultancy Specializing in SGML and XML
======================================================================

Current Thread