2017-06-30 10 views
1

次のコードは動作しますが、xpathで指定された要素の出現が影響を受ける(置き換えられた、削除された、挿入されたなどの)パラメータとして指定します。再帰的なXQuery関数で一致する要素を数える方法

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')

<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> 
を使用して取得したい、この不要な結果の

<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', 2) 

そして、この出力を得る:私はこのような別のパラメータとして、私が欲しいの発生に渡したいです

私が得たポジティブな一致の数をカウントして、それを条件に追加する方法はありますか?私は困惑している!

+0

私の理解では、 '/ fo:Test/fo:books/book/details/pages [2]'はサンプルXMLの中に何も選択しません。 2番目の 'ページ'子。 –

+0

しかし、どの要素がそのパスで選択されるはずですか?/ fo:Test/fo:books/book/details/pages [2] '?あなたのサンプルではありません。たとえば、/fo:Test/fo:books/book [2]/details/pages'または '/ fo:Test/fo:books/book/details/pages [2]'のようになります。 –

+0

私はそれが欲しいと望むことを選んでいます。入力の構文を定義するのは私の責任です。これは、別のパラメータを作成せずにオカレンス値を渡す単なる方法です。しかし、私の望む出力に示されているように、再帰関数でなく単純なxpathの選択問題でなければ '(/ fo:Test/fo:books/book/details/pages)[2]' –

答えて

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>) 
関連する問題