技巧: 用節點集計數


<script type="text/javascript"> google_ad_client = "pub-5033576919944123"; google_ad_width = 728; google_ad_height = 90; google_ad_format = "728x90_as"; google_ad_type = "text_image"; //2007-10-24: csdn.blog google_ad_channel = "8548491739"; </script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>
技巧: 用節點集計數
英文原文
使用 XSLT 節點集的特殊特性使事情變得更加容易
通過使用節點集操作的特殊特性,可以使許多常見的 XSLT 任務(包括簡單循環)變得更容易。本技巧文章討論將節點集用於簡單和有效的循環控制。

和所有編程語言一樣,着手瞭解 XSLT 的內置數據類型和結構對掌握該語言來說是最基本的。節點集是 XPath 的數據類型中最有趣的事物(它們形成了 XSLT 的數據類型的基礎)。在本文中,我將演示兩種不是顯而易見的方法,使您可以用節點集來簡化 XSLT 處理。

傳統 XSLT 中的計數循環
XSLT 爲迭代一個節點集中的所有項提供了一個原語操作:xsl:for-each。如果您認真地使用過 XSLT,那麼您還可能熟悉根據給定數字(而不是根據給定的節點集)進行迭代的標準方法。作爲示例,下面的 XSLT 模板採用一個數字,並打印那麼多的星號:

清單 1. 打印指定數的星號的模板


  <xsl:template name="print-asterisks">

  <xsl:param name="count"/>

  <!-- The termination condition (infinite recursion is no fun) -->

  <xsl:if test="$count">

    <!-- print the asterisk for this iteration -->

    <xsl:text>*</xsl:text>

    <!-- recursive call to print remaining asterisks -->

    <xsl:call-template name="print-asterisks">

      <xsl:param name="count" select="$count-1"/>

    </xsl:call-template>

  </xsl:if>

</xsl:template>

如果您不熟悉這一技術,請立刻找一本好的 XSLT 教程或書籍,以瞭解這種遞歸模板是如何工作的。這是 XSLT 中最基本的技術之一。即使本技巧文章只提供了一個不常見到的變通方法,您仍然可以在使用 XSLT 時做到八、九不離十,而無需具有在睡夢中還能背誦這幾段代碼的本領。

該模板只用了一個參數,即要計數並打印的星號數目。當最初調用該模板時,傳入打印星號的總數。在該腳本中,有關錯誤檢查,我寫得很簡單。例如,如果您給 count 傳入了一個負數,那麼結果將是無窮遞歸。當計數降爲零時,在正常情況下,if 測試不做任何事情來避免無窮遞歸。然後,打印一個星號並遞歸調用該模板(從計數中減 1)來打印剩餘星號。

性能是這種方法的最大問題。這種原始形態的遞歸會佔據許多資源。大多數 XSLT 處理器認爲它是一種最差的遞歸方式示例,可以將它優化成一個常規的迭代。這很有用,但如果它每次都經歷模板分派機制,那麼甚至這樣的迭代也會變慢。或許到目前爲止某些 XSLT 處理器甚至有更復雜的優化器,可以消除這一開銷,但我還不指望這類先進技術。通常,當遞歸中的每一步都是一個細小操作(如打印一個星號)時,開銷會是一個問題。

節點集訣竅
如果您能夠設計正好是您想要的長度的節點集,那麼可以將 xsl:for-each 用於這種循環。完成這一任務的一種方法是,採用比您想要的長度長的節點集,並用正確長度選擇子集。下面的這個 XPath 表達式就是完成這一任務的,其中 count 是期望的數目,nodeset 是您知道的比 count 長的節點集:



$nodeset[position() &lt; $count]

從源節點集,謂詞創建另一個正好有 count 個節點的節點集。主要問題是從哪裏獲取 nodeset。只要產生的節點集足夠大,那麼任何獲取節點集的方法對於該任務來說都可以。然後,您可以使用 XPath //node() 從源文檔獲取一批節點 — 或者更好一些,獲取所有節點。問題是您不能總是依靠源文檔的長度。樣式表本身可能是一種更好的來源,因爲當編寫轉換時,您可以確保它的大小,如果必要,甚至可以用虛擬節點填充它。表達式 document("") 將整個樣式表作爲一個輔助源文檔。

通過使用這些訣竅,您可以將打印星號的模板重新編寫成:

清單 2. 將定製的節點集用於循環


  <!-- use all nodes in the current stylesheet as a source -->

<xsl:variable name="nodeset" select="document('')//node()"/>



<xsl:template name="print-asterisks">

  <xsl:param name="count"/>

  <xsl:if test="$count > count($nodeset)">

    <!-- Basic safety measure: better to crash and burn

         than to fail in a non-obvious way -->

    <xsl:message terminate="yes">

      Not enough nodes for iteration

    </xsl:message>

  </xsl:template>

  <!-- Execute the loop, using the node set we want -->

  <xsl:for-each select="$nodeset[position() < $count]">

    <xsl:text>*</xsl:text>

  </xsl:for-each>

</xsl:template>

樣式表中所有節點的節點集都是在頂層一次性構造的,並且對於轉換中任何這種循環可以重用這些節點集。該模板首先檢查是否有足夠多的節點用於迭代,如果沒有,則中止所有處理。雖然您可以選擇更完美的錯誤處理,但請不要省略該檢查,否則可能要求一定數量的迭代,沒有任何警告以較少的迭代數目告終。這類錯誤就很難發現。

另一個可能的缺點是:對於某些 XSLT 實現,document("")//node() 操作在時間和空間方面的花費會很大。可能需要重新解析樣式表,然後對每個節點進行檢測。但這對於樣式表執行來說,這只是一次性的懲罰。如果您多次用到這種訣竅,在速度方面,可能還將獲得可觀的改進。如果您只需要較短長度的迭代,則可以使用變體 document("")/node(), 它可以限制對頂層的節點挖掘。按照這種思路,還有一些其它訣竅可用來適合您的目的。例如,可以通過同時從樣式表源文檔 //node()|document("")//node() 創建一個節點集來減少出現用完節點的情況。

結束語
一些挑剔之人可能認爲這種技術太過平庸,但是隻要您理解了用於 XSLT 的標準迭代訣竅,那麼當您真正需要它時,就可以使用這一快捷方式。這個訣竅看來對於 XPath 和 XSLT 2.0 似乎是多餘的,因爲它們都有更完善的內置循環原語,但在將這些原語最終定下來並且相容的實現出現之前,可能還要有一段時間。

參考資料


<script type="text/javascript"> google_ad_client = "pub-5033576919944123"; google_ad_width = 728; google_ad_height = 90; google_ad_format = "728x90_as"; google_ad_type = "text_image"; //2007-10-24: csdn.blog google_ad_channel = "8548491739"; </script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>
發佈了13 篇原創文章 · 獲贊 0 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章