如何计算递归XQuery函数中的匹配元素

时间:2017-06-30 16:51:05

标签: xpath xquery

以下代码有效,但现在我想指定一个参数,即xpath指定的元素的出现会受到影响(替换,删除,插入等)

  declare function local:replace($doc as node(), $new-content as item()*, 
    $target-path as xs:string)  {
    local:transform($doc, $new-content, $target-path, 'replace', "")
  };

  declare function local:transform($node as node(), $new-content as item()*, 
        $target-path as xs:string,  $action as xs:string, $path as xs:string) 
        as node()+ {
    if ($node instance of element() and concat($path, "/", node-name($node)) = $target-path)
    then
      if ($action = 'insert-before')
      then ($new-content, $node) 
      else

      if ($action = 'insert-after')
      then ($node, $new-content)
      else

      if ($action = 'insert-as-child')
      then element {node-name($node)}
        {
        $node/@*
        ,
        $new-content
        ,
          for $child in $node/node()
            return $child
        }
        else

        if ($action = 'replace')
        then $new-content
        else 

        if ($action = 'delete')
        then ()
        else ()
    else
        if ($node instance of element()) 
        then
            element {node-name($node)} 

            {
            $node/@* 
            ,
            for $child in $node/node()
                return 
                  local:transform($child, $new-content, $target-path,  $action, concat($path,  "/", node-name($node)))
            }
         else $node
};



let $doc :=
<fo:Test xmlns:fo="http://www.w3.org/1999/XSL/Format">
  <fo:books>
    <!-- These are my books --> 
    <book title='xQuery for Dummys'>
      <author>Jack Wizard</author>
      <details>    
        <pages>221</pages>
      </details>
    </book>  
    <book title='Mysteries of xQuery'>
      <author>Lost Linda</author>
      <details>
        <replace-this>Goodbye World!</replace-this>
        <pages>40</pages>
      </details>
    </book>  
  </fo:books>
</fo:Test>

(: -------- My test -------- :)
let $new-content := <replaced>Hello World!</replaced> return
local:replace($doc, $new-content, '/fo:Test/fo:books/book/details/replace-this')

显然$global-counter = $global-counter + 1无法正常工作。我想传递我想要的事件作为另一个参数,如:

    local:replace($doc, $new-content, '/fo:Test/fo:books/book/details/pages', 2)

获得此输出:

<fo:Test xmlns:fo="http://www.w3.org/1999/XSL/Format">
  <fo:books>
    <!-- These are my books --> 
    <book title='xQuery for Dummys'>
      <author>Jack Wizard</author>
      <details>    
        <pages>221</pages>
      </details>
    </book>  
    <book title='Mysteries of xQuery'>
      <author>Lost Linda</author>
      <details>
        <replace-this>Goodbye World!</replace-this>
        <replaced>Hello World!</replaced>
      </details>
    </book>  
  </fo:books>
</fo:Test>

我使用local:replace($doc, $new-content, '/fo:Test/fo:books/book/details/pages')

取代了这种不必要的结果
<fo:Test xmlns:fo="http://www.w3.org/1999/XSL/Format">
  <fo:books>
    <!-- These are my books --> 
    <book title='xQuery for Dummys'>
      <author>Jack Wizard</author>
      <details>    
        <replace-this>Goodbye World!</replace-this>
      </details>
    </book>  
    <book title='Mysteries of xQuery'>
      <author>Lost Linda</author>
      <details>
        <replace-this>Goodbye World!</replace-this>
        <replaced>Hello World!</replaced>
      </details>
    </book>  
  </fo:books>
</fo:Test>

有没有办法记录我得到的积极匹配数量并将其添加到条件中?我很难过!

1 个答案:

答案 0 :(得分:1)

下面提供了一个类似于您要实现的示例,它通过使用递归和记忆路径来实现,以便它可以与您希望替换的路径进行比较。

这使用XQuery 3.1,但是可以通过将映射结构编码/解码到序列或内存中的XML文档来在XQuery 1.0中重写。

xquery version "3.1";

declare namespace fo="http://www.w3.org/1999/XSL/Format";

declare %private function local:push-element($path-stack as map(xs:integer, map(xs:QName, xs:integer)), $element as element(), $depth as xs:integer) as map(xs:integer, map(xs:QName, xs:integer)) {
    let $occurence-map := $path-stack($depth)
    return
      if(not(empty($occurence-map))) then
        let $name-occurs := $occurence-map(node-name($element))
        return
          if($name-occurs) then
            map:put($path-stack, $depth, map:put($occurence-map, node-name($element), $name-occurs + 1))
          else
             map:put($path-stack, $depth, map:put($occurence-map, node-name($element), 1))
      else
        map:put($path-stack, $depth, map { node-name($element) : 1 })

        (: TODO to reduce memory you could remove (pop) any elements from the map which have depth gt $depth :)
};

declare %private function local:children($children as node()*, $replace as xs:string, $replacement as element(), $current-path as xs:QName*, $path-stack as map(xs:integer, map(xs:QName, xs:integer))) {
  if($children)then
    let $child := $children[1]
    return
      let $new-path-stack :=
        if($child instance of element())then
          local:push-element($path-stack, $child, count($current-path) + 1)
        else
          $path-stack
      return
      (
        local:replace($child, $replace, $replacement, $current-path, $new-path-stack),
        local:children(subsequence($children, 2), $replace, $replacement, $current-path, $new-path-stack)
      )
  else()      
};

declare %private function local:get-occurence($name as xs:QName, $depth as xs:integer, $path-stack as map(xs:integer, map(xs:QName, xs:integer))) {
  $path-stack($depth)($name)
};

declare %private function local:eq-path-with-positions($current-path as xs:QName*,  $path-stack as map(xs:integer, map(xs:QName, xs:integer)), $path-with-positions as xs:string) as xs:boolean {
  let $current-path-with-positions :=
    '/' || string-join(
      for $step-qn at $depth in $current-path
      let $occurence :=  local:get-occurence($step-qn, $depth, $path-stack)
      let $occurence-predicate-str := '[' || $occurence || ']'
      return
        $step-qn || $occurence-predicate-str
      , '/'
    )
  return
    $path-with-positions eq $current-path-with-positions
};

declare %private function local:replace($node as node(), $replace as xs:string, $replacement as element(), $current-path as xs:QName*, $path-stack as map(xs:integer, map(xs:QName, xs:integer))) as node() {

    typeswitch($node)

        case document-node()
        return
              document {
                  $node/node() ! local:replace(., $replace, $replacement, $current-path, $path-stack)
              }

        case element()
        return
            let $new-path := ($current-path, node-name($node))
            let $new-path-stack :=
              if(map:size($path-stack) eq 0) then
                local:push-element($path-stack, $node, count($new-path))
              else
                $path-stack
            return 
                let $matched-for-replace := local:eq-path-with-positions($new-path, $new-path-stack, $replace)
                return
                  if($matched-for-replace)then
                    $replacement
                  else            
                    element {name($node)} {
                        $node/@*,
                        local:children($node/node(), $replace, $replacement, $new-path, $new-path-stack)
                    }

        default return $node


};

declare function local:replace($node as node(), $replace as xs:string, $replacement as element()) as node() {
    let $path-stack as map(xs:integer, map(xs:QName, xs:integer)) := map {}
    return
        local:replace($node, $replace, $replacement, (), $path-stack)
};

let $doc :=
    <fo:Test xmlns:fo="http://www.w3.org/1999/XSL/Format">
      <fo:books>
        <!-- These are my books --> 
        <book title='xQuery for Dummys'>
          <author>Jack Wizard</author>
          <details>    
            <pages>221</pages>
          </details>
        </book>  
        <book title='Mysteries of xQuery'>
          <author>Lost Linda</author>
          <details>
            <replace-this>Goodbye World!</replace-this>
            <pages>40</pages>
          </details>
        </book>  
      </fo:books>
    </fo:Test>
return
    local:replace($doc, '/fo:Test[1]/fo:books[1]/book[2]/details[1]/pages[1]', <replaced>Hello World!</replaced>)