tags:

views:

295

answers:

7

I'm trying to have an XSLT that copies most of the tags but removes empty "<b/>" tags. That is, it should copy as-is "<b> </b>" or "<b>toto</b>" but completely remove "<b/>".

I think the template would look like :

<xsl:template match="b">
  <xsl:if test=".hasChildren()">
    <xsl:element name="b">
      <xsl:apply-templates/>
    </xsl:element>
  </xsl:if>
</xsl:template>

But of course, the "hasChildren()" part doesn't exist ... Any idea ?

+2  A: 

I wonder if this will work?

<xsl:template match="b">
  <xsl:if test="b/text()">
    ...
dsteinweg
Almost works, removes <b/> as well as <b></b>, but leaves <b> </b> (with a space in the middle). Good enough for me. Thanks !
Guillaume
Oops, it also removes <b><br/></b> which I dont want ...
Guillaume
+3  A: 

dsteinweg put me on the right track ... I ended up doing :

<xsl:template match="b">
 <xsl:if test="./* or ./text()">
  <xsl:element name="b">
   <xsl:apply-templates/>
  </xsl:element>
 </xsl:if>
</xsl:template>
Guillaume
You could remove the "./"s, since the context at that point is already the "b" element.
James Sulak
+1  A: 

See if this will work.

<xsl:template match="b">
  <xsl:if test=".!=''">
    <xsl:element name="b">
     <xsl:apply-templates/>
    </xsl:element>
  </xsl:if>
</xsl:template>
Vincent Ramdhanie
A: 

If you have access to update the original XML, you could try using use xml:space=preserve on the root element

<html xml:space="preserve">
...
</html>

This way, the space in the empty <b> </b> tag is preserved, and so can be distinguished from <b /> in the XSLT.

<xsl:template match="b">
   <xsl:if test="text() != ''">
   ....
   </xsl:if>
</xsl:template>
Tim C
+1  A: 

An alternative would be to do the following:

<xsl:template match="b[not(text())]" />

<xsl:template match="b">
  <b>
    <xsl:apply-templates/>
  </b>
</xsl:template>
samjudson
+1  A: 

You could put all the logic in the predicate, and set up a template to match only what you want and delete it:

<xsl:template match="b[not(node())] />

This assumes that you have an identity template later on in the transform, which it sounds like you do. That will automatically copy any "b" tags with content, which is what you want:

<xsl:template match="@*|node()">
 <xsl:copy>
  <xsl:apply-templates select="@*|node()"/>
 </xsl:copy>
</xsl:template>

Edit: Now uses node() like Dimitri, below.

James Sulak
+2  A: 

This transformation ignores any <b> elements that do not have any node child. A node in this context means an element, text, comment or processing instruction node.

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes"/>
    <xsl:template match="node()|@*">
      <xsl:copy>
         <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
    </xsl:template>

    <xsl:template match="b[not(node()]"/>
</xsl:stylesheet>

Notice that here we use one of the most fundamental XSLT design patterns -- using the identity transform and overriding it for specific nodes.

The overriding template will be selected only for nodes that are elements named "b" and do not have (any nodes as) children. This template is empty (does not have any contents), so the effect of its application is that the matching node is ignored/discarded and is not reproduced in the output.

This technique is very powerful and is widely used for such tasks and also for renaming, changing the contents or attributes, adding children or siblings to any specific node that can be matched (avery type of node with the exception of a namespace node can be used as a match pattern in the "match" attribute of <xsl:template/>

Hope this helped.

Cheers,

Dimitre Novatchev

Dimitre Novatchev