Re: [xsl] Ignoring a child element

Subject: Re: [xsl] Ignoring a child element
From: Wendell Piez <wapiez@xxxxxxxxxxxxxxxx>
Date: Thu, 07 Sep 2006 17:19:32 -0400
Emily,

At 03:35 PM 9/7/2006, you wrote:
I'm trying to weed out a <para> element that does not have any content.
However, it does have a TLStyle element which would style the content if
it existed.  The XML table entry looks like this:


<entry colname="1" morerows="0" align="left" valign="top"> <para> <TLStyle>BodyText</TLStyle> </para> </entry>

I tried using normalize-space(.), but since the TLStyle element is in
there, it tests true.  normalize-space(.) = BodyText

Right.


<xsl:choose>

<xsl:when test="normalize-space(.)">

<xsl:apply-templates select="current()"/>

</xsl:when>

Applying templates to current() is the same as applying templates to "." (the context node being the current node here). That's just FYI: it doesn't affect your problem.


The following code also tests true, but I'm not sure why.  I want it to
ignore what's in TLStyle and tell me if there is any other content
inside of <entry>.

                                            <xsl:variable
name="withoutTLStyle" select="node()[not(self::TLStyle)]"/>
                                                        <xsl:choose>

<xsl:when test="normalize-space($withoutTLStyle)">

<xsl:apply-templates select="current()"/>

</xsl:when>
                                                <xsl:otherwise>
                                                <!--do something else
because it's empty-->
                                                </xsl:otherwise>
                                          </xsl:choose>

This works, sometimes, because your XPath is correctly selecting all the children of the p except TLStyle children, and when you normalize space there, you may get the empty string you expect. It will, however, fail on other occasions when there is mixed content, since normalize-space($withoutStyle) will only normalize space on the *first* node bound to $withoutStyle, which could well have no string value even when other nodes are present (thus giving a false positive).


The essence of the problem is in defining what "content" includes, since having a TLStyle element there (which does have content) obviously doesn't make the grade. One way of handling this may seem a bit roundabout, but it is clear and quite easy to maintain and extend if/as your requirements get even more demanding. You could pre-process your content in a special "testing" mode and then test on the result you get:

<xsl:variable name="withoutStyle">
  <xsl:apply-templates mode="ignoreTL"/>
</xsl:variable>
<xsl:choose>
  <xsl:when test="normalize-space($withoutStyle)"> ... </xsl:when>
...

and

<xsl:template match="TLStyle" mode="ignoreTL"/>

should do it. Other nodes are matched in the mode and pass through, except text nodes, which have their values copied. This gives you a result tree which gets coerced into a string when you normalize its space -- testing true when there's anything in there besides space, allowing you to process to create the results of your paragraph only then.

Taking this a step further, if you wanted to be a bit more baroque and theoretically more efficient (at the price of losing some of the flexibility that alternate mode gives you), you could get close to 2.0 micropipelining (but this would work in 1.0 as well) by building your result first and then only copying it over if you decide you want it:

<xsl:variable name="withoutStyleResult">
  <xsl:apply-templates mode="ignoreTL"/>
</xsl:variable>

<xsl:template match="TLStyle" mode="ignoreTL"/>

<xsl:template match="node()" mode="ignoreTL">
  <xsl:apply-templates select="."/>
</xsl:template>

in this case $withoutStyleResult holds the results of processing these nodes as normally, except TLStyle is suppressed. If the resulting tree fragment passes the test (of having string content, presumably), copy it into your result; if it fails just drop it. Thus instead of using an xsl:choose you can often get away with an xsl:if, or even (niftiest of all) with a single cunningly constructed line:

<xsl:copy-of select="$withoutTLStyleResult[normalize-space()]"/>

... although some might argue that this is laying a little land mine for the poor maintenance programmer who has to figure out what it's doing. :-)

I hope this helps,
Wendell

Current Thread