How to pass an XPath expression as parameter?

Sunday, 28 October 2018 · 3 messages · last reply 29 Oct 2018
AM
Andreas Mixich28 Oct 2018 13:34 #
Is there a way to do something like this?
declare function local:insert(
  $doc as node(),
  $at-location as xpath(),  (: I know, there is no 'xpath' type, this is
just for demo :)
  $what as node()*
) as node() {
  copy $c := $doc
  modify insert node $what into $at-location
  return $c
};

let $xml := <xml><body><p>This is a paragraph.</p></body></xml>
let $nd := <this><is><a>Test</a></is></this>
return local:insert($xml, xpath("/body"), $nd)
As I see it, it would need a dynamic xs:string => XPath expression conversion or a defer XPath expression, which both do not exist, for $at-location. I managed to do this, however:
declare function local:insert($doc, $at-location, $what) {
  let $update := ``[
  declare variable $doc external;
  declare variable $what external;
  declare variable $at-location external;
copy $c := $doc
modify insert node $what into `{$at-location}`
return $c
]``
  return xquery:eval($update, map{
    'doc':$doc,
    'at-location':$at-location,
    '$what':$what
  })
};

let $xml := <xml><body><p>This is a paragraph.</p></body></xml>
let $node := <this><is><a>Test</a></is></this>
return local:insert($xml, "$c/body", $node)
Ideally (for me), there would be a 'defer' XPath expression, that starts its life as a string (when passed) and then unfolds upon call. Or a type (or function) fn:xpath(), that behaves that way. Or am I just blind and don't see the obvious, and there *is* a simpler solution?
-- Minden jót, all the best, Alles Gute, Andreas Mixich
AM
Andreas Mixich28 Oct 2018 17:18 #
I just realized, that there is another, simpler way to simulate a defer XPath expression. So I ended up using:
declare function local:add-node(
  $input as node()*,
  $what as node()*
) as node() {
  copy $c := $input
  modify insert node $what into $c
  return $c
};
declare function local:add-node(
  $input as node()*,
  $what as node()*,
  $at-location as xs:string
) as node() {
  copy $c := $input
  modify
    let $xpath := xquery:eval($at-location, map{"":$c})
    return
      insert node $what into $c/$xpath
  return $c
};
<html></html> => local:add-node((<head></head>,<body></body>)) => local:add-node(<title>Example Title</title>, "head")
CG
Christian Grün29 Oct 2018 15:24 #
Hi Andreas, If your dynamic path is simple, you can get rid of xquery:eval and replace it with a fold-left iteration (see the attached example). The programmatic solution may be safer, in particular if the supplied path may be the result of external user input. Obviously, you’ll need to ensure in both cases that the resulting target node is a single element node. Cheers, Christian
  declare function local:add-nodes1(
    $input as node(),
    $nodes as node()*,
    $path as xs:string
  ) as node() {
    $input update {
      let $target := xquery:eval($path, map { '': . })
      return insert nodes $nodes into $target
    }
  };

  declare function local:add-nodes2(
    $input as node(),
    $nodes as node()*,
    $path as xs:string
  ) as node() {
    $input update {
      let $steps := tokenize($path, '/')
      let $target := fold-left($steps, ., function($node, $name) {
        $node/*[name() = $name]
      })
      return insert nodes $nodes into $target
    }
  };
<xml><a><b/></a></xml> => local:add-nodes1(<new1/>, 'a/b') => local:add-nodes2(<new2/>, 'a/b')
Quoted text · Andreas Mixich · 29 lines
On Sun, Oct 28, 2018 at 5:19 PM Andreas Mixich <mixich.andreas at gmail.com> wrote: > > I just realized, that there is another, simpler way to simulate a defer XPath expression. So I ended up using: > > declare function local:add-node( > $input as node()*, > $what as node()* > ) as node() { > copy $c := $input > modify insert node $what into $c > return $c > }; > declare function local:add-node( > $input as node()*, > $what as node()*, > $at-location as xs:string > ) as node() { > copy $c := $input > modify > let $xpath := xquery:eval($at-location, map{"":$c}) > return > insert node $what into $c/$xpath > return $c > }; > > <html></html> > => local:add-node((<head></head>,<body></body>)) > => local:add-node(<title>Example Title</title>, "head") >

⚡Generated with XQuery