преобразование последовательности элементов в дерево

У меня есть список элементов с информацией о том, насколько глубоко они расположены в дереве 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.


person Jens Østergaard Petersen    schedule 03.02.2014    source источник
comment
Честно говоря, в простом приложении DOM или SAX это будет намного чище, чем в XQuery. Лично я думаю, что вы должны вернуться к тому, кто передает вам эту уродливую аннотированную глубину в первую очередь, и бить их по голове рекомендацией XML, пока они не предоставят вам правильно структурированные данные.   -  person keshlam    schedule 03.02.2014
comment
Я согласен с вашим мнением о входе; но XQuery вполне подходит для этого.   -  person Jens Erat    schedule 03.02.2014
comment
Позвольте мне объяснить, для чего я это использую. В TEI можно различать элементы блочного уровня (которые могут содержать только содержимое элемента или чей родительский элемент может содержать только содержимое элемента) и встроенные элементы (которые могут содержать текстовое/смешанное содержимое). Проблема заключалась в том, как справиться с изменением порядка и наличием элементов блочного уровня. Если сначала сгладить дерево, можно обращаться и, таким образом, перемещаться и удалять/добавлять элементы блочного уровня. С помощью Йенса Эрата мне удалось сделать первое и написать «приложение» для блочных элементов, но как снова собрать Шалтая-Болтая?   -  person Jens Østergaard Petersen    schedule 03.02.2014
comment
См. также stackoverflow.com/questions/11395990/ для аналогичного вопроса.   -  person Jens Østergaard Petersen    schedule 14.11.2014


Ответы (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)
person Jens Erat    schedule 03.02.2014
comment
Еще раз спасибо, Йенс - я обязательно отмечу вас за обе ваши публикации! - person Jens Østergaard Petersen; 03.02.2014
comment
Этот красивый. - person Jens Østergaard Petersen; 03.02.2014
comment
Я собирался поместить два решения в кулинарную книгу решений XQuery для сравнения http://kitwallace.co.uk/Book/set/maketree, но не смог заставить это работать? - он не компилируется (за/где следует let), и если я вставлю дополнительный возврат, он выдаст только верхний узел [на eXist-db 2.0] - person Chris Wallace; 03.02.2014
comment
Просто поменяйте местами два утверждения. Я написал этот код, используя BaseX, который поддерживает расширенные выражения FLWOR, и этот порядок кажется более интуитивным и будет быстрее, если в любом случае не будет оптимизирован механизмом XQuery: проверка уровня действительно дешевая (O (1)), тогда как поиск дочерних элементов находится в O(n), так что лучше отфильтровать их перед поиском. - person Jens Erat; 03.02.2014
comment
Извините, Йенс, я не совсем понимаю, что вы предлагаете - не лучше ли в любом случае опубликовать действительный XQuery? - person Chris Wallace; 03.02.2014
comment
Я добавил его в качестве альтернативы в конец моего вопроса. - person Jens Erat; 03.02.2014
comment
Проблема (по крайней мере, для XQuery на eXist, я нигде не пробовал) заключается в // в выражении chidren. Скрипт корректно работает при замене // на /. Возврат может идти после того, где (я предполагаю, что это то, что делает процессор BaseX). Я поместил оба в свою кулинарную книгу kitwallace.co.uk/Book/set/maketree/ execute, и на самом деле скрипт, использующий intersect, несколько быстрее, чем скрипт, использующий оператор порядка, хотя, конечно, это проверено только на eXist. - person Chris Wallace; 04.02.2014
comment
Этого точно не должно быть. Просто запрашивает множество узлов, которые все равно исключаются. Спасибо, что заметили. - person Jens Erat; 04.02.2014
comment
Просто поясню: в коде Йенса Эрата изначально было let $children := $node//following-sibling::*[$node << . and (not($next) or . << $next)], но теперь // было исправлено на /. - person Jens Østergaard Petersen; 04.02.2014
comment
Вы можете попробовать это решение в реальном времени по адресу try.zorba.io/queries/xquery/SR7HkqXkPfe4VAIb5k0. %2BMhxE3ug%3D - person wcandillon; 07.02.2014

Просто предлагаю другой подход - я не думаю, что раньше использовал пересечение в гневе!

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)
person Chris Wallace    schedule 03.02.2014
comment
Это определенно более элегантно и высокоуровнево, но может потребовать, чтобы сначала были рассчитаны оба набора. Я предполагаю (без каких-либо доказательств), что оператор порядка узлов будет работать быстрее. - person Jens Erat; 03.02.2014
comment
На самом деле в eXist intesect работает быстрее, см. kitwallace.co.uk/Book/set/maketree/ выполнить - person Chris Wallace; 04.02.2014
comment
Посмотрю на это, когда у меня будет больше свободного времени... Есть еще несколько отличий. Хотя этот анализ был бы более интересным с большими документами реального размера. - person Jens Erat; 04.02.2014
comment
Спасибо за это решение. Можно также использовать tumbling window XQuery 3.0, чтобы немного упростить его. Я опубликовал версию с tumbling window в качестве нового ответа. - person favq; 20.07.2014

Вот еще одна версия, основанная на подходе Криса Уоллеса, но использующая конструкцию tumbling window XQuery 3.0, которая немного упрощает этот код.

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)
person favq    schedule 20.07.2014