Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

xpath to find branches of arbitrary depth but no siblings?

1 view
Skip to first unread message

Mark

unread,
Sep 22, 2004, 6:07:03 PM9/22/04
to
Hi...

I've been beating my head trying to come up with an xpath that will find a
node at the bottom of a tree with arbitrary depth but where the tree is only
a singleton tree. For example, taking this xml as a sample
<a>
<b><c><d><e><x/></e></d></c></b>
<b><c><x/></c><c><x/></c></b>
<b><c><x/><x/></c></b>
</a>

I want an xpath that will find the x node from the first <b> line, but not
the others. The x node is buried at an arbitrary depth, but when you find it
down there, all of the ancestors have only one child nodes.

The 2nd b line should fail because there are 2 <c> nodes at the 2nd level.
The 3rd should fail because the x node has a sibling.

I've tried using the descendant axis with a qualifier but the qualifier
doesn't seem to be applied all the way down.

For example I tried "./*/descendant-or-self::*[count (./*) = 1]/x" figuring
that the qualifier would be applied to the nodeset all of the way down, but
it's only applied to one level. I get the first <b> match AND i get the 2nd
line, too, even though the <b> node on the 2nd line has 2 children, not one.
The children of <b> on the 2nd line only have one child, but the middle level
fails, in my opinion because the 2nd level has more than one child.

I tried going backwards, using ".//x[count(ancestor::*/*) = 1 or
not(ancestor::*/..)]". Essentially, find all of the x nodes where every
ancestor has only one child or that ancestor is the root. But that never
selected anything.

I know this is a quirky request, but I was wondering if anyone knew how it
might be phrased?

Thanks
-Mark

Oleg Tkachenko [MVP]

unread,
Sep 23, 2004, 7:24:31 AM9/23/04
to
Mark wrote:

> I've been beating my head trying to come up with an xpath that will find a
> node at the bottom of a tree with arbitrary depth but where the tree is only
> a singleton tree. For example, taking this xml as a sample
> <a>
> <b><c><d><e><x/></e></d></c></b>
> <b><c><x/></c><c><x/></c></b>
> <b><c><x/><x/></c></b>
> </a>
>
> I want an xpath that will find the x node from the first <b> line, but not
> the others. The x node is buried at an arbitrary depth, but when you find it
> down there, all of the ancestors have only one child nodes.

Well, but element a has 3 child nodes - so according to your description
no node should be selected.
If you need to omit root element, then you can try something
straightforward like
/a/descendant::x[not(ancestor::*[parent::*][count(*)>1])]

--
Oleg Tkachenko [XML MVP]
http://blog.tkachenko.com

Mark

unread,
Sep 23, 2004, 11:33:05 AM9/23/04
to
Thank you for your reply. I tried your suggestion and it worked. But at
least conceptuallly it seems similar to some of the other things I tried and
I was wondering if you might help explain where my formulas went wrong? I
appreciate the help, and I'm just trying to learn.

"./*/descendant-or-self::*[count (./*) = 1]/x"

On this one, I thought I was getting past the root (<a>) node having
multiple children with the ./*/... but looking at it again this morning,
descendant-or-self would undo that restriction. Yet the problem with this
xpath was that it found too many, not too few (it found the x's in both the
first and second lines). My reasoning was that if the descendant qualifier
([count(./*) = 1]) was applied on all the nodes in the descendant chain, it
would eliminate the 2nd line because <b> has 2 children, not one. But the
2nd line got found anyway, so that test wasn't doing what I thought it would.
What was wrong with my reasoning?

".//x[count(ancestor::*/*) = 1 or not(ancestor::*/..)]"

This is as close as I got to your construct, but it found nothing at all.
The reasoning on this one was "find all of the x's where the ancestor
collection had all single-child nodes or were not the root." Obviously I was
thinking about something wrong here,
but I though "count(ancestor::*/*) = 1" would be the "go up one level and
see if the parent has only one child" part of the test and
"not(ancestor::*/..)" would be the root test when you got to the top (the
root having no parent).

It looks like your construct has similar logic except for two things
1) the explicit /a/ which explicitly excuses the fact that /a/ has multiple
children and

2) the 2nd level test on the ancestor axis (i.e. the *parent* of the
ancestor has to have only one child. I'd hoped/figured that invoking the
count(ancestor::*/*) = 1 would have a similar effect in going up the
parentage chain and looking at the child count. Is the problem that all the
nodes ancestor::*/* (basically the whole tree) get collected before count()
is applied? By adding the 2nd conditional bringing in the parent axis, I
guess you explicitly got the test that I was looking for.

Thanks
-mark

Mark

unread,
Sep 23, 2004, 12:55:10 PM9/23/04
to
Sorry to be a pest, but I was working with your example:

/a/descendant::x[not(ancestor::*[parent::*][count(*)>1])]

I tried rephrasing it a little as
/a/descendant::x[ancestor::*[count(parent::*/*)=1]]
figuring this would be a little clearer and forthright - looking for child
count = 1 rather than not(child count > 1). But the rephrasing ended up also
selecting the x's on line 3 (the one with 2 x siblings and the parent with
more than one child).

Thinking that collapsing the double [] might have spoiled something, I tried
rephrasing it with the same structure but with =1 rather than not ( > 1), and
it ended up getting *all* of the x's.
/a/descendant::x[ancestor::*[parent::*][count(*)=1]]

what is it about the not() structure that makes it work? What does the
separate conditional [][] accomplish?

Thanks
-mark

0 new messages