Remove parent element if there are no children


C

CI

I have the following XML file

<Book>
<Chapters>
<Chapter number="1"/>
<Chapter number="2"/>
</Chapters>
<Colors>
<Color RGB="255"/>
<Color RGB="211" name="Red"/>
<Color name="Yellow"/>
<Color RGB="127"/>
</Colors>
</Book>

I want to get a tree with the <Color> elements that don't have RGB
attribute:

So I came up with this XSLT sheet.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/
Transform">


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


<xsl:template match="Color">

<xsl:choose>
<xsl:when test="@RGB">

</xsl:when>
<xsl:eek:therwise>
<xsl:copy-of select="."/>
</xsl:eek:therwise>
</xsl:choose>

</xsl:template>

</xsl:stylesheet>

It works correctly, but if in my XML file all the <Color> elements
have RGB attribute the resulted tree
contains empty <Colors></Colors> node.

Anyone had an idea how to conditionally remove it?

Regards,

Michael
 
Ad

Advertisements

J

Joseph Kesselman

Anyone had an idea how to conditionally remove it?

Adding a check for no-children-exist to your RGB-attribute-exists test
ought to do it, right?
 
J

Joseph Kesselman

said:
<Color RGB="255"/>
<Color RGB="211" name="Red"/>
<Color name="Yellow"/>
<Color RGB="127"/>
</Colors>
It works correctly, but if in my XML file all the <Color> elements
have RGB attribute the resulted tree
contains empty <Colors></Colors> node.

Anyone had an idea how to conditionally remove it?

Don't think about "removing", think about "not copying".

You want to copy the <Colors> element only if it contains something
other than a <Color> element with an RGB attribute.

Complication: Removing the <Color> elements from the above example does
NOT make <Colors> empty, even if you remove Yellow -- because it still
contains the whitespace text nodes between the elements.

It's possible to solve this in a single pass, but I think it's going to
be ugly. You may want to think about making it simpler by running a
second stylesheet that removes <Colors> elements that contain only
whitespace... or, if you're willing to rely on the nodeset extensions,
running two passes within a single stylesheet by first styling part or
all of the document into a variable and then styling from the variable
to your actual output. Look for examples of use of the exslt:node-set
extension for information about how to do this.
 
C

CI

Don't think about "removing", think about "not copying".

You want to copy the <Colors> element only if it contains something
other than a <Color> element with an RGB attribute.

Complication: Removing the <Color> elements from the above example does
NOT make <Colors> empty, even if you remove Yellow -- because it still
contains the whitespace text nodes between the elements.

It's possible to solve this in a single pass, but I think it's going to
be ugly. You may want to think about making it simpler by running a
second stylesheet that removes <Colors> elements that contain only
whitespace... or, if you're willing to rely on the nodeset extensions,
running two passes within a single stylesheet by first styling part or
all of the document into a variable and then styling from the variable
to your actual output. Look for examples of use of the exslt:node-set
extension for information about how to do this.

Thanks Joe, I appreciate it.

I posted this problem to another group and Dimitre Novatchev
suggested the following stylesheet:

------------------------------------------------------------------------------------------
<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="Colors[not(Color[not(@RGB)])]"/>
<xsl:template match="Color[@RGB]"/>
</xsl:stylesheet>
------------------------------------------------------------------------------



Regards,

Michael
 
J

Joe Kesselman

CI said:
<xsl:template match="Colors[not(Color[not(@RGB)])]"/>

OK, that will certainly discard <Colors> elements that do not contain a
<Color> which does not have the RGB attribute. If the only things that
will be in <Colors> is <Color> elements and whitespace, that should
work. If there's anything else which might appear there, you may wind up
deleting some elements that you wanted to keep.
 
D

Dimitre Novatchev

Joe Kesselman said:
CI said:
<xsl:template match="Colors[not(Color[not(@RGB)])]"/>

OK, that will certainly discard <Colors> elements that do not contain a
<Color> which does not have the RGB attribute. If the only things that
will be in <Colors> is <Color> elements and whitespace, that should work.

Of course, and this is exactly what the OP wanted! :eek:)


Cheers,
Dimitre Novatchev
 
Ad

Advertisements

P

p.lepin

On Apr 13, 4:59 pm, Joseph Kesselman
[...]
Don't think about "removing", think about "not
copying".
[...]

I posted this problem to another group and Dimitre
Novatchev suggested the following stylesheet:

<xsl:template match="Colors[not(Color[not(@RGB)])]"/>
<xsl:template match="Color[@RGB]"/>

This is a very clean and neat solution,--which is hardly
surprising,--but I always cringe a bit when I have to
express the same thing twice in the code. The following
looks a bit scarier, but has what counts as virtue with me:
it's re-uses the condition that both your rules are
dependent upon:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Colors">
<xsl:variable name="color-elements"
select="Color[not(@RGB)]"/>
<xsl:if test="$color-elements">
<xsl:copy>
<xsl:apply-templates select="$color-elements"/>
</xsl:copy>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

YMMV.
 
P

p.lepin

This is a very clean and neat solution,--which is hardly
surprising,--but I always cringe a bit when I have to
express the same thing twice in the code. The following
looks a bit scarier, but has what counts as virtue with me:
it's re-uses the condition that both your rules are
dependent upon:

On a side note, XSLT2 is bliss:

<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:variable name="post-pass-1">
<xsl:apply-templates mode="pass-1"/>
</xsl:variable>
<xsl:apply-templates select="$post-pass-1"
mode="pass-2"/>
</xsl:template>
<xsl:template match="@*|node()" mode="pass-1">
<xsl:copy>
<xsl:apply-templates select="@*|node()"
mode="pass-1"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Color[@RGB]" mode="pass-1"/>
<xsl:template match="@*|node()" mode="pass-2">
<xsl:copy>
<xsl:apply-templates select="@*|node()"
mode="pass-2"/>
</xsl:copy>
</xsl:template>
<xsl:template match="Colors[not(*)]" mode="pass-2"/>
</xsl:stylesheet>
 
J

Joe Kesselman

On a side note, XSLT2 is bliss:

A minor change, but a good one: XSLT removed the distinction between
Result Tree Fragments and Node Sets (both are now grouped together as
Temporary Trees), so one doesn't need the node-set extension to convert
the former into the latter.

Outside of that, this is the same two-past solution I suggested.
 
J

Joe Kesselman

Dimitre said:
Of course, and this is exactly what the OP wanted! :eek:)

Granted. But I tend to practice defensive programming, so I look for the
maximally robust solution...
 
D

Dimitre Novatchev

Joe Kesselman said:
A minor change, but a good one: XSLT removed the distinction between
Result Tree Fragments and Node Sets (both are now grouped together as
Temporary Trees), so one doesn't need the node-set extension to convert
the former into the latter.

Outside of that, this is the same two-past solution I suggested.


C'mon people,

Do you know how much additional memory such a solution may use? Twice the
memory of one-pass solution.

Where possible, do try to "fuse" (the term is "fusion" or "deforestation") a
pipeline of passes into a single pass.

And, of course, I am not rediscovering the wheel here...


Cheers,
Dimitre Novatchev.
 
Ad

Advertisements

J

Joe Kesselman

Dimitre said:
Do you know how much additional memory such a solution may use? Twice the
memory of one-pass solution.

It may, or it may not, depending on the internal data model of the
processor. And for simple input documents, that really may not matter.

In fact, what I'd do if taking this approach was to do two-pass
processing only locally, on the element which requires it -- so the
overhead is both smaller and transient.

It can be taken further... but see first paragraph; before spending too
much effort on optimization, consider whether the improvement is worth
the effort. Learn to do it right, but also learn when good enough is
good enough.
 
R

roy axenov

message




Do you know how much additional memory such a solution
may use? Twice the memory of one-pass solution.

Where possible, do try to "fuse" (the term is "fusion" or
"deforestation") a pipeline of passes into a single pass.

I don't know about you, but premature optimization is a sin
in my book. Go for clarity and neatness first, deal with
performance issues as they arise.
 
J

Joe Kesselman

roy said:
I don't know about you, but premature optimization is a sin
in my book. Go for clarity and neatness first, deal with
performance issues as they arise.

Especially when still teaching basic concepts.

Steve Boies: "Make it work, make it good, make it great."
(Chorus response: "In that order!")
 
D

Dimitre Novatchev

Joe Kesselman said:
Especially when still teaching basic concepts.

Steve Boies: "Make it work, make it good, make it great."
(Chorus response: "In that order!")

Yes, and following all these recommendations I'd still prefer the most
compact and elegant solution (sorry, it happens to be the one I proposed
) )

I didn't spend any time trying to optimize -- just chose the most *natural*
solution, which for XSLT happens to be overriding the identity rule -- and
this is so for more than 90% of all cases.


Cheers,
Dimitre Novatchev.
 
Ad

Advertisements

J

Joe Kesselman

Dimitre said:
I didn't spend any time trying to optimize -- just chose the most *natural*
solution, which for XSLT happens to be overriding the identity rule -- and
this is so for more than 90% of all cases.

I agree that this is the core starting point for most XSLT design.
 
Ad

Advertisements


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

Top