4

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?

Jens Erat
  • 37,523
  • 16
  • 80
  • 96

1 Answers1

5

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.

Jens Erat
  • 37,523
  • 16
  • 80
  • 96
  • Thanks, @Jens Erat. So by using ´tail()´ and ´head()´, one can iterate without a `for` expression, since `tail()`return all before the (moving) `head()`. Cool. I will see if I can grok this enough to use it myself …. – Jens Østergaard Petersen Oct 17 '15 at 17:42
  • 1
    `tail($item)` and `head($item` are just syntactic sugar for `$item[1]` respective `$item[position() > 1]`. But they result in code that's easier to read and understand, especially to developers with strong functional background. – Jens Erat Oct 17 '15 at 18:16