2009-06-04 14 views
24

In XSLT, c'è un modo per determinare dove ci si trova in un documento XML durante l'elaborazione di un elemento?Come si genera il percorso dell'elemento corrente in XSLT?

Esempio: Dato il seguente XML Doc Frammento ...

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

In XSLT, se il mio contesto è l'elemento "Ele111", come posso ottenere XSLT per l'uscita il percorso completo? Vorrei che uscisse: "/ Doc/Ele1/Ele11/Ele111".

Il contesto di questa domanda: ho un documento molto grande, molto profondo che voglio percorrere esaustivamente (genericamente usando la ricorsione), e se trovo un elemento con un particolare attributo, voglio sapere dove l'ho trovato . Suppongo che potrei portare avanti il ​​mio attuale percorso mentre attraversavo, ma penserei che XSLT/XPath dovrebbe saperlo.

risposta

6

Non pensare che questo sia incorporato in XPath, probabilmente hai bisogno di un modello ricorsivo, come quello here, su cui ho basato questo esempio. Camminerà su ogni elemento di un documento XML e genererà il percorso di quell'elemento in uno stile simile a quello che hai descritto.

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

Ci sono alcune complicazioni; considera questo albero:

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

Come si fa a distinguere tra i due nodi figlio? Quindi hai bisogno di un indice nella sequenza degli articoli, ad esempio bambino 1 e figlio [2].

3

È possibile utilizzare l'antenato XPath Axes per camminare su tutti i genitori e nonni.

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

io non sono sicuro di quale processore XSLT che si sta utilizzando, ma se si tratta di Saxon, è possibile utilizzare la funzione di estensione path(). Altri processori possono avere la stessa funzionalità.

20

La risposta attualmente accettata restituirà percorsi errati. Ad esempio, l'elemento Ele2 nell'X Sample XML restituisce il percorso /Doc[1]/Ele2[2]. Dovrebbe essere /Doc[1]/Ele2[1].

Ecco un simile modello XSLT 1.0 che restituisce i percorsi corretti:

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

Ecco un esempio che aggiungerà un attributo path a tutti gli elementi.

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

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

Ecco un'altra versione che emette solo il predicato posizionale se è necessario. Questo esempio è anche diverso dal fatto che sta semplicemente emettendo il percorso invece di aggiungere un attributo.

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

utilizzando l'ingresso di cui sopra, queste uscite foglio di stile:

/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 per le espressioni XPATH. –

1

Poiché XPath 3.0 come sostenuto da Saxon 9,8 (tutte le edizioni) o Saxon 9.7 con version="3.0" nella XSLT e XmlPrime 4 (utilizzando --xt30) così come 2017 stampa di Altova (utilizzando fogli di stile version="3.0") è incorporata in funzione path (https://www.w3.org/TR/xpath-functions-30/#func-path, https://www.w3.org/TR/xpath-functions-31/#func-path) che per un ingresso come

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

e un foglio di stile come

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

dà l'uscita

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

Tale uscita non è così compatta in caso di mancanza di namespace come la maggior parte dei tentativi fatti a mano ma il formato ha il vantaggio (A almeno il supporto per XPath 3.0 o 3.1) per consentire l'uso di spazi dei nomi e ottenere un formato per il percorso restituito che non richiede all'utente dell'espressione di percorso di impostare alcun binding di spazi dei nomi per valutarlo.

+0

Bello. Ho usato replace() per sbarazzarmi della roba del namespace, che era confusa nel mio particolare contesto. Sono contento che le informazioni sul namespace siano lì comunque. – David

Problemi correlati