2009-06-04 4 views
24

Gibt es in XSLT eine Möglichkeit zu bestimmen, wo Sie sich in einem XML-Dokument befinden, wenn Sie ein Element verarbeiten?Wie geben Sie den aktuellen Elementpfad in XSLT aus?

Beispiel: das folgende XML-Doc Fragment ...

<Doc> 
    <Ele1> 
    <Ele11> 
     <Ele111> 
     </Ele111> 
    </Ele11> 
    </Ele1> 
    <Ele2> 
    </Ele2> 
</Doc> 

In XSLT Gegeben, wenn mein Kontext das Element "Ele111" ist, wie kann ich XSLT zur Ausgabe erhalten Sie den vollständigen Pfad? Ich möchte es ausgeben: "/ Doc/Ele1/Ele11/Ele111". Der Kontext dieser Frage: Ich habe ein sehr großes, sehr tiefes Dokument, das ich erschöpfend durchlaufen möchte (generisch mit Rekursion), und wenn ich ein Element mit einem bestimmten Attribut finde, möchte ich wissen, wo ich es gefunden habe . Ich nehme an, ich könnte meinen aktuellen Weg mitnehmen, während ich durchquere, aber ich würde denken, XSLT/XPath sollte es wissen.

Antwort

6

Denken Sie nicht, dass dies in XPath integriert ist. Sie benötigen wahrscheinlich eine rekursive Vorlage, wie die here, auf der ich dieses Beispiel basiert habe. Es wird jedes Element in einem XML-Dokument durchlaufen und den Pfad zu diesem Element in einem ähnlichen Stil wie den von Ihnen beschriebenen ausgeben.

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
     xmlns:xs="http://www.w3.org/2001/XMLSchema" 
     exclude-result-prefixes="xs" 
     version="2.0"> 

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

    <xsl:template match="//*"> 
     <path> 
     <xsl:for-each select="ancestor-or-self::*"> 
      <xsl:call-template name="print-step"/> 
     </xsl:for-each> 
     </path> 
     <xsl:apply-templates select="*"/> 
    </xsl:template> 

    <xsl:template name="print-step"> 
     <xsl:text>/</xsl:text> 
     <xsl:value-of select="name()"/> 
     <xsl:text>[</xsl:text> 
     <xsl:value-of select="1+count(preceding-sibling::*)"/> 
     <xsl:text>]</xsl:text> 
    </xsl:template> 

</xsl:stylesheet> 

Es gibt ein paar Komplikationen; Sehen Sie diesen Baum:

<root> 
    <child/> 
    <child/> 
</root> 

Wie erklären Sie den Unterschied zwischen den beiden untergeordneten Knoten? Du brauchst also einen Index in deine Item-Sequenz, zum Beispiel Kind 1 und Kind [2].

3

Sie können die VorfahrenXPath Axes verwenden, um alle Eltern und Großeltern zu gehen.

<xsl:for-each select="ancestor::*">... 
3

Ich bin nicht sicher, welche XSLT-Prozessor Sie verwenden, aber wenn es Saxon ist, können Sie Erweiterungsfunktion path() verwenden. Andere Prozessoren können dieselbe Funktionalität haben.

20

Die aktuell angenommene Antwort gibt falsche Pfade zurück. Beispielsweise würde das Element Ele2 in der OP-Beispiel-XML den Pfad /Doc[1]/Ele2[2] zurückgeben. Es sollte /Doc[1]/Ele2[1] sein.

Hier ist eine ähnliche XSLT 1.0-Vorlage, die die richtigen Pfade zurückgibt:

<xsl:template name="genPath"> 
    <xsl:param name="prevPath"/> 
    <xsl:variable name="currPath" select="concat('/',name(),'[', 
     count(preceding-sibling::*[name() = name(current())])+1,']',$prevPath)"/> 
    <xsl:for-each select="parent::*"> 
     <xsl:call-template name="genPath"> 
     <xsl:with-param name="prevPath" select="$currPath"/> 
     </xsl:call-template> 
    </xsl:for-each> 
    <xsl:if test="not(parent::*)"> 
     <xsl:value-of select="$currPath"/>  
    </xsl:if> 
    </xsl:template> 

Hier ist ein Beispiel, das ein path Attribut für alle Elemente hinzufügen.

XML-Eingabe

<Doc> 
    <Ele1> 
    <Ele11> 
     <Ele111> 
     <foo/> 
     <foo/> 
     <bar/> 
     <foo/> 
     <foo/> 
     <bar/> 
     <bar/> 
     </Ele111> 
    </Ele11> 
    </Ele1> 
    <Ele2/> 
</Doc> 

XSLT 1,0

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:output indent="yes"/> 
    <xsl:strip-space elements="*"/> 

    <xsl:template match="text()|@*"> 
    <xsl:copy> 
     <xsl:apply-templates select="node()|@*"/> 
    </xsl:copy> 
    </xsl:template> 

    <xsl:template match="*"> 
    <xsl:copy> 
     <xsl:attribute name="path"> 
     <xsl:call-template name="genPath"/> 
     </xsl:attribute> 
     <xsl:apply-templates select="node()|@*"/> 
    </xsl:copy>  
    </xsl:template> 

    <xsl:template name="genPath"> 
    <xsl:param name="prevPath"/> 
    <xsl:variable name="currPath" select="concat('/',name(),'[', 
     count(preceding-sibling::*[name() = name(current())])+1,']',$prevPath)"/> 
    <xsl:for-each select="parent::*"> 
     <xsl:call-template name="genPath"> 
     <xsl:with-param name="prevPath" select="$currPath"/> 
     </xsl:call-template> 
    </xsl:for-each> 
    <xsl:if test="not(parent::*)"> 
     <xsl:value-of select="$currPath"/>  
    </xsl:if> 
    </xsl:template> 

</xsl:stylesheet> 

XML-Ausgabe

<Doc path="/Doc[1]"> 
    <Ele1 path="/Doc[1]/Ele1[1]"> 
     <Ele11 path="/Doc[1]/Ele1[1]/Ele11[1]"> 
     <Ele111 path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]"> 
      <foo path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/foo[1]"/> 
      <foo path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/foo[2]"/> 
      <bar path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/bar[1]"/> 
      <foo path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/foo[3]"/> 
      <foo path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/foo[4]"/> 
      <bar path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/bar[2]"/> 
      <bar path="/Doc[1]/Ele1[1]/Ele11[1]/Ele111[1]/bar[3]"/> 
     </Ele111> 
     </Ele11> 
    </Ele1> 
    <Ele2 path="/Doc[1]/Ele2[1]"/> 
</Doc> 

hier eine andere Version ist Das gibt nur das Positionsprädikat aus, wenn es benötigt wird. Dieses Beispiel unterscheidet sich auch darin, dass nur der Pfad ausgegeben wird, anstatt ein Attribut hinzuzufügen.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
    <xsl:output method="text"/> 
    <xsl:strip-space elements="*"/> 

    <xsl:template match="text()"/> 

    <xsl:template match="*"> 
     <xsl:for-each select="ancestor-or-self::*"> 
      <xsl:value-of select="concat('/',local-name())"/> 
      <!--Predicate is only output when needed.--> 
      <xsl:if test="(preceding-sibling::*|following-sibling::*)[local-name()=local-name(current())]"> 
       <xsl:value-of select="concat('[',count(preceding-sibling::*[local-name()=local-name(current())])+1,']')"/> 
      </xsl:if> 
     </xsl:for-each> 
     <xsl:text>&#xA;</xsl:text> 
     <xsl:apply-templates select="node()"/> 
    </xsl:template> 

</xsl:stylesheet> 

der Eingang über Verwendung dieses Stylesheet Ausgänge:

/Doc 
/Doc/Ele1 
/Doc/Ele1/Ele11 
/Doc/Ele1/Ele11/Ele111 
/Doc/Ele1/Ele11/Ele111/foo[1] 
/Doc/Ele1/Ele11/Ele111/foo[2] 
/Doc/Ele1/Ele11/Ele111/bar[1] 
/Doc/Ele1/Ele11/Ele111/foo[3] 
/Doc/Ele1/Ele11/Ele111/foo[4] 
/Doc/Ele1/Ele11/Ele111/bar[2] 
/Doc/Ele1/Ele11/Ele111/bar[3] 
/Doc/Ele2 
+0

+1 für XPATH-Ausdrücke. –

1

Da XPath 3,0, wie durch Saxon 9.8 (Ausgaben) oder Saxon 9.7 mit version="3.0" im XSLT und XmlPrime 4 (unter Verwendung von --xt30) unterstützt sowie 2017 Freisetzungen von Altova (unter Verwendung von version="3.0" Sheets), gibt es die eingebaute Funktion in path (https://www.w3.org/TR/xpath-functions-30/#func-path, https://www.w3.org/TR/xpath-functions-31/#func-path), die für eine Eingabe wie

<?xml version="1.0" encoding="UTF-8"?> 
<Doc> 
    <Ele1> 
     <Ele11> 
      <Ele111> 
       <foo/> 
       <foo/> 
       <bar/> 
       <foo/> 
       <foo/> 
       <bar/> 
       <bar/> 
      </Ele111> 
     </Ele11> 
    </Ele1> 
    <Ele2/> 
</Doc> 

und ein Stylesheet wie

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    xmlns:math="http://www.w3.org/2005/xpath-functions/math" 
    exclude-result-prefixes="xs math" 
    version="3.0"> 

    <xsl:output method="text"/> 

    <xsl:template match="/"> 
     <xsl:value-of select="//*/path()" separator="&#10;"/> 
    </xsl:template> 

</xsl:stylesheet> 

der Ausgang gibt

/Q{}Doc[1] 
/Q{}Doc[1]/Q{}Ele1[1] 
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1] 
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1] 
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}foo[1] 
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}foo[2] 
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}bar[1] 
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}foo[3] 
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}foo[4] 
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}bar[2] 
/Q{}Doc[1]/Q{}Ele1[1]/Q{}Ele11[1]/Q{}Ele111[1]/Q{}bar[3] 
/Q{}Doc[1]/Q{}Ele2[1] 

Dieser Ausgang bei fehlenden Namensräume wie die meisten von Hand hergestellt Versuche nicht so kompakt ist, aber das Format hat den Vorteil (bei mindestens XPath 3.0 oder 3.1 unterstützt), um die Verwendung von Namespaces zu ermöglichen und ein Format für den zurückgegebenen Pfad zu erhalten, bei dem der Benutzer des Pfadausdrucks keine Namespace-Bindungen zum Auswerten einrichten muss.

+0

Schön. Ich habe replace() verwendet, um den Namespace-Kram loszuwerden, der in meinem speziellen Kontext Unordnung war. Ich bin froh, dass die Namespace-Informationen dort sind. – David

Verwandte Themen