RE: [xsl] Filter out elements that have one specific sub-element and nothing else

Subject: RE: [xsl] Filter out elements that have one specific sub-element and nothing else
From: "Michael Kay" <mike@xxxxxxxxxxxx>
Date: Thu, 22 Feb 2007 08:38:15 -0000
> I created a test sheet that tried all suggested methods, all 
> seem to work. 
> 
> The big question now: which one would be the most efficient 
> (fastest) one?

A bigger question is whether they are all correct. This one at least seems
wrong, because it has no check for text nodes:

listitem[formatinfo][count(child::*) = 1]

while this solution appears to allow more than one formatinfo child:

match="listitem[formatinfo and not(*[not(self::formatinfo)]) and 
           (normalize-space() = '')]"

(It also makes the assumption that the formatinfo element will be empty:
this was true of your examples but wasn't stated as a constraint in your
specification of the problem.)

The performance factor will depend on the processor you are using, and also
on the data. For example, the solution that uses normalize-space() is likely
to be very expensive if some of the nodes you are rejecting contain large
amounts of text.

Michael Kay
http://www.saxonica.com/



> 
> Here is the sheet for reference:
> 
> <?xml version="1.0" encoding="UTF-8"?>
> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"; 
> version="1.0">
> 
>     <xsl:output method="xml" indent="yes"/>
> 
>     <xsl:template match="/">
>         <bigresult>
>             <take1>
>                 <xsl:comment>match="listitem [formatinfo [ 
> not (sibling::* 
> | 
>                     sibling::text()[normalize-space()]) 
> ]]"</xsl:comment>
>                 <xsl:apply-templates/>
>             </take1>
>             <take2>
>                 <xsl:comment>match="listitem[formatinfo and
>                     not(*[not(self::formatinfo)]) and 
> (normalize-space() = '')]"</xsl:comment>
>                 <xsl:apply-templates mode="take2"/>
>             </take2>
>             <take3>
>                 
> <xsl:comment>match="listitem[formatinfo][count(child::*) = 
> 1]"</xsl:comment>
>                 <xsl:apply-templates mode="take3"/>
>             </take3>
>             <take4>
>  
> <xsl:comment>select="listitem[node()[last()=1][self::formatinf
> o]]"</xsl:comment>
>                 <xsl:apply-templates mode="take4"/>
>             </take4>
>         </bigresult>
> 
>     </xsl:template>
>     <!-- Clearing out the unwanted elementes one by one -->
>     <!-- take 1-->
>     <xsl:template match="listitem [formatinfo [ not 
> (following-sibling::* 
> | following-sibling::text()[normalize-space()]) ]]" />
> 
>     <!-- take 2-->
>     <xsl:template mode="take2" match="listitem[formatinfo and
> not(*[not(self::formatinfo)]) and (normalize-space() = '')]" />
>  
>     <!-- take 3-->
>     <xsl:template mode="take3" 
> match="listitem[formatinfo][count(child::*)
> = 1]" />
>  
>     <!-- take 4-->
>     <xsl:template mode="take4" 
> match="llistitem[node()[last()=1][self::formatinfo]]" />
>  
>  
>  
> <!-- Standard copy templates for the rest-->
>     <xsl:template match="node() | @*">
>         <xsl:copy>
>             <xsl:apply-templates select="node() | @*"/>
>         </xsl:copy>
>     </xsl:template>
> 
>     <xsl:template match="node() | @*" mode="take2">
>         <xsl:copy>
>             <xsl:apply-templates select="node() | @*"/>
>         </xsl:copy>
>     </xsl:template>
>  
>     <xsl:template match="node() | @*" mode="take3">
>         <xsl:copy>
>             <xsl:apply-templates select="node() | @*"/>
>         </xsl:copy>
>     </xsl:template>
>     <xsl:template match="node() | @*" mode="take4">
>         <xsl:copy>
>             <xsl:apply-templates select="node() | @*"/>
>         </xsl:copy>
>     </xsl:template>
>  
> </xsl:stylesheet>
> 
> "Michael Kay" <mike@xxxxxxxxxxxx> wrote on 02/21/2007 09:28:43 PM:
> 
> > select="listitem[node()[last()=1][self::formatinfo]]"
> > 
> > Michael Kay
> > 
> > 
> > > -----Original Message-----
> > > From: stephan@xxxxxxxxxx [mailto:stephan@xxxxxxxxxx]
> > > Sent: 21 February 2007 13:05
> > > To: xsl-list@xxxxxxxxxxxxxxxxxxxxxx
> > > Subject: [xsl] Filter out elements that have one specific 
> > > sub-element and nothing else
> > > 
> > > Hi there,
> > > 
> > > I have an XML file like this:
> > > 
> > > <?xml version="1.0" encoding="UTF-8"?> <funnylist>
> > >     <listitem>
> > >         <formatinfo color="yellow" />
> > >         <stuffinside>Info</stuffinside> MoreInfo
> > >     </listitem>
> > >     <listitem>
> > >         <formatinfo color="blue" />
> > >     </listitem>
> > >     <listitem>
> > >         <formatinfo color="red" />EvenMoreInfo
> > >     </listitem>
> > > </funnylist>
> > > 
> > > I need to filter out this element:
> > >     <listitem>
> > >         <formatinfo color="blue" />
> > >     </listitem>
> > > 
> > > The rule: if listitem contains only formatinfo and no 
> other element 
> > > or text then remove it.
> > > 
> > > I have no clue how to formulate the xPath.
> > > Help appreciated.
> > > :-) stw

Current Thread