4

我有一个元素列表,其中包含有关它们在 XML 树中的位置的信息。“底部”的元素,即出现在深度较低的元素之前的元素,包含文本。

<input>
    <text n="x" xml:id="a" depth="1"/>
    <div xml:id="b" depth="2"/>
    <div xml:id="c" depth="3"/>
    <p xml:id="e" depth="4">text</p>
    <p xml:id="d" depth="4">text</p>
    <p xml:id="x" depth="4">text</p>
    <div xml:id="f" depth="3"/>
    <lg xml:id="j" depth="4"/>
    <l xml:id="k" depth="5">text</l>
    <l xml:id="l" depth="5">text</l>
    <p xml:id="n" depth="3">text</p>
</input>

我想在一次操作中将其重构为下面的 XML 树。

<text n="x" xml:id="a" depth="1">
    <div xml:id="b" depth="2">
        <div xml:id="c" depth="3">
            <p xml:id="e" depth="4">text</p>
            <p xml:id="d" depth="4">text</p>
            <p xml:id="x" depth="4">text</p>
        </div>
        <div xml:id="f" depth="3">
            <lg xml:id="j" depth="4">
                <l xml:id="k" depth="5">text</l>
                <l xml:id="l" depth="5">text</l>
            </lg>
        </div>
        <p xml:id="n" depth="3">text</p>
    </div>
</text>

我想我需要从深度最高的元素开始,即深度为 5 的所有元素,然后将它们包裹在深度为 5-1 的前一个元素中,依此类推,但我无法理解如何通过这个递归。

@xml:ids 仅供参考。

我的问题与我之前的stackoverflow问题相反。它类似于这个stackoverflow问题,但我需要使用 XQuery。

4

3 回答 3

3

构建一个递归构建树的函数。这段代码非常通用,通过更改local:getLevel($node)它应该适用于任意“扁平”​​树的函数。

declare function local:getLevel($node as element()) as xs:integer {
  $node/@depth
};

declare function local:buildTree($nodes as element()*) as element()* {
  let $level := local:getLevel($nodes[1])
  (: Process all nodes of current level :)
  for $node in $nodes
  where $level eq local:getLevel($node)

  (: Find next node of current level, if available :)
  let $next := ($node/following-sibling::*[local:getLevel(.) le $level])[1]
  (: All nodes between the current node and the next node on same level are children :)
  let $children := $node/following-sibling::*[$node << . and (not($next) or . << $next)]

  return
    element { name($node) } {
      (: Copy node attributes :)
      $node/@*,
      (: Copy all other subnodes, including text, pi, elements, comments :)
      $node/node(),

      (: If there are children, recursively build the subtree :)
      if ($children)
      then local:buildTree($children)
      else ()
    }
};

let $xml := document {
  <input>
    <text n="x" xml:id="a" depth="1"/>
    <div xml:id="b" depth="2"/>
    <div xml:id="c" depth="3"/>
    <p xml:id="e" depth="4">text</p>
    <p xml:id="d" depth="4">text</p>
    <p xml:id="x" depth="4">text</p>
    <div xml:id="f" depth="3"/>
    <lg xml:id="j" depth="4"/>
    <l xml:id="k" depth="5">text</l>
    <l xml:id="l" depth="5">text</l>
    <p xml:id="n" depth="3">text</p>
  </input>
}

return local:buildTree($xml/input/*)

在此,我将此代码发布到公共领域。

如果您的 XQuery 处理器不支持增强的 FLWOR 表达式,则需要重新排序一些行;我省略了评论:

  for $node in $nodes
  let $level := local:getLevel($nodes[1])
  let $next := ($node/following-sibling::*[local:getLevel(.) le $level])[1]
  let $children := $node/following-sibling::*[$node << . and (not($next) or . << $next)]
  where $level eq local:getLevel($node)
于 2014-02-03T14:51:15.340 回答
2

只是提出另一种方法-我认为我以前没有在愤怒中使用过相交!

declare function local:buildTree($nodes,$level)  {
  for $node in $nodes[@depth=$level]
  let $end := $node/following-sibling::*[@depth = $level][1]
  let $rest := 
       if ($end) 
       then $node/following-sibling::*  intersect  $end/preceding-sibling::*
       else $node/following-sibling::*
  return 
    element {$node/name()} {
        $node/@*,
        $node/node(),
        local:buildTree($rest,$level+1)
    }
};
declare function local:buildTree($node) {
       local:buildTree($node/*,1)
};
let $xml := document {
  <input>
    <text n="x" xml:id="a" depth="1"/>
    <div xml:id="b" depth="2"/>
    <div xml:id="c" depth="3"/>
    <p xml:id="e" depth="4">text</p>
    <p xml:id="d" depth="4">text</p>
    <p xml:id="x" depth="4">text</p>
    <div xml:id="f" depth="3"/>
    <lg xml:id="j" depth="4"/>
    <l xml:id="k" depth="5">text</l>
    <l xml:id="l" depth="5">text</l>
    <p xml:id="n" depth="3">text</p>
  </input>
}

return local:buildTree($xml/input)
于 2014-02-03T17:17:33.003 回答
2

这是另一个版本,基于 Chris Wallace 的方法,但使用了 XQuery 3.0 的tumbling window构造,这使得该代码稍微简单一些。

declare function local:buildTree($nodes,$level)  {
  for tumbling window $node-window in $nodes
  start $start when $start/@depth = $level
  let $rest := fn:tail($node-window)
  return 
    element {$start/fn:name()} {
        $start/@*,
        $start/node(),
        local:buildTree($rest,$level+1)
    }
};
declare function local:buildTree($node) {
       local:buildTree($node/*,1)
};
let $xml := document {
  <input>
    <text n="x" xml:id="a" depth="1"/>
    <div xml:id="b" depth="2"/>
    <div xml:id="c" depth="3"/>
    <p xml:id="e" depth="4">text</p>
    <p xml:id="d" depth="4">text</p>
    <p xml:id="x" depth="4">text</p>
    <div xml:id="f" depth="3"/>
    <lg xml:id="j" depth="4"/>
    <l xml:id="k" depth="5">text</l>
    <l xml:id="l" depth="5">text</l>
    <p xml:id="n" depth="3">text</p>
  </input>
}

return local:buildTree($xml/input)
于 2014-07-20T10:41:00.777 回答