In welcher Reihenfolge werden Vorlagen in einem XSLT-Dokument ausgeführt und stimmen sie mit dem Quell-XML oder der gepufferten Ausgabe überein?

73

Folgendes hat mich an XSLT immer verwirrt:

  1. In welcher Reihenfolge werden die Vorlagen ausgeführt, und
  2. Stimmen sie bei der Ausführung mit (a) dem ursprünglichen Quell-XML oder (b) der aktuellen Ausgabe des XSLT bis zu diesem Punkt überein?

Beispiel:

<person>
  <firstName>Deane</firstName>
  <lastName>Barker</lastName>
</person>

Hier ist ein Fragment von XSLT:

<!-- Template #1 -->
<xsl:template match="/">
  <xsl:value-of select="firstName"/> <xsl:value-of select="lastName"/>
</xsl:template>

<!-- Template #2 -->
<xsl:template match="/person/firstName">
  First Name: <xsl:value-of select="firstName"/>
</xsl:template>

Zwei Fragen dazu:

  1. Ich gehe davon aus, dass Vorlage Nr. 1 zuerst ausgeführt wird. Ich weiß nicht, warum ich das annehme - liegt es nur daran, dass es zuerst im Dokument erscheint?
  2. Wird Vorlage Nr. 2 ausgeführt? Es stimmt mit einem Knoten im Quell-XML überein, aber bis wir zu dieser Vorlage gelangen (vorausgesetzt, sie wird als zweite ausgeführt), befindet sich der Knoten "firstName" nicht im Ausgabebaum.

Sind "spätere" Vorlagen also dem verpflichtet, was in "früheren" Vorlagen geschehen ist, oder arbeiten sie mit dem Quelldokument, ohne zu wissen, was "vor" ihnen transformiert wurde? (Alle diese Wörter stehen in Anführungszeichen, weil es mir schwer fällt, zeitbasierte Probleme zu diskutieren, wenn ich wirklich keine Ahnung habe, wie die Vorlagenreihenfolge überhaupt bestimmt wird ...)

Im obigen Beispiel haben wir eine Vorlage, die mit dem Stammknoten ("/") übereinstimmt, der nach Abschluss der Ausführung im Wesentlichen alle Knoten aus der Ausgabe entfernt hat. Würde dies in diesem Fall die Ausführung aller anderen Vorlagen verhindern, da nach Abschluss dieser ersten Vorlage nichts mehr übereinstimmt?

Bis jetzt habe ich mich mit späteren Vorlagen befasst, die nicht ausgeführt werden, weil die Knoten, auf denen sie gearbeitet haben, nicht in der Ausgabe erscheinen, aber was ist mit der Umkehrung? Kann eine "frühere" Vorlage einen Knoten erstellen, mit dem eine "spätere" Vorlage etwas anfangen kann?

Betrachten Sie diese XSL in derselben XML-Datei wie oben:

<!-- Template #1 -->
<xsl:template match="/">
  <fullName>
    <xsl:value-of select="firstName"/> <xsl:value-of select="lastName"/>
  </fullName>
</xsl:template>

<!-- Template #2 -->
<xsl:template match="//fullName">
  Full Name: <xsl:value-of select="."/>
</xsl:template>

Vorlage Nr. 1 erstellt einen neuen Knoten mit dem Namen "fullName". Vorlage Nr. 2 stimmt mit demselben Knoten überein. Wird Vorlage 2 ausgeführt, weil der Knoten "fullName" in der Ausgabe vorhanden ist, wenn wir zu Vorlage 2 gelangen?

Mir ist klar, dass ich das "Zen" von XSLT zutiefst ignoriere. Bisher bestanden meine Stylesheets aus einer Vorlage, die mit dem Stammknoten übereinstimmt, und sind von dort aus vollständig prozedural. Ich bin es leid, das zu tun. Ich würde XSLT lieber richtig verstehen, daher meine Frage.

Deane
quelle
Sie haben das / in der 2. Person-Tag in Ihrer Beispiel-XML verpasst.
Chris R
3
Das "Zen", das ich gelernt habe - der Ausführungsprozess eines XSLT ist XML-zentriert, nicht XSL-zentriert. Die Struktur des XML bestimmt den Fluss, nicht die Struktur des XSL. Dies war eines der großen Stücke, die ich all die Jahre nicht verstanden habe.
Deane
1
match = "// fullName" ist dasselbe wie match = "fullName". Ein Muster testet, ob ein bestimmter Knoten mit ihm aus einem beliebigen Kontext übereinstimmt, im Gegensatz zu einem XPath- Ausdruck, der Knoten aus einem bestimmten Kontext auswählt .
Evan Lenz

Antworten:

91

Ich liebe deine Frage. Sie sprechen sehr gut über das, was Sie noch nicht verstehen. Sie brauchen nur etwas, um die Dinge zusammenzubinden. Meine Empfehlung ist, dass Sie "Wie XSLT funktioniert" lesen , ein Kapitel, das ich geschrieben habe, um genau die Fragen zu beantworten, die Sie stellen. Ich würde gerne hören, ob es die Dinge für Sie zusammenhält.

Weniger formal werde ich versuchen, jede Ihrer Fragen zu beantworten.

  1. In welcher Reihenfolge werden die Vorlagen ausgeführt, und
  2. Stimmen sie bei der Ausführung mit (a) dem ursprünglichen Quell-XML oder (b) der aktuellen Ausgabe des XSLT bis zu diesem Punkt überein?

Zu jedem beliebigen Punkt in XSLT - Verarbeitung gibt es in gewissem Sinne, zwei Kontexten, die Sie identifizieren als (a) und (b): wo Sie in der sind Quellbaum , und wo Sie sind im Ergebnisbaum . Wo Sie sich im Quellbaum befinden, wird als aktueller Knoten bezeichnet . Es kann sich ändern und rund um den Quellbaum springen, wenn Sie beliebige Knotensätze auswählen, die mit XPath verarbeitet werden sollen. Konzeptionell "springen" Sie jedoch nie auf die gleiche Weise um den Ergebnisbaum herum. Der XSLT-Prozessor erstellt es ordnungsgemäß. Zuerst wird der Wurzelknoten des Ergebnisbaums erstellt. Anschließend werden untergeordnete Elemente hinzugefügt, und das Ergebnis wird in der Reihenfolge der Dokumente erstellt (Tiefe zuerst). [Ihr Beitrag motiviert mich, meine Software-Visualisierung für XSLT-Experimente wieder aufzunehmen ...]

Die Reihenfolge der Vorlagenregeln in einem Stylesheet spielt keine Rolle. Anhand des Stylesheets können Sie nicht erkennen, in welcher Reihenfolge die Vorlagenregeln instanziiert werden, wie oft eine Regel instanziiert wird oder ob sie überhaupt instanziiert wird. ( match="/"ist eine Ausnahme; Sie können immer wissen, dass es ausgelöst wird.)

Ich gehe davon aus, dass Vorlage Nr. 1 zuerst ausgeführt wird. Ich weiß nicht, warum ich das annehme - liegt es nur daran, dass es zuerst im Dokument erscheint?

Nee. Es wird zuerst aufgerufen, selbst wenn Sie es zuletzt in das Dokument einfügen. Die Reihenfolge der Vorlagenregeln spielt keine Rolle (außer unter einer Fehlerbedingung, wenn mehr als eine Vorlagenregel mit derselben Priorität mit demselben Knoten übereinstimmt; selbst dann ist dies für den Implementierer optional und Sie sollten sich niemals auf ein solches Verhalten verlassen). Es wird zuerst aufgerufen, da das erste, was immer passiert, wenn Sie einen XSLT-Prozessor ausführen, ein virtueller Aufruf von ist <xsl:apply-templates select="/"/> . Der eine virtuelle Aufruf erstellt den gesamten Ergebnisbaum. Draußen passiert nichts. Sie können das Verhalten dieser Anweisung anpassen oder "konfigurieren", indem Sie Vorlagenregeln definieren.

Wird Vorlage Nr. 2 ausgeführt? Es stimmt mit einem Knoten im Quell-XML überein, aber bis wir zu dieser Vorlage gelangen (vorausgesetzt, sie wird als zweite ausgeführt), befindet sich der Knoten "firstName" nicht im Ausgabebaum.

Vorlage Nr. 2 (oder andere Vorlagenregeln) werden niemals ausgelöst, es sei denn, Sie haben <xsl:apply-templates/>irgendwo in der match="/"Regel einen Aufruf . Wenn Sie keine haben, werden keine anderen Vorlagenregeln als match="/"ausgelöst. Stellen Sie sich das so vor: Damit eine Vorlagenregel ausgelöst wird, kann sie nicht einfach mit einem Knoten in der Eingabe übereinstimmen. Es muss mit einem Knoten übereinstimmen, den Sie (mit ) verarbeiten möchten <xsl:apply-templates/>. Umgekehrt stimmt es weiterhin so oft mit dem Knoten überein, wie Sie ihn verarbeiten möchten.

Würde [die match="/" Vorlage] die Ausführung aller anderen Vorlagen verhindern, da nach Abschluss dieser ersten Vorlage nichts mehr übereinstimmt?

Diese Regel geht dem Rest durch nichts voraus <xsl:apply-templates/>. Es gibt noch viele Knoten, die im Quellbaum verarbeitet werden könnten . Sie sind immer alle da, reif für die Ernte; Verarbeiten Sie jeden so oft, wie Sie möchten. Die einzige Möglichkeit, sie mithilfe von Vorlagenregeln zu verarbeiten, ist der Aufruf <xsl:apply-templates/>.

Bis jetzt habe ich mich mit späteren Vorlagen befasst, die nicht ausgeführt werden, weil die Knoten, auf denen sie gearbeitet haben, nicht in der Ausgabe erscheinen, aber was ist mit der Umkehrung? Kann eine "frühere" Vorlage einen Knoten erstellen, mit dem eine "spätere" Vorlage etwas anfangen kann?

Es ist nicht so, dass eine "frühere" Vorlage einen neuen Knoten erstellt, der verarbeitet werden soll. Es ist so, dass eine "frühere" Vorlage wiederum mehr Knoten aus dem Quellbaum verarbeitet, wobei dieselbe Anweisung verwendet wird ( <xsl:apply-templates). Sie können sich vorstellen, dass dieselbe "Funktion" rekursiv mit jeweils unterschiedlichen Parametern aufgerufen wird (die zu verarbeitenden Knoten werden durch den Kontext und das selectAttribut bestimmt).

Am Ende erhalten Sie einen baumstrukturierten Stapel rekursiver Aufrufe derselben "Funktion" ( <xsl:apply-templates>). Und diese Baumstruktur ist isomorph zu Ihrem tatsächlichen Ergebnis. Nicht jeder merkt das oder hat so darüber nachgedacht; Das liegt daran, dass wir noch keine effektiven Visualisierungswerkzeuge haben.

Vorlage Nr. 1 erstellt einen neuen Knoten mit dem Namen "fullName". Vorlage Nr. 2 stimmt mit demselben Knoten überein. Wird Vorlage 2 ausgeführt, weil der Knoten "fullName" in der Ausgabe vorhanden ist, wenn wir zu Vorlage 2 gelangen?

Nee. Die einzige Möglichkeit, eine Verarbeitungskette zu erstellen, besteht darin, sie explizit auf diese Weise einzurichten. Erstellen Sie beispielsweise eine Variable, $tempTreedie das neue <fullName>Element enthält , und verarbeiten Sie es dann wie folgt <xsl:apply-templates select="$tempTree">. Um dies in XSLT 1.0 zu tun, müssen Sie die Variablenreferenz mit einer Erweiterungsfunktion (z. B. exsl:node-set()) umschließen, aber in XSLT 2.0 funktioniert es so wie es ist.

Unabhängig davon, ob Sie Knoten aus dem ursprünglichen Quellbaum oder in einem von Ihnen erstellten temporären Baum verarbeiten, müssen Sie in beiden Fällen explizit angeben, welche Knoten Sie verarbeiten möchten.

Was wir nicht behandelt haben, ist, wie XSLT all sein implizites Verhalten erhält. Sie müssen auch die integrierten Vorlagenregeln verstehen . Ich schreibe ständig Stylesheets, die nicht einmal eine explizite Regel für den Stammknoten enthalten (match="/"). Stattdessen verlasse ich mich auf die integrierte Regel für Stammknoten (Vorlagen auf untergeordnete Knoten anwenden), die mit der integrierten Regel für Elementknoten identisch ist. Auf diese Weise kann ich große Teile der Eingabe ignorieren, den XSLT-Prozessor automatisch durchlaufen lassen und nur dann etwas Besonderes tun, wenn er auf einen Knoten stößt, an dem ich interessiert bin. Oder ich könnte eine einzelne Regel schreiben, die alles rekursiv kopiert (Identitätsumwandlung genannt) und nur bei Bedarf überschreibt, um inkrementelle Änderungen an der Eingabe vorzunehmen. Nachdem Sie "Funktionsweise von XSLT" gelesen haben, müssen Sie als Nächstes die "Identitätstransformation" nachschlagen.

Mir ist klar, dass ich das "Zen" von XSLT zutiefst ignoriere. Bisher bestanden meine Stylesheets aus einer Vorlage, die mit dem Stammknoten übereinstimmt, und sind von dort aus vollständig prozedural. Ich bin es leid, das zu tun. Ich würde XSLT lieber richtig verstehen, daher meine Frage.

Ich applaudiere dir. Jetzt ist es Zeit, die "rote Pille" einzunehmen: Lesen Sie "Wie XSLT funktioniert"

Evan Lenz
quelle
Übrigens, was Sie versucht haben (Vorlagenregeln schreiben, um Knoten zu verarbeiten, die von anderen Vorlagenregeln erstellt wurden), ist tatsächlich cool, obwohl es, wie gesagt, nur funktioniert, wenn Sie die Pipeline explizit mithilfe von Variablen und Variablenreferenzen einrichten. Ich habe mich manchmal gefragt, ob ich einen Verarbeitungskontext erstellen soll, in dem ein XSLT-Prozessor wiederholt für das Ergebnis aufgerufen wird, bis alle Elemente (z. B. in einem bestimmten Makronamensraum) verarbeitet werden und nicht mehr im Ergebnis erscheinen - ähnlich wie bei einem Rekursiv Einschlussmechanismus. Das wäre praktisch. Pipelines in XSLT sind syntaktisch etwas klobig.
Evan Lenz
Evan, deine Antwort und dein Beispielkapitel waren wirklich fantastisch. Ich glaube, ich habe es verstanden, außer einer Frage, die ich separat posten werde.
Deane
6
Hervorragender Artikel von @EvanLenz !! Danke vielmals.
GuruM
Hervorragende Antwort, hat mir sehr geholfen zu verstehen. Ich stimme zu, dass Visualisierungstools eine große Hilfe wären - insbesondere, um genau zu sehen, welche Operatoren Inhalte in der Quelle auswählen, welche Inhalte in der Ausgabe generieren und wie der aktuelle Knoten und Kontext verwaltet werden.
Chrispitude
Ich habe in den letzten Jahren einige Fortschritte bei einem Visualisierungstool gemacht: github.com/evanlenz/xslt-visualizer
Evan Lenz
6

Vorlagen stimmen im Quell-XML immer überein. Die Reihenfolge spielt also keine Rolle, es sei denn, zwei oder mehr Vorlagen stimmen mit denselben Knoten überein. In diesem Fall wird etwas kontraintuitiv die Regel mit der letzten übereinstimmenden Vorlage ausgelöst.

Mirod
quelle
2
Mein Verständnis war, wenn zwei oder mehr Vorlagen mit demselben Knoten übereinstimmen, wird die mit der spezifischsten Übereinstimmung ausgeführt. Ich nehme an, Sie meinen 2 oder mehr mit genau der gleichen Übereinstimmungsbedingung?
Chris R
2
sorta, die Regeln sind in der Spezifikation angegeben: w3.org/TR/xslt#conflict . Wenn Sie das Prioritätsattribut nicht verwenden, haben viele Muster dieselbe Priorität. Beachten Sie jedoch den letzten Absatz des Abschnitts in der Spezifikation: "Ein XSLT-Prozessor kann den Fehler signalisieren".
Mirod
Ich wusste nicht, dass die gleiche Priorität je nach Prozessorimplementierung entweder einen Fehler enthält oder die letzte verwendet, oder dass Sie die Priorität selbst mit dem Attribut festlegen können, danke.
Chris R
2

In Ihrem ersten Beispiel wird Vorlage Nr. 1 ausgeführt, da die Eingabe der XML-Eingabe am Stamm beginnt und dies die einzige Vorlage in Ihrem Stylesheet ist, die mit dem Stammelement übereinstimmt. Selbst wenn es im Stylesheet der 2. Platz wäre, würde es immer noch der 1. Platz sein.

In diesem Beispiel wird Vorlage 2 nicht ausgeführt, da Sie das Stammelement bereits mit Vorlage 1 verarbeitet haben und nach dem Stamm keine weiteren Elemente mehr verarbeitet werden müssen. Wenn Sie andere Elemente mit zusätzlichen Vorlagen verarbeiten möchten, sollten Sie diese ändern.

<xsl:template match="/">
  <xsl:apply-templates/>
</xsl:template>

Auf diese Weise können Sie für jedes Element, an dem Sie interessiert sind, eine Vorlage definieren und die XML-Datei logischer verarbeiten, anstatt sie prozedural auszuführen.

Beachten Sie auch, dass in diesem Beispiel nichts ausgegeben wird, da im aktuellen Kontext (dem Stamm) kein Vorname-Element vorhanden ist, sondern nur ein Personenelement.

<xsl:template match="/">
  <xsl:value-of select="person/firstName"/> <xsl:value-of select="person/lastName"/>
</xsl:template>

Ich finde es einfacher zu glauben, dass Sie durch die XML-Datei gehen, am Stamm beginnen und nach der Vorlage suchen, die diesem Element entspricht, und dann diesen Anweisungen folgen, um die Ausgabe zu generieren. Das XSLT wandelt das Eingabedokument in die Ausgabe um, sodass das Ausgabedokument zu Beginn der Umwandlung leer ist. Die Ausgabe wird nicht als Teil der Transformation verwendet, sondern ist nur die Ausgabe von ihr.

In Ihrem zweiten Beispiel wird Vorlage 2 nicht ausgeführt, da die Vorlage für die Eingabe-XML und nicht für die Ausgabe ausgeführt wird.

Chris R.
quelle
0

Evans Antwort ist im Grunde eine gute.

Eine Sache, die jedoch zu fehlen scheint, ist die Fähigkeit, Codestücke ohne Abgleich "aufzurufen". Dies würde - zumindest nach Meinung einiger Leute - eine viel bessere Strukturierung ermöglichen.

Ich habe ein kleines Beispiel gemacht, um zu zeigen, was ich meine.

<xsl:template match="/" name="dotable">
<!-- Surely the common html part could be placed somewhere else -->
    <!-- the head and the opening body -->
<html>
<head><title>Salary table details</title></head>

    <body>
<!-- Comments are better than nothing -->
    <!-- but that part should really have been somewhere else ... -->

<!-- Now do what we really want here ... this really is making the table! -->

<h1>Salary Table</h1>
<table border = "3" width="80%">
<xsl:for-each select="//entry">
    <tr>
        <td><xsl:value-of select="name" /></td>
        <td><xsl:value-of select="firstname" /></td>
        <td><xsl:value-of select="age" /></td>
        <td><xsl:value-of select="salary" /></td>
    </tr>
</xsl:for-each>
</table>

<!-- Now close out the html -->
</body>
</html>
<!-- this should also really be somewhere else -->

<!-- This approach works, but leads to horribly monolithic code -->
    <!-- Further - it leads to templates including code which is strictly -->
    <!-- not relevant to them. I've not found a way round this yet -->
</xsl:template>

Nachdem ich ein wenig herumgespielt und zunächst den Hinweis verwendet hatte, dass bei zwei übereinstimmenden Vorlagen die letzte im Code ausgewählt wird, und dann meinen Code (hier nicht alle gezeigt) neu strukturiert habe, habe ich dies erreicht, wie es scheint zu arbeiten, und hoffentlich generiert den richtigen Code, sowie die gewünschten Daten anzuzeigen -

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

<xsl:template name="dohtml">
  <html>
      <xsl:call-template name="dohead" />
      <xsl:call-template name="dobody" />
  </html>
</xsl:template>

<xsl:template name="dohead">
<head>
    <title>Salary details</title>
</head>
</xsl:template>

<xsl:template name="dobody">
<body>
    <xsl:call-template name="dotable" />
</body>
</xsl:template>

<xsl:template match="/entries" name="dotable">

<h1>Salary Table</h1>
<table border = "3" width="80%">
<xsl:for-each select="//entry">
    <tr>
        <td><xsl:value-of select="name" /></td>
        <td><xsl:value-of select="firstname" /></td>
        <td><xsl:value-of select="age" /></td>
        <td><xsl:value-of select="salary" /></td>
    </tr>
</xsl:for-each>
</table>

</xsl:template>

<xsl:template  match="/" name="main">
            <xsl:call-template name="dohtml" />
</xsl:template> 

[Scrollen Sie den Code oben nach oben nach unten, wenn Sie nicht alles sehen können]

Die Art und Weise, wie dies funktioniert, ist, dass die Hauptvorlage immer übereinstimmt - Übereinstimmungen mit /

Dies hat die Teile des Codes - Vorlagen -, die aufgerufen werden.

Dies bedeutet nun, dass es nicht möglich ist, eine andere Vorlage auf / abzugleichen, aber es ist möglich, explizit auf einem benannten Knoten übereinzustimmen, der in diesem Fall der Knoten der höchsten Ebene in den XML-Einträgen ist.

Eine kleine Änderung des Codes ergab das oben angegebene Beispiel.

user2151421
quelle