6 Stimmen

Wie kann ich mit XSLT einen Baum aus einer flachen XML-Liste erstellen?

Ich verwende ein minimalistisches MVC-Framework, bei dem die PHP-Steuergerät übergibt die DOM-Modell zum XSLT-Ansicht (z.B.. okapi ).

Um einen Navigationsbaum zu erstellen, habe ich verschachtelte Sets in MYSQL verwendet. Auf diese Weise habe ich am Ende ein XML-Modell, das wie folgt aussieht:

<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>

die die folgende Struktur darstellt:

  • Wurzel
    • FERNSEHEN
      • TUBE
      • LCD
      • PLASMA
    • TRAGBARE ELEKTRONIK
      • MP3-ABSPIELGERÄTE
        • FLASH
      • CD-PLAYERS
      • 2 WAY RADIOS

Wie kann ich diese flache XML-Liste mit XSLT in eine verschachtelte HTML-Liste umwandeln?

PS: Dies ist der Beispielbaum aus der Hierarchische Daten in MySQL verwalten .

5voto

Marc Gravell Punkte 970173

Diese Form der flachen Liste ist in xslt sehr schwer zu bearbeiten, da man die Position der nächsten Gruppierung usw. finden muss. Können Sie ein anderes Xml verwenden? Zum Beispiel mit der flachen 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>

Es wird trivial zu tun (sehr effizient):

<?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=""/>
  <xsl:template match="tree">
    <ul>
      <xsl:apply-templates select="node[not()]"/>
    </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>

Ist das eine Option?

Natürlich ist es noch einfacher, wenn man die Xml als Hierarchie aufbaut ;-p

0 Stimmen

Sie könnten sich die Münch'sche Methode der Gruppierung.

1 Stimmen

@Edward - sehen Sie noch einmal nach... sorry, aber Sie irren sich. In der Tat, wenn Sie nicht die Rohdaten verwenden, ist es nicht möglich zu tun, wie Sie in xslt sagen, da xslt selbst wohlgeformtes Xml ist. Die Ausgabe ist: <ul>[<li><ul>[etc]</ul></li>]</ul> con [] Bedeutung "fakultativ, abhängig von den Daten"

0 Stimmen

@Marc - Ich habe die obigen Daten kopiert und mit Saxon 6.5.5 und HE 9.3.0.4 in Sauerstoff ausgeführt. Die Ausgabe ist nicht gut geformt. <?xml version="1.0" encoding="UTF-8"? > <ul> <li>Root<ul> <li>TELEVISIONS<ul> <li>TUBE<ul/></li><li>LCD<ul/></li><li>PLASMA<ul/></li></ul>‌​</li><li>PORTABLE ELECTRONICS<ul><li>MP3 PLAYERS<ul><li>FLASH<ul/></li></ul></li><li>CD PLAYERS<ul/></li><li>2 WAY RADIOS<ul/></li></ul></li></ul></li> </ul>

1voto

Bambax Punkte 2692

In XSLT 2.0 wäre das mit den neuen Gruppierungsfunktionen recht einfach.

In XSLT 1.0 ist es ein wenig komplizierter, aber es funktioniert:

<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>

Der Kern der Sache ist die lange und hässliche "descendants"-Variable, die nach Knoten nach dem aktuellen Knoten, die eine "Tiefe" Kind größer als die aktuelle Tiefe haben, aber nicht nach einem anderen Knoten, die die gleiche Tiefe wie die aktuelle Tiefe haben würde (weil, wenn sie waren, würden sie Kinder dieses Knotens statt der aktuellen ein sein) sucht.

Übrigens gibt es einen Fehler in Ihrem Beispielergebnis: "FLASH" sollte ein Kind von "MP3 PLAYERS" sein und nicht ein Geschwisterkind.

EDITAR

In der Tat (wie in den Kommentaren erwähnt) funktioniert dies in "reinem" XSLT 1.0 aus zwei Gründen nicht: Der Pfadausdruck verwendet generate-id() falsch, und man kann kein "Ergebnisbaumfragment" in einem Pfadausdruck verwenden.

Hier ist eine korrekte XSLT 1.0 Version des "node" Templates (erfolgreich getestet mit Saxon 6.5), die weder EXSLT noch XSLT 1.1 verwendet:

<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>

Natürlich sollte man den Pfadausdruck, der wiederholt wird, faktorisieren, aber ohne die Möglichkeit, "Ergebnisbaumfragmente" in XML zu verwandeln, das tatsächlich verarbeitet werden kann, weiß ich nicht, ob das möglich ist? (das Schreiben einer benutzerdefinierten Funktion würde den Trick natürlich erledigen, aber dann ist es viel einfacher, EXSLT zu verwenden)

Fazit: Verwenden Sie XSLT 1.1 oder EXSLT, wenn Sie können!

2. Bearbeitung

Um zu vermeiden, dass der Pfadausdruck wiederholt werden muss, können Sie den Test auch ganz weglassen. Das Ergebnis ist dann einfach eine leere Stelle, die Sie entweder im Ergebnis belassen oder durch Nachbearbeitung eliminieren können.

0 Stimmen

Es scheint, dass Sie Dinge wie <xsl:if test="$descendants/*"> nicht tun können aber mit EXSLT können Sie <xsl:if test="exslt:node-set($descendants)/*"> tun

1voto

Shawn Punkte 184

Sehr hilfreich!

ein Vorschlag ist das Verschieben der < ul > innerhalb der Vorlage würde die leere ul entfernen.

<xsl:template match="tree">
      <xsl:apply-templates select="node[not()]"/>
  </xsl:template>
<xsl:template match="node">
   <ul>
    <li>
      <xsl:value-of select="."/>
      <xsl:apply-templates select="key('nodeChildren',@key)"/>  
    </li>
   </ul>
  </xsl:template>
</xsl:stylesheet>

0 Stimmen

Ja, aber Sie würden eine ul pro Artikel statt einer ul mit mehreren li erhalten; um die leere ul zu entfernen, müssten Sie Test für Knoten

0voto

annakata Punkte 72408

Sie haben noch nicht gesagt, wie die HTML-Ausgabe aussehen soll, aber ich kann Ihnen sagen, dass die Umwandlung einer flachen Struktur in eine Baumstruktur aus XSLT-Sicht komplex und kostspielig sein wird, wenn Sie auch die Position der Elemente in der Baumstruktur und ihre Beziehung zu den Geschwistern zugrunde legen.

Es wäre weit besser, eine <parent> Attribut/Knoten als die <depth> .

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X