How to access the ancestor node in the same XQuery? - xpath

<results>
{
for $p in
(
for $o in doc("mondial-3.0.xml") /mondial/organization
where fn:count($o/members)
order by fn:count($o/members) descending
return <organization>
<name> {$o/#name/string()} </name>
<abbreviation> {$o/#abbrev/string()} </abbreviation>
<num_members> {fn:count($o/members)} </num_members>
<members> {for $m in doc("mondial-3.0.xml") $o/members
return <country> {mondial/country[#id=$m/#country]/#name/string()} </country>} </members>
</organization>
)[position() < 10]
return $p
}
</results>
I am unable to access the ancestor node in this problem as I have got an id stored of a parameter and now I want to match the id of that parameter and get a name of the parameter.
I am not getting any output for this. I am not sure where I am going wrong.
XML FILE :-
The link for the xml file is https://raw.githubusercontent.com/kroell/hsrm-mi-2semester-markuplanguage/master/Abgabe2/Aufgabe2/mondial-3.0.xml

In the link you posted, the root element of the XML is mondial not users, so I'd use mondial in this answer. Notice that member of organization and country are linked by country id, so you can do as follow to get country name of every member element :
<f>
{
for $m in $o/members
return <g> {mondial/country[#id=$m/#country]/#name/string()} </g>
}
</f>
Here is the complete working query. Tested in http://www.xpathtester.com/xquery using XML from the link posted in question as input :
<a>
{
for $p in
(
for $o in /mondial/organization
where fn:count($o/members)
order by fn:count($o/members) descending
return <b>
<c> {$o/#name/string()} </c>
<d> {$o/#abbrev/string()} </d>
<e> {fn:count($o/members)} </e>
<f> {for $m in $o/members
return <g> {/mondial/country[#id=$m/#country]/#name/string()} </g>} </f>
</b>
)[position() < 10]
return $p
}
</a>

You don't define the desired output of your query so it's hard to tell you why your query isn't giving the desired output; but there are several things about the query that suggest it's completely wrong.
Firstly, $o is always a single <mem> element, so fn:count($o) is always 1, so the use of this expression in the where and order by clauses cannot have any useful effect.
Secondly, the expression used to produce the content of the <f> element looks all wrong because it doesn't depend in any way on the value of $o.

Related

How to identify distinct xpath for an element that occurs in different level in XML document?

For example, I want to find all the unique xpath of element in below XML. Can you please help on identifying it using xquery or any other way
<a>
<b>
<identify>Level-1</identify>
</b>
<c>
<identify>Level-2</identify>
<d>
<identify>Level-3</identify>
<e>
<identify>Level-4-1</identify>
<identify>Level-4-2</identify>
</e>
<f>
<identify>Level-4</identify>
<g>
<identify>Level-5</identify>
<identify>Level-5-2</identify>
</g>
</f
</d>
</c>
Your xml is invalid, but assuming you fix it, try this (based on this):
xquery version "3.1";
declare namespace functx = "http://www.functx.com";
declare function functx:path-to-node
( $nodes as node()* ) as xs:string* {
$nodes/string-join(ancestor-or-self::*/name(.), '/')
} ;
let $in-xml :=
<a>
<b>
<identify>Level-1</identify>
</b>
<c>
<identify>Level-2</identify>
<d>
<identify>Level-3</identify>
<e>
<identify>Level-4-1</identify>
<identify>Level-4-2</identify>
</e>
<f>
<identify>Level-4</identify>
<g>
<identify>Level-5</identify>
<identify>Level-5-2</identify>
</g>
</f>
</d>
</c>
</a>
return
functx:path-to-node($in-xml//*[name()="identify"])
Output:
"a/b/identify"
"a/c/identify"
"a/c/d/identify"
"a/c/d/e/identify"
"a/c/d/e/identify"
"a/c/d/f/identify"
"a/c/d/f/g/identify"
"a/c/d/f/g/identify"
There is also the path function: //identify/path() would give
/Q{}a[1]/Q{}b[1]/Q{}identify[1]
/Q{}a[1]/Q{}c[1]/Q{}identify[1]
/Q{}a[1]/Q{}c[1]/Q{}d[1]/Q{}identify[1]
/Q{}a[1]/Q{}c[1]/Q{}d[1]/Q{}e[1]/Q{}identify[1]
/Q{}a[1]/Q{}c[1]/Q{}d[1]/Q{}e[1]/Q{}identify[2]
/Q{}a[1]/Q{}c[1]/Q{}d[1]/Q{}f[1]/Q{}identify[1]
/Q{}a[1]/Q{}c[1]/Q{}d[1]/Q{}f[1]/Q{}g[1]/Q{}identify[1]
/Q{}a[1]/Q{}c[1]/Q{}d[1]/Q{}f[1]/Q{}g[1]/Q{}identify[2]
for the example in Jack's answer: https://xqueryfiddle.liberty-development.net/nc4P6ya
The format is ugly for XML without namespaces but in other cases has the lengthy but working format to work without setting up any namespace bindings from prefixes to URIs.

How to get parent element with attribute using xpath

I have posted sample XML and expected output kindly help to get the result.
Sample XML
<root>
<A id="1">
<B id="2"/>
<C id="2"/>
</A>
</root>
Expected output:
<A id="1"/>
You can formulate this query in several ways:
Find elements that have a matching attribute, only ascending all the time:
//*[#id=1]
Find the attribute, then ascend a step:
//#id[.=1]/..
Use the fn:id($id) function, given the document is validated and the ID-attribute is defined as such:
/id('1')
I think it's not possible what you're after. There's no way of selecting a node without its children using XPATH (meaning that it'd always return the nodes B and C in your case)
You could achieve this using XQuery, I'm not sure if this is what you want but here's an example where you create a new node based on an existing node that's stored in the $doc variable.
declare variable $doc := <root><A id="1"><B id="2"/><C id="2"/></A></root>;
element {fn:node-name($doc/*)} {$doc/*/#*}
The above returns <A id="1"></A>.
is that what you are looking for?
//*[#id='1']/parent::* , similar to //*[#id='1']/../
if you want to verify that parent is root :
//*[#id='1']/parent::root
https://en.wikipedia.org/wiki/XPath
if you need not just parent - but previous element with some attribute: Read about Axis specifiers and use Axis "ancestor::" =)

Using back references with Xpath 1.0

I need to get a node based on another which is at the same level :(
Here is an example :
<A>
<B id=2>
<F1>aaa</F1>
<F2>bbb</F2>
<F3>ccc</F3>
</B>
<B>
....
</B>
<B>
....
</B>
<D>
<F1>aaa</F1>
<F2>bbb</F2>
<F3>ccc</F3>
<F4>yyy</F4>
</D>
<D>
...
</D>
</A>
So I need to get A/D/F4 where A/D/F1=A/B[#id=2]/F1 and A/D/F2=A/B[#id=2]/F2 and A/D/F3=A/B[#id=2]/F3
I only have the id to execute the query and I get the node B as a starting point (returned by other internal code). I was thinking about a way to get a back reference to a node in the path that means I could use something like :
/A/B[#id=2]/../D[F1=Bref/F1 and F2=Bref/F2 and F3=Bref/F3]/F4
I'm trying to not build manually a join and to use only Xpath capabilities if possible.
Any idea ?
I'm not sure I totally understand what you mean about backreferences, but do you want something like
/A/D[F1 = ../B[#id='2']/F1 and
F2 = ../B[#id='2']/F2 and
F3 = ../B[#id='2']/F3]/F4
?
In XPath 1.0, there is not a way to create a variable and then dereference it within the same expression. In XPath 2.0, you could use
for $b in A/B[#id = '2']
return A/D[F1 = $b/F1 and F2 = $b/F2 and F3 = $b/F3]/F4
but that' won't help you, since your question is about XPath 1.0.
Update: per your comment that you're "on node B", I see two possibilities.
One possibility is that the context node is the B element whose #id is the id you've been given, and you want to use the context node rather than looking up B by its #id again. In that case, you might be able to use current():
/A/D[F1 = current()/F1 and
F2 = current()/F2 and
F3 = current()/F3]/F4
current() means the context node as it was outside the entire XPath expression.
The other possibility is that the context node is some B element but it may not be the one whose #id you've been given, or you don't care whether it is or not. In that case, just use the first XPath expression I gave above. I just added / on the front so that it doesn't matter where in the document the context node was originally.

How to return xpath union of nodes in separate trees?

It's a basic question, but I couldn't find the answer anywhere.
<a>
<b1>
<d1>D1</d1>
<e1>E1</e1>
</b1>
<b2>
<c2>
<d2>D2</d2>
<e2>E2</e2>
</c2>
</b2>
</a>
From the above I'd like to return:
<a>
<d1>D1</d1>
<e1>E1</e1>
<d2>D2</d2>
<e2>E2</e2>
</a>
And not:
<a>
<b1>
<d1>D1</d1>
<e1>E1</e1>
</b1>
<b2>
<d2>D2</d2>
<e2>E2</e2>
</b2>
</a>
If that makes any sense. I tried "/a", but that gave me:
<a>
<b1>
<d1>D1</d1>
<e1>E1</e1>
</b1>
<b2>
<c2>D2E2</c2>
</b2>
</a>
If you meant to select all leave nodes (nodes without child node(s)), you can try this XPath :
//*[not(*)]
Or using XPath union (|) to get child nodes of <b1> and <c2> :
(//b1/* | //c2/*)
Given sample XML you posted, both XPath above will return :
<d1>D1</d1>
<e1>E1</e1>
<d2>D2</d2>
<e2>E2</e2>
But if you really need the result to be wrapped in <a>, then I agree with #minopret comment, that isn't what XPath meant to do. XSLT is more proper way to transform an XML to different format.
UPDATE :
In respond to your last comment, there is no such grouping in XPath. Should be done in the host language if you need that data structure. Your best bet is to select parent of those desired nodes in XPath so you get them grouped by their parent. Then you can do further processing in the host language, for example :
//*[not(*)]/parent::*
//*[*[not(*)]]
Any of above two XPath queries can return :
<b1>
<d1>D1</d1>
<e1>E1</e1>
</b1>
<c2>
<d2>D2</d2>
<e2>E2</e2>
</c2>
XPath can only return nodes that are already present in your source tree. To construct new nodes, or reorganise the tree, you need XSLT or XQuery.

XPath: limit scope of result set

Given the XML
<a>
<c>
<b id="1" value="noob"/>
</c>
<b id="2" value="tube"/>
<a>
<c>
<b id="3" value="foo"/>
</c>
<b id="4" value="goo"/>
<b id="5" value="noob"/>
<a>
<b id="6" value="near"/>
<b id="7" value="bar"/>
</a>
</a>
</a>
and the Xpath 1.0 query
//b[#id=2]/ancestor::a[1]//b[#value="noob"]
The Xpath above returns both node ids 1 and 5. The goal is to limit the result to just node id=1 since it is the only #value="noob" element that is a descendant of the same <a> that (//b[#id=2]) is also a descendant of.
In other words, "Find all b elements who's value is "noob" that are descendants of the a element which also has a descendant whose id is 2, but is not the descendant of any other a element". How's that for convoluted? In practice the id number and values would be variable and there would hundreds of node types.
If the id=2, we would expect to return element id=1 not id=5 since it is contained in another a element. If the id=4, we would expect to return id=5, but not id=1 since it is not in the first ancestor a element as id=4.
Edit:
Based on the comments of Dimitre and Alejandro, I found this helpful blog entry explaining the use of count() with the | union operator as well as some other excellent tips.
Use:
//b[#value='noob']
[count(ancestor::a[1] | //b[#id=2]/ancestor::a[1]) = 1]
Explanation:
The second predicate assures that both b elements have the same nearest ancestor a.
Remember: In XPath 1.0 the test for node identity is:
count($n1 | $n2) = 1
First, this
is there some way to limit the result
set to the <b> elements that are ONLY
the children of the immediate <a>
element of the start node
(//b[#id=2])?
//b[#value='noob'][ancestor::a[1]/b/#id=2]
It's not the same as:
Starting at a node whose id is equal
to 2, find all the elements whose
value is "noob" that are descendants
of the immediate parent c element
without passing through another c
element
Wich is:
//c[b/#id=2]//*[.='noob'][ancestor::c[1][b/#id=2]]
Besides these expressions, when you are dealing with "context marks" you can use the set's membership test as in:
$node[count(.|$node-set)=count($node-set)]
I leave you its use for this case as an exercise...
//b[#id=2]/ancestor::a[1]//b[#value="noob" and not(ancestor::a[2]=//b[#id=2]/ancestor::a[1])] ?
that works only for your case though, not sure how generic it should be!

Resources