[xsl] RE:ignoring a child element

Subject: [xsl] RE:ignoring a child element
From: <Emily.Garrett@xxxxxxxxxxx>
Date: Fri, 8 Sep 2006 09:23:32 -0400
Wendell,


I tried the first suggestion,

<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"/>

 and it worked like a charm!

Thanks,

Emily

__________________________________________________________

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