XSL: how to remove nodes from the XML tree? (advanced)

P

pavel.repkin

Hey!
How would you do the following task?

Let you have an XML tree on input.
Suppose, there is a special kind of node you want to remove.
Let it have "bad" name.
Each "bad" node has a parent node, obviously.
In case there are no children left in the parent after removal of all
the "bad" nodes, the parent must also be removed.
And this rule is applied to all the ancetors of the "bad" node
recursively.

How would you do this in XSLT?
I don't know :(

Example input:
<a>
<b>
<c>
<bad/>
<bad/>
</c>
</b>
<d>
<bad/>
</d>
<e/>
</a>

Desired output:
<a>
<e/>
</a>

Pasha
 
J

Joseph Kesselman

Standard approach: Start with the identity transform, then add templates
for anything that doesn't want to simply be copied over.

You want to discard any node that contains <bad/> somewhere in its
subtree. Those nodes can be expressed as *[.//bad]. Write a template
that matches that and outputs nothing.

Exception: You want to keep the top-level element. Write a template that
explicitly matches it and always outputs it, recursively processing its
contents. Or modify the "anything containing bad" pattern to explicitly
not match the top-level element.

Details are left as an exercise for the student.
 
R

roy axenov

Suppose, there is a special kind of node you want to
remove. Let it have "bad" name. Each "bad" node has a
parent node, obviously. In case there are no children left
in the parent after removal of all the "bad" nodes, the
parent must also be removed. And this rule is applied to
all the ancetors of the "bad" node recursively.

How would you do this in XSLT?

Try reading XPath/XSLT tutorials. Note that this is a bit
tricky to implement in XSLT1, you would need some fairly
evil XPath expressions to filter out unneeded nodes. XSLT2
would make things much easier for you. Reading something
about identity transformation and exclusion templates
should be extremely useful.

Just for the heck of it:

<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template
match=
"
*
[..][descendant::bad]
[not(descendant::*[not(*)][not(self::bad)])]
"/>
</xsl:stylesheet>

Hm, let's see...

"bad.xml" 12L, 96C written
xsltproc bad.xsl bad.xml
<?xml version="1.0"?>
<a>



Yep. It even seems to work on your sample document.

Oh, and stop using the google groups. GG never worked all
that well for posting on the usenet newsgroups, but it got
beyond bad in the last few days--seems like their ng
archives suddenly broke down in a fairly spectacular
fashion, and no one even bothered to fix them.
 
M

M

Hi Roy,

Not quite there...

If you apply your stylesheet to...
<a>
<bad/>
<b>
<c>
<bad/>
<bad/>
</c>
</b>
<d>
<bad/>
</d>
<e/>
</a>

You get left with a <bad> element left in.

I think the empty template needs to be...
<xsl:template match="*[descendant-or-self::bad and parent::*]"/>


Cheers
M

roy axenov said:
Suppose, there is a special kind of node you want to
remove. Let it have "bad" name. Each "bad" node has a
parent node, obviously. In case there are no children left
in the parent after removal of all the "bad" nodes, the
parent must also be removed. And this rule is applied to
all the ancetors of the "bad" node recursively.

How would you do this in XSLT?

Try reading XPath/XSLT tutorials. Note that this is a bit
tricky to implement in XSLT1, you would need some fairly
evil XPath expressions to filter out unneeded nodes. XSLT2
would make things much easier for you. Reading something
about identity transformation and exclusion templates
should be extremely useful.

Just for the heck of it:

<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template
match=
"
*
[..][descendant::bad]
[not(descendant::*[not(*)][not(self::bad)])]
"/>
</xsl:stylesheet>

Hm, let's see...

"bad.xml" 12L, 96C written
xsltproc bad.xsl bad.xml
<?xml version="1.0"?>
<a>



Yep. It even seems to work on your sample document.

Oh, and stop using the google groups. GG never worked all
that well for posting on the usenet newsgroups, but it got
beyond bad in the last few days--seems like their ng
archives suddenly broke down in a fairly spectacular
fashion, and no one even bothered to fix them.
 
D

Dimitre Novatchev

As replied in another newsgroup, here is one solution:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:eek:utput omit-xml-declaration="yes" indent="yes"/>

<xsl:strip-space elements="*"/>

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

<xsl:template match="bad"/>

<xsl:template match=
"*[* and not(descendant::*[not(*) and not(self::bad)])]"/>
</xsl:stylesheet>

Let's have a little bit more complex xml, such as this one:

<a>
<b>
<c>
<bad/>
<bad/>
</c>
</b>
<d>
<bad/>
</d>
<e/>
<f>
<bad/>
<good/>
<bad/>
</f>
</a>


The transformation above produces the required result:

<a>
<e/>
<f>
<good/>
</f>
</a>


Cheers,
Dimitre Novatchev
 
P

Pavel Lepin

M said:
roy axenov said:
<?xml version="1.0"?>
<a>




Yep. It even seems to work on your sample document.

I think the empty template needs to be...
<xsl:template match="*[descendant-or-self::bad and
parent::*]"/>

That would remove any element that has any <bad/>
descendants. I don't believe that's what the OP was asking
for.
 
P

pavel.repkin

Dimitre, thank you so much, the transformation works perfectly!
Besides, I have even managed to understand how does it work. :) Hope
to use this technique in future.

Very professional, thanks!
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top