標籤:
SLT 2.0 與 XPath 2.0 攜手並肩。之所以將它們分開描述,是因為 XPath 2.0 也可用於XSLT之外的環境,比如XQuery 1.0。但是對於XSLT使用者來說,它們是互相關聯的。你不能在XSLT 1.0 中使用 XPath 2.0,或者在 XSLT 2.0 中使用 XPath 1.0。(至少到目前為止,W3C還沒有這種組合的提議。)
注
你也許會問:XSLT 1.1 發生了什嗎?XSLT 1.1 已經取消了。官方的說法是在2001年8月,實際上在幾個月前,XSLT工作群組中止了XSLT 1.1,集中力量開發XSLT和XPath 2.0,將XSLT 1.1 的前期要求轉到XSLT 2.0。
歡迎到來
許多的XSLT使用者在很大程度上參與了新版XSLT的制訂過程。就像許多語言的第一版一樣,不經過實踐的檢驗,這個語言的哪些擴充被證明是最重要的往往不很清楚。自從1999年11月16日XSLT 1.0 成了推薦標準以後,顯然在某些方面缺少的功能應當在下一個版本中包括進來,本文我們將從下面四個方面看看XSLT 2.0:
- 從結果樹片斷(result tree fragments)到節點集(node-sets)的轉化
- 有多個輸出的文檔
- 對分組的內在支援
- 使用者定義函數(以XSLT實現)
RTF的末日
在XSLT 1.0 中結果樹片斷(Result Tree Fragment,RTF)類型很像節點集,實際上卻是個二等公民。當你使用xsl:variable構建一個臨時樹的時候,得到的就是RTF。問題是你不能使用XPath運算式訪問這個樹的內容,除非你使用一個供應商提供的擴充函數,通常是node-set()之類,來把這個RTF轉化成一類節點集(含有一個根節點)。這種RTF資料類型的存在本來是為了減少實現時的限制,但由於幾乎所有的XSLT處理器都提供諸如node-set()之類的擴充函數,這種考慮顯得有些不切實際。不管怎樣,突破這種限制的呼聲總會越來越明顯,因為把複雜的變換分解成一系列簡單的變換非常重要。
不知道你有沒有猜出來,XSLT 2.0 為RTF開啟了這個門。現在當你使用xsl:variable建立一個臨時樹,這個變數的值就是個真正的節點集。事實上,用XPath 2.0 的術語來講,它是個真正的節點序列(true node sequence),包含一個 document node (這是一個XPath 2.0 的名稱,也就是XPath 1.0 的"root node")。在這個序列上你可以使用XPath運算式深入到這棵樹,對它應用模板(template)等等,就像使用其它源檔案一樣。有了XLST 2.0 就不再需要node-set()之類的擴充函數。
允許多個輸出文檔
許多XSLT 1.0 處理器所提供的另一個擴充就是多個輸出文檔,這種擴充被證明非常有用,對具有多個頁面的網站的靜態產生尤其如此。問題在於這個擴充功能不是標準的。每個處理器完成這種擴充的元素都不一樣,例如 saxon:output, xt:document等等。
XSLT 2.0 使用 xsl:result-document元素提供了多個輸出文檔的一個標準方法。下面的樣式表例子構建了多個輸出文檔,一個“主要結果檔案”和幾個“次級結果檔案”。主要結果檔案以XHTML儲存,次級結果檔案以純文字方式儲存。
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/1999/xhtml"> <xsl:output method="xhtml"/> <xsl:output method="text" name="textFormat"/> <xsl:template match="/"> <html> <head> <title>Links to text documents</title> </head> <body> <p>Here is a list of links to text files:</p> <ul> <xsl:apply-templates select="//textBlob"/> </ul> </body> </html> </xsl:template> <xsl:template match="textBlob"> <xsl:variable name="uri" select="concat(‘text‘, position(), ‘.txt‘)"/> <li> <a href="{$uri}"> <xsl:value-of select="$uri"/> </a> </li> <xsl:result-document href="{$uri}" format="textFormat"> <xsl:value-of select="."/> </xsl:result-document> </xsl:template></xsl:stylesheet>
xsl:document的 href 屬性用來為相應的輸出檔案指定一個URI。對於許多處理器來說,這意味著以該檔案名稱隱藏檔。而format屬性用來引用一個已命名的指定輸出。在這裡,它指向以textFormat命名的xsl:output元素。
上例中的XHTML輸出方式也是在XSLT 2.0中新引入的,這裡勿須贅言。
簡化的分組
XSLT 1.0 沒有對分組的內在支援。特定的分組問題無疑可使用各種技巧解決,例如Muenchian方法,但是這種解決方案相當複雜難懂。對XSLT 2.0 的要求之一就是必須要簡化分組,就像我們在下例中所看到的,這種方法能滿足要求。
出現在 Requirements document 和XSLT 2.0 工作草案的這個例子是關於把下面簡單XML文檔中的城市列表,
<cities> <city name="milan" country="italy" pop="5"/> <city name="paris" country="france" pop="7"/> <city name="munich" country="germany" pop="4"/> <city name="lyon" country="france" pop="2"/> <city name="venice" country="italy" pop="1"/></cities>
轉換為如下所示的以國家進行分組的HTML表格:
<table> <tr> <th>Country</th> <th>City List</th> <th>Population</th> </tr> <tr> <td>italy</td> <td>milan, venice</td> <td>6</td> </tr> <tr> <td>france</td> <td>paris, lyon</td> <td>9</td> </tr> <tr> <td>germany</td> <td>munich</td> <td>4</td> </tr></table>
這種轉換的痛點在於產生最後三行(如粗體所示)。下面是XSLT1.0的一種解決方案:
<xsl:for-each select="cities/city[not(@country = preceding::*/@country)]"> <tr> <td><xsl:value-of select="@country"/></td> <td> <xsl:for-each select="../city[@country = current()/@country]"> <xsl:value-of select="@name"/> <xsl:if test="position() != last()">, </xsl:if> </xsl:for-each> </td> <td><xsl:value-of select="sum(../city[@country = current()/@country]/@pop)"/></td> </tr> </xsl:for-each>
在上例中,對每個特定的國家,我們首先要用下面的XPath運算式找出它的第一個城市:
cities/city[not(@country = preceding::*/@country)]
然後,對每個分組,為了獲得每個國家的城市名字列表以及該國的總人口,我們需要再回頭引用該組的所有其他成員,這兩種情況我們不得不作一些額外的工作,因為只能用下面的運算式才能引用到當前的分組:
../city[@country = current()/@country]
顯然這不是個理想的局面,因為這種額外的代碼往往是錯誤之源。 使用 xsl:for-each-group, XSLT 2.0 為你的大多數分組問題提供了答案。下面就是XSLT2.0對這個問題的解決辦法(粗體表示新特性):
<xsl:for-each-group select="cities/city" group-by="@country"> <tr> <td><xsl:value-of select="@country"/></td> <td> <xsl:value-of select="current-group()/@name" separator=", "/> </td> <td><xsl:value-of select="sum(current-group()/@pop)"/></td> </tr> </xsl:for-each-group>
在上例中,xsl:for-each-group 作為XPath的取值內容相關的一部分,對"current group"進行初始化,當前的分組是一個簡單序列。一旦我們使用group-by屬性設定好分組,以後就可以使用current-group()函數引用當前的分組。這就完全消除了XSLT1.0方案中的額外開銷。
注意xsl:value-of中的separator屬性。這個屬性的存在是為了告訴處理器不只是要輸出這個序列中的第一個元素的string值(XSLT1.0是如此作的),而是要按順序輸出該序列中所有元素的值。separator屬性的取值為一個可選的字串,用作輸出中每個字串的分隔字元。為了與XSLT1.0相容,如果沒有指定separator屬性,只輸出該序列中的一個成員的值。
最後,根據xsl:for-each-group的三個屬性的取值,可以解決不同的分組問題:group-by(如上面所示),goup-adjacent(用於根據文當中的節點順序的相鄰關係進行分組,例如將inline的<para>元素轉化為塊級(block)的<para>元素),以及group-start-with(根據序列中元素的模式(patterns)分組)。這些方法的例子可以在最新的XSLT2.0工作草案的"13.3 Examples of Grouping"中找到。
使用者定義函數
XSLT2.0引入了一個新特性,就是允許定義他們自己的函數,並可以在Xpath運算式中使用。這是個非常強大的功能,無疑是非常有用的。樣式函數(Stylesheet functions)是用xsl:function元素定義的。這個元素必須指定一個name屬性。它包含0個或多個xsl:param元素,然後是0個或多個xsl:variable元素,後面是唯一的xsl:result元素。這種嚴格的內容模型看起來是個限制,但是你會發現XSLT2.0的真正強大之處就在於可定義xsl:result元素的select屬性。你可能會想到,XPath2.0具備有條件運算式(if...then)和迭代(iterative)(列舉?)運算式(for...return)。
就像下面例子(直接取於最新的工作草案)所示,許多工作是在xsl:result的select屬性中完成的。這個樣式表調用了使用者自訂的一個遞迴函式str:reverse()來輸出字串"MAN BITES DOG"。
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:str="http://user.com/namespace" version="2.0" exclude-result-prefixes="str"><xsl:function name="str:reverse"> <xsl:param name="sentence"/> <xsl:result select="if (contains($sentence, ‘ ‘)) then concat(str:reverse(substring-after($sentence, ‘ ‘)), ‘ ‘, substring-before($sentence, ‘ ‘)) else $sentence"/> </xsl:function><xsl:template match="/"><output> <xsl:value-of select="str:reverse(‘DOG BITES MAN‘)"/></output></xsl:template></xsl:transform>
其他有用的東西
XSLT2.0還有其他有用的新特性,我們在這裡難以祥述。包括一種機制,可定義XPath運算式的預設名稱空間(namespace),可在模式比對判定(match pattern predicates)中使用變數,為排序說明命名(named sort specifications),以非解析文本(unparsed text)的方式讀入外部檔案等等。
此外,XSLT2.0說明書的一大部分仍在制訂中,特別是有關構建和複製W3C XML Schema類型的內容。關於這點,最新的工作草案中說,“這項工作進行中,有關構建元素和屬性的類型資訊關聯,將可能會出現在XSLT2草案的未來版本中。(This is work in progress. Facilities for associating type information with constructed elements and attributes are likely to appear in future drafts of XSLT 2)”。
XSLT 2.0 和 XPath 2.0 的新特性