Jak wypisać bieżącą ścieżkę elementu w XSLT?

W XSLT, czy istnieje sposób, aby określić, gdzie jesteś w dokumencie XML podczas przetwarzania elementu?

Przykład: podano następujący Fragment XML Doc...

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

W XSLT, jeśli mój kontekst jest elementem "Ele111", Jak mogę zmusić XSLT do wypisania pełnej ścieżki? Chciałbym, aby wyszło: "/Doc/Ele1/Ele11 / Ele111".

Kontekst tego pytania: mam bardzo duży, bardzo głęboki dokument, który chcę przejrzeć wyczerpująco( ogólnie używając rekurencji) i jeśli znajdę element z określonym atrybutem, chcę wiedzieć, gdzie go znalazłem. Przypuszczam, że mógłbym nieść moją obecną ścieżkę podczas trawersowania, ale myślę, że XSLT / XPath powinien wiedzieć.

Author: Daniel Haley, 2009-06-05

5 answers

Nie myśl, że jest to wbudowane w XPath, prawdopodobnie potrzebujesz rekurencyjnego szablonu, takiego jak ten TUTAJ , na którym oparłem ten przykład. Spowoduje to przejście każdego elementu w dokumencie XML i wyświetli ścieżkę do tego elementu w stylu podobnym do opisanego przez Ciebie.

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

Jest kilka komplikacji; rozważ to drzewo:

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

Jak odróżnić dwa węzły potomne? Więc potrzebujesz indeksu do swojej sekwencji elementów, dziecko1 oraz na przykład dziecko [2].

 6
Author: brabster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-06-04 21:44:57

Aktualnie zaakceptowana odpowiedź zwróci niepoprawne ścieżki. Na przykład element Ele2 W op sample XML zwróci ścieżkę /Doc[1]/Ele2[2]. Powinno być /Doc[1]/Ele2[1].

Oto podobny szablon XSLT 1.0, który zwraca poprawne ścieżki:

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

Oto przykład, który doda atrybut {[9] } do wszystkich elementów.

Wejście XML

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

Wyjście XML

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

Oto kolejna wersja to wyświetla predykat pozycyjny tylko wtedy, gdy jest to potrzebne. Ten przykład jest również inny, ponieważ po prostu wypisuje ścieżkę zamiast dodawać atrybut.

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

Używając powyższego wejścia, arkusz stylów wyświetla:

/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
 21
Author: Daniel Haley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-15 19:08:20

Nie jestem pewien, którego procesora XSLT używasz, ale jeśli jest używany, możesz użyć funkcji rozszerzenia path () . Inne procesory mogą mieć taką samą funkcjonalność.

 3
Author: MartinM,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-11 09:44:06

Możesz użyć przodka XPath Axes , aby przejść wszystkich rodziców i dziadków.

<xsl:for-each select="ancestor::*">...
 3
Author: Mister Lucky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-29 14:02:38

Od wersji XPath 3.0 obsługiwanej przez Saxon 9.8 (wszystkie edycje) lub Saxon 9.7 z version="3.0" w XSLT i XmlPrime 4 (używając --xt30), a także wydaniach Altova 2017 (używając arkuszy stylów version="3.0") wbudowana jest funkcja path ( https://www.w3.org/TR/xpath-functions-30/#func-path, https://www.w3.org/TR/xpath-functions-31/#func-path ), które dla wejścia jak

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

I arkusz stylów jak

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

Daje wyjście

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

Że wyjście nie jest jak Kompaktowy w przypadku braku przestrzeni nazw, jak większość ręcznie robionych prób, ale format ma tę zaletę (przynajmniej biorąc pod uwagę obsługę XPath 3.0 lub 3.1), aby umożliwić korzystanie z przestrzeni nazw i uzyskać format zwracanej ścieżki, który nie wymaga od użytkownika wyrażenia path skonfigurowania żadnych powiązań przestrzeni nazw w celu jej oceny.

 1
Author: Martin Honnen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-29 16:28:39