Search code examples
recursionxqueryxquery-3.0

Recursively wrapping up an element


Say I have an element <x>x</x> and some empty elements (<a/>, <b/>, <c/>), and I want to wrap up the first inside the second one at a time, resulting in <c><b><a><x>x</x></a></b></c>. How do I go about this when I don't know the number of the empty elements?

I can do

xquery version "3.0";

declare function local:wrap-up($inner-element as element(), $outer-elements as element()+) as element()+ {
    if (count($outer-elements) eq 3)
    then element{node-name($outer-elements[3])}{element{node-name($outer-elements[2])}{element{node-name($outer-elements[1])}{$inner-element}}}
    else 
        if (count($outer-elements) eq 2)
        then element{node-name($outer-elements[2])}{element{node-name($outer-elements[1])}{$inner-element}}
        else
            if (count($outer-elements) eq 1)
            then element{node-name($outer-elements[1])}{$inner-element}
            else ($outer-elements, $inner-element)
};

let $inner-element := <x>x</x>
let $outer-elements := (<a/>, <b/>, <c/>)

return 
    local:wrap-up($inner-element, $outer-elements)

but is there a way to do this by recursion, not decending and parsing but ascending and constructing?


Solution

  • In functional programming, you usually try to work with the first element and the tail of a list, so the canonical solution would be to reverse the input before nesting the elements:

    declare function local:recursive-wrap-up($elements as element()+) as element() {
      let $head := head($elements)
      let $tail := tail($elements)
      return
        element { name($head) } { (
          $head/@*,
          $head/node(),
          if ($tail)
          then local:recursive-wrap-up($tail)
          else ()
        ) }
    };
    
    let $inner-element := <x>x</x>
    let $outer-elements := (<a/>, <b/>, <c/>)
    
    return (
        local:wrap-up($inner-element, $outer-elements),
        local:recursive-wrap-up(reverse(($inner-element, $outer-elements)))
    )
    

    Whether reverse(...) will actually require reversing the output or not will depend on your XQuery engine. In the end, reversing does not increase computational complexity, and might not only result in cleaner code, but even faster execution!

    Similar could be achieved by turning everything upside down, but there are no functions for getting the last element and everything before this, and will possibly reduce performance when using predicates last() and position() < last(). You could use XQuery arrays, but will have to pass counters in each recursive function call.

    Which solution is fastest in the end will require benchmarking using the specific XQuery engine and code.