xpath: select node closest to root - xpath

I need select a specific node name closest to (not needly on) root.
Example:
<root>
<a>
<b id="1"></b>
<b id="2">
<b id="3"></b>
</b>
<c>
<b id="4"></b>
</c>
</a>
</root>
It should select b#1, b#2 and b#4, but not b#2, because it is included inside of another b node.
Currently I'm doing that: select all b, so check if some of parents is b, if yes, discard that. But I do it hardcoded, maybe xpath can solve that alone?

I found the solution, just using not + ancestor, like:
//table[not(ancestor::table)]

I would try below expression-
//b[not(.//ancestor::b)]
It selects-
<b id="1"/>
<b id="4"/>
See live at here.

Related

Get parents attribute value if child doesn't have a specific attribute value

I have an xml file in linux that I want to process.
I need to get all ids of a parent nodes based on its children.
Here I want to get all id of 'a' that have 'c' without key "f.g".
<a id="11111">
<b>
<c key="d.e">stuff1</c>
<c key="f.g">stuff2</c>
<c key="j.k">stuff4</c>
</b>
</a>
<a id="22222">
<b>
<c key="d.e">stuff1</c>
<c key="h.i">stuff3</c>
<c key="j.k">stuff4</c>
<c key="l.m">stuff5</c>
</b>
</a>
<a id="33333">
<b>
<c key="c.d">stuff0</c>
<c key="d.e">stuff1</c>
<c key="h.i">stuff3</c>
<c key="j.k">stuff4</c>
<c key="l.m">stuff5</c>
</b>
</a>
In this case I should be getting 22222 and 33333.
I'm not really sure how to write the xpath for this.
I think you are looking for something like:
//a[not(.//c[#key="f.g"])]/#id
which can be translated as: find any node <a> which does NOT have a child node <c> which itself has an attribute called key which itself has an attribute value of "f.g".
You can filter by (not):
//a[[not(#key = 'f.g')]]
It will return you needed 'a' elements, but I don't know how to get their ids.
#Jack Fleeting's answer is probably the best solution. As an alternative (more consuming) :
//c[not(#key="f.g" or preceding-sibling::c[#key="f.g"] or following-sibling::c[#key="f.g"])]/ancestor::a
Look for c elements where itself, and preceding or following siblings contain an attribute different from #key="f.g". Then select their a ancestors.

Why doesn't //* return the document node?

I am trying to understand the following example
<?xml version="1.0" encoding="UTF-8"?>
<c>
<a>
<b att1="5">
<c/>
</b>
<d/>
</a>
<a att1="10">
<d>
<c/>
</d>
<b/>
</a>
</c>
Now I run the XPath query
//*[c]
which I take to mean "All nodes that have a child that is a c". However, this returns only the <b> and <d> nodes that have a <c> child without returning the Document node as I expected. Can anyone explain why?
Because //* equivalent to /descendant-or-self::node()/*. Notice that the document node referenced by self::node() in the previous XPath, so the outer most node selected by that XPath would be the child of the document node (due to /*), which is the root element c, which doesn't have direct child c, hence didn't get selected.
You want /descendant-or-self::node()[c] to include the document node, which is equivalent to //.[c], see the demo.

Ruby: How to find XML child element from specific element using xpath?

Given xml
<a>
<b key=1>
<c value=xxx />
</b>
<b key=2>
<c value=yyy />
</b>
</a>
Goal: Get each "b" first, then get the "c" under that "b", like result below. With XPath for searching child.
for <b key=1>
<c value=xxx />
for <b key=2>
<c value=xxx />
but below code
b_elements = XPath.match(xml, "//b[#key]")
b_elements.each do |b_element|
puts b_element.elements["//c"]
end
will result in yeilding
for <b key=1>
<c value=xxx />
<c value=yyy />
for <b key=2>
<c value=xxx />
<c value=yyy />
instead of just getting the "c" under each "b"
I had tried below method but no luck, seems that if using Xpath, it will automatically search from root element
b.get_elements("//c")
XPath.first(b, "//c")
My workaround now is traverse child element 1 layer at a time and search for desired key, which seems quite stupid comparing to using XPath.
Please advise, thanks : )
Reference:
http://ruby-doc.org/stdlib-1.9.3/libdoc/rexml/rdoc/REXML/Element.html#method-i-each_element_with_attribute
Not sure here, but my assumption is that XPath looks at the first char, sees that it is a /, and thinks that the path is absolute (because the path starting with / is meant to be absolute).
Probably you can force the path to be relative by using a . before //, so the parser doesn't confuse // for /?
I mean, instead of "//c" use ".//c"? Hope this helps.

Web config transformation condition/match to select a node based on parent node attribute

I have a transform that looks like this
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<a>
<b>
<c>
<d>
<e name="UpdateLanguageProfile">
<f xdt:Transform="Replace" xdt:Locator="Condition(/..#name='UpdateLanguageProfile')">
stuff here
</f>
</e>
</d>
</c>
</b>
</a>
So I want the xdt:Locator to select the f node only if the parent node has an attribute with the specified value.
The xdt:Locator gets translated into the following xpath expression:
/a/b/c/d/e/f[/..#name='UpdateLanguageProfile']
Which is invalid.
So the question is, what could I put in the Condition, that is the XPath square brackets, in order to select the f node based on an attribute in the parent node.
The answer is that the xdt:Locator and the xdt:Transform do not need to be on the same node. They just happen to be on the same node in every example I've ever seen.
You can do this:
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<a>
<b>
<c>
<d>
<e name="UpdateLanguageProfile" xdt:Locator="Match(name)">
<f xdt:Transform="Replace">
stuff here
</f>
</e>
</d>
</c>
</b>
</a>

XPath - Get node with no child of specific type

XML: /A/B or /A
I want to get all A nodes that do not have any B children.
I've tried
/A[not(B)]
/A[not(exists(B))]
without success
I prefer a solution with the syntax /*[local-name()="A" and .... ], if possible. Any ideas that works?
Clarification. The xml looks like:
<WhatEver>
<A>
<B></B>
</A>
</WhatEver>
or
<WhatEver>
<A></A>
</WhatEver>
Maybe
*[local-name() = 'A' and not(descendant::*[local-name() = 'B'])]?
Also, there should be only one root element, so for /A[...] you're either getting all your XML back or none. Maybe //A[not(B)] or /*/A[not(B)]?
I don't really understand why /A[not(B)] doesn't work for you.
~/xml% xmllint ab.xml
<?xml version="1.0"?>
<root>
<A id="1">
<B/>
</A>
<A id="2">
</A>
<A id="3">
<B/>
<B/>
</A>
<A id="4"/>
</root>
~/xml% xpath ab.xml '/root/A[not(B)]'
Found 2 nodes:
-- NODE --
<A id="2">
</A>
-- NODE --
<A id="4" />
Try this "/A[not(.//B)]" or this "/A[not(./B)]".
The first / causes XPath to start at the root of the document, I doubt that is what you intended.
Perhaps you meant //A[not(B)] which would find all A nodes in the document at any level that do not have a direct B child.
Or perhaps you are already at a node that contains A nodes in which case you just want A[not(B)] as the XPath.
If you are trying to get A anywhere in the hierarchy from the root, this works (for xslt 1.0 as well as 2.0 in case its used in xslt)
//descendant-or-self::node()[local-name(.) = 'a' and not(count(b))]
OR you can also do
//descendant-or-self::node()[local-name(.) = 'a' and not(b)]
OR also
//descendant-or-self::node()[local-name(.) = 'a' and not(child::b)]
There are n no of ways in xslt to achieve the same thing.
Note: XPaths are case-sensitive, so if your node names are different (which I am sure, no one is gonna use A, B), then please make sure the case matches.
Use this:
/*[local-name()='A' and not(descendant::*[local-name()='B'])]

Resources