2009-05-07 135 views
26

我正在處理一個XML文件,我希望保持節點數量的計數,以便在編寫新節點時可以將它用作ID。在XSLT中,如何從不同範圍增加全局變量?

此刻我有一個全局變量叫做'counter'。我可以在模板中訪問它,但我還沒有找到在模板中操作它的方法。

這裏是我的XSLT文件的壓縮版:

<xsl:variable name="counter" select="1" as="xs:integer"/> 

<xsl:template match="/"> 
    <xsl:for-each select="section"> 
     <xsl:call-template name="section"></xsl:call-template> 
    </xsl:for-each> 
</xsl:template> 

<xsl:template name="section"> 

    <!-- Increment 'counter' here --> 

    <span class="title" id="title-{$counter}"><xsl:value-of select="title"/></span> 
</xsl:template> 

任何建議如何從這裏走?

回答

44

其他人已經解釋如何變量是不可變的 - 有在XSLT沒有賦值語句(如一般而言,純函數式編程語言)。

我有迄今爲止提出的解決方案的替代方案。它避免了參數傳遞(在XSLT中冗長而醜陋 - 即使我承認這一點)。

在XPath,你可以簡單地計算它之前<section>元素的當前數量:

<xsl:template name="section"> 
    <span class="title" id="title-{1 + count(preceding-sibling::section)}"> 
    <xsl:value-of select="title"/> 
    </span> 
</xsl:template> 

(注:空格代碼格式化不會出現在你的結果,因爲只有空白文本節點因此不要強迫將指令放在同一行上)

這種方法的一大優點(與使用position()相反)是它僅依賴於當前節點,而不依賴於當前節點在當前節點列表上。如果您以某種方式更改了處理過程(例如,<xsl:for-each>不僅處理了部分,而且還處理了其他一些元素),那麼position()的值將不再需要與文檔中<section>元素的位置相對應。另一方面,如果您使用上面的count(),那麼它將始終對應於每個元素的位置。這種方法減少了與代碼的其他部分的耦合,這通常是非常好的事情。

count()的替代方法是使用<xsl:number>指令。這是默認的行爲將在同一水平線上,這恰好是你想要的號碼的所有名稱相似的元素:

<xsl:template name="section"> 
    <xsl:variable name="count"> 
    <xsl:number/> 
    </xsl:variable> 
    <span class="title" id="title-{$count}"> 
    <xsl:value-of select="title"/> 
    </span> 
</xsl:template> 

這是一個權衡冗長(無需額外的變量聲明,如果你仍然想使用屬性值模板花括號),但只是稍微如此,因爲它也極大地簡化了XPath表達式。

還有更多的改進空間。雖然我們已經取消了對當前節點列表的依賴,但仍然依賴於當前節點。這本身並不是一件壞事,但從模板看當前節點是什麼並不明顯。我們所知道的是該模板被命名爲「section」;要確切知道正在處理的內容,我們必須查看我們的代碼中的其他地方。但即便如此,情況也不一定如此。

如果你曾經感覺到一起使用<xsl:for-each><xsl:call-template>(如你的例子),退後一步,並找出如何使用<xsl:apply-templates>來代替。

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

<xsl:template match="section"> 
    <xsl:variable name="count"> 
    <xsl:number/> 
    </xsl:variable> 
    <span class="title" id="title-{$count}"> 
    <xsl:value-of select="title"/> 
    </span> 
</xsl:template> 

這不僅是方法更簡潔(<xsl:apply-templates/>取代了<xsl:for-each><xsl:call-template/>),但它也立即變得明確當前節點是什麼。您只需查看match屬性,即刻知道您正在處理的元素爲<section>,並且您要計算的元素爲<section>

有關如何使用模板規則(即<xsl:template>屬性具有match屬性)的簡明說明,請參閱"How XSLT Works"

+0

非常感謝你!這篇文章和回答非常有幫助 – anpatel 2012-01-25 22:07:58

+0

不客氣!很高興你發現它很有用。 – 2012-02-03 18:27:47

+0

對不起,埃文,但這是一個非常低效的解決方案(O(N^2))。使用參數傳遞的解決方案只能是O(N)。所有這些關於「冗長」的討論都只是這個 - 冗長而沒有提到關於效率的字眼。如果您提到所提議的解決方案的時間複雜性並將其與其他可能的解決方案進行比較,則可以使讀者更有用。由於這些原因,我認爲這個答案屬於輕型教程類型,不適用於生產工作。 – 2012-07-26 03:44:28

2

變量在本地範圍內,只能在xslt中讀取。

+0

我明白了。你知道我可以採取什麼方法來實現我的目標嗎? – Marcel 2009-05-07 06:22:46

+0

首先我會說你應該避免使用foreach構造和call-template。這是程序語句,XSLT是遞歸的。因此,您應該認爲它是遞歸的而不是程序性的。 用戶@警告顯示的是通過參數增加計數器的有效方法。然後更好地使用帶有參數的apply-template,每次調用時加1。 只要我不清楚,就評論一下。 – Luixv 2009-05-07 06:54:24

8

XSLT變量不能更改。您將從模板傳遞值到模板。

如果您使用XSLT 2.0,則可以使用參數並使用隧道將變量傳播到正確的模板。

你的模板將是這個樣子:

<xsl:template match="a"> 
<xsl:param name="count" select="0"> 
    <xsl:apply-templates> 
    <xsl:with-param select="$count+1"/> 
    </xsl:apply-templates> 
</xsl:template> 

也期待在使用產生-ID(),如果你想創建的ID。

+2

+1 for generate-id()謝謝! – 2012-03-05 16:03:52

0

自己沒有嘗試過,但可以嘗試將參數傳遞給模板。 在您的第一個模板中,您可以在for-each語句中將參數設置爲count()(或current()可能?),然後將該值傳遞給您的「部分」模板。

這裏有更多關於passing parameters to templates

2

根據您的XSLT處理器,您可以引入腳本函數到您的XLST。例如,Microsoft XML庫支持包含javascript。一個例子見http://msdn.microsoft.com/en-us/library/aa970889(VS.85).aspx。如果您打算在公共客戶端瀏覽器上部署/執行XSLT,這種策略顯然不起作用;它必須由特定的XSLT處理器完成。

+0

我以前曾經使用過這個技巧,但是它只能作爲最後的手段來完成,在這種情況下,將其沿着不可變/功能線進行構建會是令人望而卻步的。但它的工作。在某些場景(如.NET)中,您可以使用擴展對象在xslt之外執行相同的操作,但是又如此:這並不是一個好主意。 – 2009-05-13 09:04:25

1

你可以使用position()函數來做你想做的事。它看起來像這樣。

<xsl:template match="/"> 
    <xsl:for-each select="section"> 
    <xsl:call-template name="section"> 
     <xsl:with-param name="counter" select="{position()}"/> 
    </xsl:call-template> 
    </xsl:for-each> 
</xsl:template> 

<xsl:template name="section"> 
    <xsl:param name="counter"/> 
    <span class="title" id="title-{$counter}"> 
    <xsl:value-of select="title"/> 
    </span> 
</xsl:template> 
+0

xsl:with-param的select屬性是一個表達式,而不是可以使用AVT的字符串。 – jelovirt 2009-05-07 10:39:45

+0

此外,不需要傳遞position()的值,因爲不會更改當前節點列表。您可以使用「section」模板中的position()輕鬆訪問相同的值。 – 2009-05-13 09:33:31

6

XSLT中的變量是不可變的,因此您必須考慮到這個問題。您既可以直接使用position()

<xsl:template match="/"> 
    <xsl:for-each select="section"> 
     <xsl:call-template name="section"/> 
    </xsl:for-each> 
</xsl:template> 

<xsl:template name="section"> 
    <span class="title" id="title-{position()}"><xsl:value-of select="title"/></span> 
</xsl:template> 

或者在多個模板導向的方式:

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

<xsl:template match="section"> 
    <span class="title" id="title-{position()}"><xsl:value-of select="title"/></span> 
</xsl:template> 
0

使用<xsl:variable name="RowNum" select="count(./preceding-sibling::*)" />$ ROWNUM爲遞增值。

如:<xsl:template name="ME-homeTiles" match="Row[@Style='ME-homeTiles']" mode="itemstyle"> <xsl:variable name="RowNum" select="count(./preceding-sibling::*)" /> ...<a href="{$SafeLinkUrl}" class="tile{$RowNum}"><img ....></a>

這將爲鏈接班,值tile1,tile2,tile3等等