Как я могу построить дерево из плоского списка XML, используя XSLT? - PullRequest
6 голосов
/ 10 марта 2009

Я использую минималистичную инфраструктуру MVC, где PHP-контроллер передает модель DOM в XSLT-представление (ср okapi ) .

для построения дерева навигации я использовал вложенные множества в MYSQL. Таким образом, я получаю модель XML, которая выглядит следующим образом:

<tree>
    <node>
        <name>root</name>
        <depth>0</depth>
    </node>
    <node>
        <name>TELEVISIONS</name>
        <depth>1</depth>
    </node>
    <node>
        <name>TUBE</name>
        <depth>2</depth>
    </node>
    <node>
        <name>LCD</name>
        <depth>2</depth>
    </node>
    <node>
        <name>PLASMA</name>
        <depth>2</depth>
    </node>
    <node>
        <name>PORTABLE ELECTRONICS</name>
        <depth>1</depth>
    </node>
    <node>
        <name>MP3 PLAYERS</name>
        <depth>2</depth>
    </node>
    <node>
        <name>FLASH</name>
        <depth>3</depth>
    </node>
    <node>
        <name>CD PLAYERS</name>
        <depth>2</depth>
    </node>
    <node>
        <name>2 WAY RADIOS</name>
        <depth>2</depth>
    </node>
</tree>

которая представляет собой следующую структуру:

  • корень
    • ТЕЛЕВИЗОРЫ
      • ТРУБА
      • LCD
      • PLASMA
    • ПОРТАТИВНАЯ ЭЛЕКТРОНИКА
      • MP3-плееры
        • FLASH
      • CD-ПЛЕЕРЫ
      • 2 WAY RADIOS

Как я могу преобразовать этот плоский список XML во вложенный список HTML, используя XSLT?

PS: это дерево примеров из Управление иерархическими данными в MySQL .

Ответы [ 4 ]

5 голосов
/ 10 марта 2009

С этой формой плоского списка очень сложно работать в xslt, так как вам нужно найти положение следующей группировки и т. Д. Можно ли использовать другой xml? Например, с плоским xml:

<?xml version="1.0" encoding="utf-8" ?>
<tree>
  <node key="0">root</node>
  <node key="1" parent="0">TELEVISIONS</node>
  <node key="2" parent="1">TUBE</node>
  <node key="3" parent="1">LCD</node>
  <node key="4" parent="1">PLASMA</node>
  <node key="5" parent="0">PORTABLE ELECTRONICS</node>
  <node key="6" parent="5">MP3 PLAYERS</node>
  <node key="7" parent="6">FLASH</node>
  <node key="8" parent="5">CD PLAYERS</node>
  <node key="9" parent="5">2 WAY RADIOS</node>
</tree>

Это становится тривиально (очень эффективно):

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:key name="nodeChildren" match="/tree/node" use="@parent"/>
  <xsl:template match="tree">
    <ul>
      <xsl:apply-templates select="node[not(@parent)]"/>
    </ul>
  </xsl:template>
  <xsl:template match="node">
    <li>
      <xsl:value-of select="."/>
      <ul>
        <xsl:apply-templates select="key('nodeChildren',@key)"/>
      </ul>
    </li>
  </xsl:template>
</xsl:stylesheet>

Это вариант?

Конечно, если вы строите XML как иерархию, это еще проще; -p

1 голос
/ 23 июля 2010

очень полезно!

одно предложение при перемещении

1 голос
/ 10 марта 2009

В XSLT 2.0 было бы довольно легко с новыми функциями группировки.

В XSLT 1.0 это немного сложнее, но это работает:

<xsl:template match="/tree">
    <xhtml>
        <head/>
        <body>
            <ul>
                <xsl:apply-templates select="node[depth='0']"/>
                </ul>
            </body>
        </xhtml>
    </xsl:template>

<xsl:template match="node">
    <xsl:variable name="thisNodeId" select="generate-id(.)"/>
    <xsl:variable name="depth" select="depth"/>
    <xsl:variable name="descendants">
        <xsl:apply-templates select="following-sibling::node[depth = $depth + 1][preceding-sibling::node[depth = $depth][1]/generate-id() = $thisNodeId]"/>
        </xsl:variable>
    <li>
        <xsl:value-of select="name"/>
        </li>
    <xsl:if test="$descendants/*">
        <ul>
            <xsl:copy-of select="$descendants"/>
            </ul>
        </xsl:if>
    </xsl:template>

Суть дела - длинная и уродливая переменная «потомков», которая ищет узлы после текущего узла, у которых «глубина» дочернего элемента больше текущей глубины, но не после другого узла, который будет иметь такой же глубина как текущая глубина (потому что если бы они были, они были бы дочерними элементами этого узла вместо текущего).

Кстати, в вашем примере с ошибкой: «FLASH» должен быть потомком «MP3 PLAYERS», а не «родным братом».

EDIT

Фактически (как упомянуто в комментариях), в «чистом» XSLT 1.0 это не работает по двум причинам: выражение пути неправильно использует generate-id (), и нельзя использовать «фрагмент дерева результатов» в выражение пути.

Вот правильная версия шаблона узла "XSLT 1.0" (успешно протестированная с Saxon 6.5), которая не использует ни EXSLT, ни XSLT 1.1:

<xsl:template match="node">
    <xsl:variable name="thisNodeId" select="generate-id(.)"/>
    <xsl:variable name="depth" select="depth"/>
    <xsl:variable name="descendants">
        <xsl:apply-templates select="following-sibling::node[depth = $depth + 1][generate-id(preceding-sibling::node[depth = $depth][1]) = $thisNodeId]"/>
        </xsl:variable>
    <xsl:variable name="descendantsNb">
        <xsl:value-of select="count(following-sibling::node[depth = $depth + 1][generate-id(preceding-sibling::node[depth = $depth][1]) = $thisNodeId])"/>
        </xsl:variable>
    <li>
        <xsl:value-of select="name"/>
        </li>
    <xsl:if test="$descendantsNb &gt; 0">
        <ul>
            <xsl:copy-of select="$descendants"/>
            </ul>
        </xsl:if>
    </xsl:template>

Конечно, нужно учитывать повторяющееся выражение пути, но без возможности превращать «фрагменты дерева результатов» в XML, который действительно может быть обработан, я не знаю, возможно ли это? (написание пользовательской функции, конечно, поможет, но гораздо проще использовать EXSLT)

Итог: используйте XSLT 1.1 или EXSLT, если можете!

2-е редактирование

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

0 голосов
/ 10 марта 2009

Вы на самом деле не сказали, как вы хотите, чтобы вывод html выглядел, но я могу вам сказать, что с точки зрения XSLT переход от плоской структуры к дереву будет сложным и дорогим, если Вы также основываете это на позиции предметов в дереве и их отношении к братьям и сестрам.

Было бы далеко лучше предоставить атрибут / узел <parent>, чем <depth>.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...