2017-06-30 99 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', 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> 
得到的

有什麼辦法保持積極的比賽我得到,並添加到條件的數量的計數?我很難過!

+0

在我的理解路徑'/ FO:測試/ FO:書/電子書/信息/頁[2]因爲沒有'都具有details'元素'不選擇你的示例XML什麼第二個'頁面'的孩子。 –

+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:測試/ FO:書/電子書/信息/頁)[2]' –

回答

1

下面提供類似於你正在努力實現的,它通過使用遞歸和它去,這樣就可以比較反對您要更換你的路徑記憶路徑這樣做什麼的例子。

這使用的XQuery 3.1,但是它可以被用XQuery 1.0由編碼重寫/地圖結構從一個序列或一個內存中的XML文檔進行解碼,以/。

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