Selecting the sibling or niece of the loop varaible in "for" loop in XQuery

131 Views Asked by At

Suppose there is this XML file:

<Adam>     
<JackSons>
   <s1>Jack</s1>
   <s2>Sara</s2>
</JackSons>
<PeterSons>
   <s1>Peter</s1>
   <n>
     <s2>Anna</s2>
   </n>
</PeterSons>
<DavidSons>
   <q>
     <s1>David</s1>
   </q>
   <n>
     <s2>Suzann</s2>
   </n>
</DavidSons>
</Adam>

I would like to get s2 which is the sibling or niece of each s1 to call a function on both of them inside the loop, please note we don't know anything about the structure of XML except that each s1 and s2 have a common ancestor, I tried

let $db := doc('test2.xq')
for $s1 in $db//s1
let $s2 := $db//*[//$s1]/(s2, */s2)


return <p>  {$s1/text()} marry {$s2/text()} </p>

It returns

<p>Jack marry SaraAnnaSuzann</p>
<p>Peter marry SaraAnnaSuzann</p>
<p>David marry SaraAnnaSuzann</p>

I expect

<p>Jack marry Sara</p>
<p>Peter marry Anna</p>
<p>David marry Suzann</p>
3

There are 3 best solutions below

4
On

Depending on the complexity of your text's within s1 and s2, something as simple as the following might be enough for you:

//item[starts-with(s2, s1)]/s2

I would need to see more real data to know if you needed something more complex, however this should work for the examples you have posted.

14
On

The problem with your query is that you search the whole document for s2 nodes again, not the same item.

Don't search for s1 elements, but look for pairs and print a message for each of them.

for $pair in //item
return <p>{ $pair//s1/text() } marry { $pair//s2/text() }</p>

You could also look for the s1 elements and find matching s2 nodes, but this is harder to read and understand, and pobably performs worse because of descending up back down the tree to find the item both belong to:

for $s1 in //s1
let $s2 := $s1//ancestor::item//s2
return <p>{ $s1/text() } marry { $s2/text() }</p>

In both cases, depending on what's allowed in the document you might want to add some safeguards to only find pairs of two. The probably most restrictive example would be

for $pair in //item
let $s1 := $pair//s1, $s2 := $pair//s2
where count($s1) = 1 and count($s2) = 1
return <p>{ $s1/text() } marry { $s2/text() }</p>

For completeness, this would even be possible without an explicit for loop (while not necessarily being a reasonable solution, as it will get much harder to read and understand if growing in complexity):

//item/element p { concat(.//s1/text(), " marry ", .//s2/text()) }
1
On

When you just know the s1 and s2 have a common ancestor, you can use the following code:

let $db := doc('test2.xq')
for $s1 in $db//s1
   let $item :=  $s1/ancestor::*[.//s1 and .//s2][not(.//*[.//s1 and .//s2])]
   let $s2 := $item//s2
   return <p>{ $s1/text() } marry { $s2/text() }</p> 

For each $s1 find an ancestor which contains both s1 and s2 of which no descendant contains s1 and s2. This is the first common ancestor (parent). Using this item, find the corresponding (sibling or niece) s2.