Рекурсивный выбор родителя XPath в плоской структуре

Дан следующий XML:

<root>
  <element>
    <id>1</id>
  </element>
  <element>
    <id>2</id>
    <parentId>1</parentId>
  </element>
  <element>
    <id>3</id>
    <parentId>2</parentId>
  </element>
  <element>
    <id>4</id>
    <parentId>3</parentId>
  </element>
  <element>
    <id>5</id>
    <parentId>2</parentId>
  </element>
  <element>
    <id>6</id>
    <parentId>5</parentId>
  </element>
</root>

Теперь я хочу выбрать все «родительские» узлы, например. элемент 3. Допустим, желаемый результат для элемента 3 должен быть:

  • элемент 1
  • элемент 2

Желаемый результат для элемента 2 должен быть:

  • элемент 1

И желаемый результат для элемента 6 должен быть

  • элемент 5
  • элемент 2
  • элемент 1

Возможно ли это сделать с помощью XPath? Если да, то как вы могли это сделать?


person moritz.vieli    schedule 25.09.2019    source источник
comment
Как/куда бы вы ввели начальный элемент?   -  person michael.hor257k    schedule 25.09.2019
comment
С переменной в XSLT. Как-то так: /root/element[magic = $startElement]   -  person moritz.vieli    schedule 25.09.2019
comment
На самом деле в такой иерархии может быть более одного начального элемента. Существуют алгоритмы и реализации XSLT, которые не нуждаются в каком-либо начальном элементе — они находят все возможные начальные элементы и помещают их в начало отсортированной последовательности.   -  person Dimitre Novatchev    schedule 30.09.2019


Ответы (2)


Возможно ли это сделать с помощью XPath? Если да, то как вы могли это сделать?

И. Общее решение XSLT 1.0

Как указано в комментарии ОА:

«Цель состоит в том, чтобы производить родительские элементы раньше их потомков».

Это также известно как "топологическая сортировка"

А вот моя реализация топологической сортировки XSLT 1.0, датированная 2001 годом:

"Решение. Re: как переупорядочить узлы на основе графа зависимостей?"

А вот еще один вариант этой XSLT-топологической сортировки, «которая удерживает клики вместе» (стабильная топологическая сортировка) https://www.biglist.com/lists/lists.mulberrytech.com/xsl-list/archives/200112/msg01009.html

Что касается получения с помощью чистого XPath последовательности идентификаторов предков подразумеваемой иерархии для данного элемента, ниже приведено решение с использованием XPath 3.0 или более поздней версии.


II. Чистое решение XPath 3

Это выражение XPath 3.0 определяет встроенную (XPath 3.0) функцию, которая вычисляет путь предка элемента, переданный как внешний параметр $pCurrent:

   let $pCurrent := current(),
       $ancestor-path-inner := function($el as element(), $self as function(*)) as xs:string*
       {
           let $parent := $el/../element[id eq $el/parentId]
              return
               if(not(empty($parent))) then $self($parent, $self)
                 else ()
           ,
            $el/parentId
       },
       $ancestor-path := function($el as element()) as xs:string*
       { $ancestor-path-inner($el, $ancestor-path-inner)}
    return
      string-join($ancestor-path($pCurrent), '-')

Проверка на основе XSLT 3.0:

<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:strip-space elements="*"/>

    <xsl:template match="element">
      <element id="{id}" ancestor-path-ids=
       "{let $pCurrent := current(),
             $ancestor-path-inner := function($el as element(), 
                                              $self as function(*)) as xs:string*
            {
              let $parent := $el/../element[id eq $el/parentId]
               return
                 if(not(empty($parent))) then $self($parent, $self)
                   else ()
                 ,
                 $el/parentId
            },
            $ancestor-path := function($el as element()) as xs:string*
             { $ancestor-path-inner($el, $ancestor-path-inner)}
       return
        string-join($ancestor-path($pCurrent), '-')}"/>
    </xsl:template>
</xsl:stylesheet>

Когда это преобразование применяется к предоставленному XML-документу:

<root>
    <element>
        <id>1</id>
    </element>
    <element>
        <id>2</id>
        <parentId>1</parentId>
    </element>
    <element>
        <id>3</id>
        <parentId>2</parentId>
    </element>
    <element>
        <id>4</id>
        <parentId>3</parentId>
    </element>
    <element>
        <id>5</id>
        <parentId>2</parentId>
    </element>
    <element>
        <id>6</id>
        <parentId>5</parentId>
    </element>
</root>

получен желаемый правильный результат:

<element id="1" ancestor-path-ids=""/>
<element id="2" ancestor-path-ids="1"/>
<element id="3" ancestor-path-ids="1-2"/>
<element id="4" ancestor-path-ids="1-2-3"/>
<element id="5" ancestor-path-ids="1-2"/>
<element id="6" ancestor-path-ids="1-2-5"/>
person Dimitre Novatchev    schedule 30.09.2019
comment
Большое спасибо! Похоже, XPath 3 предлагает решение! ???? - person moritz.vieli; 02.10.2019
comment
Да, @moritz.vieli, XPath 3.0, вероятно, завершен по Тьюрингу. Для получения дополнительной информации и демонстрации программирования с помощью XPath 3 прочитайте это: balisage .net/Proceedings/vol10/html/Novatchev01/ и посмотрите эту презентацию (сжатые слайды PPT): balisage.net/Proceedings/vol10/author-pkg/Novatchev01/ . Также этот курс Pluralsight охватывает XPath 3.0: multiplesight.com/library/courses/xpath-3-0-whats-new/ - person Dimitre Novatchev; 02.10.2019
comment
Отличный пример XPath 3.0. - person Alejandro; 02.12.2019
comment
@Alejandro Ваша оценка очень ценна для меня. Для более полного ознакомления с программированием с помощью XPath 3 щелкните ссылки в моем комментарии выше — особенно я рекомендую слайды. Надеюсь, вам это понравится. - person Dimitre Novatchev; 03.12.2019

Рассмотрим следующий пример:

XSLT 1.0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:param name="start-id"/>

<xsl:key name="elem" match="element" use="id"/>

<xsl:template match="/root">
    <root>
        <xsl:apply-templates select="key('elem', $start-id)"/>
    </root>
</xsl:template>

<xsl:template match="element">
    <element id="{id}"/>
    <xsl:apply-templates select="key('elem', parentId)"/>
</xsl:template>

</xsl:stylesheet>

Применив это к входным данным XML со значением параметра start-id, равным 6, вы получите:

Результат

<?xml version="1.0" encoding="UTF-8"?>
<root>
  <element id="6"/>
  <element id="5"/>
  <element id="2"/>
  <element id="1"/>
</root>

Чтобы исключить начальный узел и перечислить только его предков, вы можете сделать:

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:param name="start-id" select="6"/>

<xsl:key name="elem" match="element" use="id"/>

<xsl:template match="/root">
    <root>
        <xsl:apply-templates select="key('elem', key('elem', $start-id)/parentId)"/>
    </root>
</xsl:template>

<xsl:template match="element">
    <element id="{id}"/>
    <xsl:apply-templates select="key('elem', parentId)"/>
</xsl:template>

</xsl:stylesheet>
person michael.hor257k    schedule 25.09.2019
comment
Большое спасибо за ваш пример! Мне нужно это в выражении сортировки for-each as. Цель состоит в том, чтобы произвести родительские элементы перед их дочерними элементами. Возможно, я также могу переписать шаблоны for-each для применения, но это довольно сложно, и мне нужно это проверить. - person moritz.vieli; 25.09.2019
comment
Если вы хотите изменить порядок, просто переместите инструкцию <element id="{id}"/> после инструкции the <xsl:apply-templates select="key('elem', parentId)"/>. Вам не нужно использовать xsl:for-each, и вы не можете использовать xsl:for-each с этим типом ввода. Вам нужно рекурсивно переходить от дочернего к родительскому. - person michael.hor257k; 25.09.2019
comment
Большое спасибо! На самом деле у меня есть два списка. Один с элементами и один с элементами-отношениями. Мне нужно вывести все элементы, независимо от отношений. Отношения определяют только порядок (иногда для элемента отношения нет). Возможно ли это как-то с применением шаблонов? Я все равно проверю, но думаю, что это будет очень сложно. - person moritz.vieli; 26.09.2019
comment
Теперь решил это с помощью рекурсивной XSLT-функции. Таким образом, окончательный ответ будет заключаться в том, что это требование не может быть достигнуто только с помощью XPath, я полагаю. - person moritz.vieli; 26.09.2019
comment
Возможно нет. --- - person michael.hor257k; 26.09.2019
comment
@moritz.vieli На самом деле, окончательный ответ заключается в том, что существует чистое решение XPath — наслаждайтесь! - person Dimitre Novatchev; 01.10.2019