2009-06-04 51 views
24

在XSLT中,有沒有辦法在處理元素時確定XML文檔的位置?如何在XSLT中輸出當前元素路徑?

實例:假設下面的XML文檔片段...

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

在XSLT,如果我的背景是元素「Ele111」,我怎麼能得到XSLT輸出的完整路徑?我希望它輸出:「/ Doc/Ele1/Ele11/Ele111」。

這個問題的上下文:我有一個非常大,非常深的文檔,我想詳盡地遍歷(一般使用遞歸),如果我找到一個具有特定屬性的元素,我想知道我在哪裏找到它。我想我可以在遍歷時繼續使用當前的路徑,但我會認爲XSLT/XPath應該知道。

回答

6

不要認爲這是內置於XPath中的,您可能需要遞歸模板,就像我已經基於此示例的一個here一樣。它將遍歷XML文檔中的每個元素,並以類似於您所描述的樣式的方式輸出該元素的路徑。

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

有一些併發症;考慮這棵樹:

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

你如何區分兩個子節點?因此,您需要在項目序列中找到一些索引,例如子節點1和子節點[2]。

3

你可以使用祖先XPath Axes走所有的父母和祖父母。

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

我不知道你使用的XSLT處理器,但如果是撒克遜人,你可以使用擴展功能path()。其他處理器可能有相同的功能。

20

當前接受的答案將返回不正確的路徑。例如,OP示例XML中的元素Ele2將返回路徑/Doc[1]/Ele2[2]。它應該是/Doc[1]/Ele2[1]

這裏的一個類似的XSLT 1.0模板返回正確的路徑:

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

下面是將一個path屬性添加到所有元素的例子。

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> 

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> 

這裏的另一個版本只有在需要時才輸出位置謂詞。這個例子也不同,它只是輸出路徑而不是添加屬性。

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

使用上面的輸入,這個樣式表輸出:

/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

對於XPATH表達式+1。 – 2014-11-13 16:07:42

1

由於的XPath 3.0如通過撒克遜9.8(所有版本)或撒克遜9.7使用在XSLT和XmlPrime 4 version="3.0"支持(使用--xt30)以及Altova公司的2017年版本中(使用version="3.0"樣式表)有內置path功能(https://www.w3.org/TR/xpath-functions-30/#func-pathhttps://www.w3.org/TR/xpath-functions-31/#func-path),其用於像

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

和類似

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

一個樣式表給出了輸出

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

即輸出是不緊湊在缺乏名字空間最手工製作的嘗試的情況下,但格式具有的優點(在最少給定的XPath 3.0或3.1支持)允許使用名稱空間,並獲取返回路徑的格式,不需要路徑表達式的用戶設置任何名稱空間綁定來評估它。

+0

不錯。我使用replace()來擺脫命名空間的東西,這在我的特定環境中很混亂。儘管如此,我很高興命名空間信息在那裏。 – David 2017-10-31 04:39:30

相關問題