I have a list of elements with information about how deep they are located in an XML tree. The elements at "the bottom," i.e. those elements that occur before an element with a lower depth, contain text.
<input>
<text n="x" xml:id="a" depth="1"/>
<div xml:id="b" depth="2"/>
<div xml:id="c" depth="3"/>
<p xml:id="e" depth="4">text</p>
<p xml:id="d" depth="4">text</p>
<p xml:id="x" depth="4">text</p>
<div xml:id="f" depth="3"/>
<lg xml:id="j" depth="4"/>
<l xml:id="k" depth="5">text</l>
<l xml:id="l" depth="5">text</l>
<p xml:id="n" depth="3">text</p>
</input>
I would like to reconstitute this as the XML tree below, in one operation.
<text n="x" xml:id="a" depth="1">
<div xml:id="b" depth="2">
<div xml:id="c" depth="3">
<p xml:id="e" depth="4">text</p>
<p xml:id="d" depth="4">text</p>
<p xml:id="x" depth="4">text</p>
</div>
<div xml:id="f" depth="3">
<lg xml:id="j" depth="4">
<l xml:id="k" depth="5">text</l>
<l xml:id="l" depth="5">text</l>
</lg>
</div>
<p xml:id="n" depth="3">text</p>
</div>
</text>
I think I need to start with the elements of the highest depth, i.e. with all elements of depth 5, and then wrap them up in the preceding element of depth 5-1, and so on, but I can't get my head around how to recurse through this.
The @xml:ids are just for reference.
My question is the converse of my earlier stackoverflow question. It resembles this stackoverflow question, but I need to use XQuery.
Build a function that recursively builds the tree. This code is very generic, by changing the local:getLevel($node)
function it should work for arbitrary "flattened" trees.
declare function local:getLevel($node as element()) as xs:integer {
$node/@depth
};
declare function local:buildTree($nodes as element()*) as element()* {
let $level := local:getLevel($nodes[1])
(: Process all nodes of current level :)
for $node in $nodes
where $level eq local:getLevel($node)
(: Find next node of current level, if available :)
let $next := ($node/following-sibling::*[local:getLevel(.) le $level])[1]
(: All nodes between the current node and the next node on same level are children :)
let $children := $node/following-sibling::*[$node << . and (not($next) or . << $next)]
return
element { name($node) } {
(: Copy node attributes :)
$node/@*,
(: Copy all other subnodes, including text, pi, elements, comments :)
$node/node(),
(: If there are children, recursively build the subtree :)
if ($children)
then local:buildTree($children)
else ()
}
};
let $xml := document {
<input>
<text n="x" xml:id="a" depth="1"/>
<div xml:id="b" depth="2"/>
<div xml:id="c" depth="3"/>
<p xml:id="e" depth="4">text</p>
<p xml:id="d" depth="4">text</p>
<p xml:id="x" depth="4">text</p>
<div xml:id="f" depth="3"/>
<lg xml:id="j" depth="4"/>
<l xml:id="k" depth="5">text</l>
<l xml:id="l" depth="5">text</l>
<p xml:id="n" depth="3">text</p>
</input>
}
return local:buildTree($xml/input/*)
Hereby I release this code to the public domain.
If your XQuery processor does not support enhanced FLWOR expressions, you need to reorder some of the lines; I omitted the comments:
for $node in $nodes
let $level := local:getLevel($nodes[1])
let $next := ($node/following-sibling::*[local:getLevel(.) le $level])[1]
let $children := $node/following-sibling::*[$node << . and (not($next) or . << $next)]
where $level eq local:getLevel($node)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With