views:

105

answers:

3

Hello all, My first post here - it's a great site and I will certainly do my best to give back as much as I can.

I have seen different manifestations of this following question; however my attempts to resolve don't appear to work.

Consider this simple tree:

<root>
    <div>
        <p>hello</p>
        <p>hello2</p>
        <p><span class="bad">hello3</span></p>
    </div>
</root>

I would like to come up with an XPath expression that will select all child nodes of "div", except for elements that have their "class" attribute equal to "bad".

Here is what I have tried:

/root/div/node()[not (@class='bad')]

... However this doesn't seem to work.

What am I missing here?

Cheers,
Isaac

+1  A: 

When testing your XPath here with the provided XML document, the XPath seems to be indeed selecting all child nodes that do not have an attribute class="bad" - these are all the <p> elements in the document.

You will note that the only child node that has such an attribute is the <span>, which indeed does not get selected.

Are you expecting the p node surrounding your span not to be selected?

Oded
Shalom Oded,Interesting link - thanks. Now, the example I gave above is a minimal representation of a huge XML tree I'm working with. Trying to execute the same XPath expression using Java doesn't seem to work.The "p" element should be selected; indeed, all I wanted was for that "span" element to be thrown out.I should investigate further as to why my XPath expression doesn't work for my specific problem; will post here if I come up with anything useful. Thanks!
Isaac
@Isaac: We are trying to tell you that `span` element will **never** be selected because it's not a child of `div` but a child of `p`.
Alejandro
@Oded: I think you have a typo somewhere in your first sentence, because this `Child nodes means all child nodes, whatever their depth, not just direct descendants` is wrong.
Alejandro
@Alejandro: well DUH. Good catch. Can't believe I wasted so much time because of a missing slash... Thank you!
Isaac
A: 

The identity transform just matches and copies everything:

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

But you add a null transform that more specifically matches the pattern you want to exclude:

 <xsl:template match="span[@class='bad']" />

( you can also add a priority attrib if you want to be more explicit about which one has precedence. )

Steven D. Majewski
A: 

Welcome to SO, Isaac!

I'd try this:

/root/div/*[./*[@class != "bad"]]

this ought to select all child elements (*) of the div element that do not have a descendant element with a class attribute that equals bad.

Edit:

As per @Alejandros comment:

/root/div/*[not(*/@class "bad")]
FK82
@FK82: I think that a more proper path for what you are trying is `/root/div/*[not(*/@class = "bad")]`. Do note that @class != "bad" will be false for those elements not having @class...
Alejandro
Well I tried `/root/div/node()[not (descendant-or-self::*[@class="bad"])]` when I thought that was what he wanted, but he seems to be saying in the comments above that he wants to include the outer <p> containing the <span class="bad">, but filter out the inner <span class="bad"> .
Steven D. Majewski
@Alejandro: how about this?
FK82
@Steven D. Majewski: that doesn't make sense at all. I figure there is nothing to add then.
FK82
@FK82: Again, `@class and @class != "bad"` will be false for those element having not @class, but you want to exclude only those having @class equal to "bad". So `@class="bad"` is true for those you want to exclude, then `not(@class="bad")` is true for those you want to keep: `!=` and `=` operator are not reversible for node set comparison. And last, starting a relative path expression with `.` is almost never useful.
Alejandro
@ Alejandro: Yes.
FK82