将元素序列转换为树

时间:2014-02-03 12:38:12

标签: xml xquery flwor tei

我有一个元素列表,其中包含有关它们在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:id仅供参考。

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

3 个答案:

答案 0 :(得分: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)

答案 1 :(得分: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)

答案 2 :(得分:1)

这是另一个版本,基于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)