關於本系列教程
W3C 發布的最新規范 XSLT 2.0 是一種轉換 XML 文檔的語言。它包括很多新的特性,其中一些特性是專為克服 XSLT 1.0 的不足而設計的。這組文章從 XSLT 1.0 用戶的角度(希望解決老問題、學習新技術、發現值得期待的新特性),提供了對 XSLT 2.0 的高水平概述和深入剖析。我們提供了來自常見應用程序的例子,並為那些希望升級的用戶提供了實用的建議。為了幫助您開始使用 XSLT 2.0,本文還提供了相應的遷移技術。
XSLT 處理程序的遵從性
實現 XSLT 2.0 的軟件供應商必須遵循 W3C 發布的規范,但是允許有所不同。和 XSLT 1.0 一樣,很多細節是事先定義的,即每種實現可以選擇怎麼做。XSLT 2.0 也有三個互相獨立的主要模塊,供應商可以選擇實現額外的特性:序列化、模式感知和向後兼容。供應商實現的每個特性模塊必須符合規范,雖然模塊中還有一些地方允許供應商自己選擇。對於序列化特性,2.0 的遵從性要求比 1.0 更清晰,在 1.0 中不僅序列化本身是可選的,而且很多特征也是可選的(表達方式是 應該 而不是 必須)。
2.0 中減少了允許供應商選擇的細節,主要是要求構造結果中出現的某些錯誤必須標記為錯誤(而不允許改變結構得到意外的結果)。仍然有很多實現定義的內容,可以在 XSLT 2.0 規范的附錄中找到完整的 XSLT 選項列表。其中部分實現定義的選項允許供應商支持特定的語言和地區。在描述每種特性模塊的章節之後還可讀到更多相關內容。
需要向後兼容嗎?
向後兼容(BC) 特性的主要目標是允許能夠用於 1.0 處理程序的樣式表也能(或多或少)地用於 2.0 處理程序。符合 2.0 的處理程序不是和 1.0 規范 100% 兼容的,因此和調用舊的 1.0 代碼不同。它仍然是一個 2.0 處理程序!不應該將其看作一個問題,某些情況下也可能是一個機會(允許在兩種方式中選擇一個)。本系列文章旨在介紹跨版本兼容性的各個方面。
您所熟悉的 XSLT 1.0 在設計時就考慮到將來會制定新的版本。最重要的證據是要求所有的樣式表在外層 xsl:stylesheet 元素中使用 version 屬性。所有 XSLT 處理程序包括 1.0 處理程序,都要求支持向前兼容(Forwards Compatibility,FC),就是說如果樣式表的版本號高於處理程序的版本,處理程序應該能夠處理它能識別的部分。如果要讓 FC 在樣式表中生效,處理程序必須對未知的屬性、已知屬性的未知值、XSLT 聲明和指令更加寬松。還必須忽略新的 XSLT 元素而不引起錯誤。但是要注意,少數情況下,1.0 中存在的指令在 2.0 中不是通過屬性擴展的。您可能在現有的樣式表中使用了 FC 以便為 2.0 做准備,不過請堅持閱讀本系列指南。
2.0 處理程序可能僅支持 XSLT 2.0(和 FC),沒有實現 BC 特性。這意味著樣式表中出現的任何 version="1.0" 都會造成錯誤,除非在 xsl:output 中用於設置 XML 的版本。本系列的 第 2 部分 提出了整體切換到 2.0 的一些決策因素,這一部分將進一步分析這些信息。如果支持 BC 特性,處理程序不僅接受頂層 xsl:stylesheet 元素中的 version="1.0",而且允許在下級元素中出現,這種情況下它作用於該元素及其後代。遺留樣式表審查表 中列出了一些方法,限制 BC 的作用范圍可以解決兩個版本行為上的一些差異。
需要模式感知嗎?
模式感知是一些符合 XSLT 2.0 的處理程序支持的另一種可選特性。如果支持該特性,那麼附加的語法項及其在 XSLT 轉換上的作用就是經過定義的和可互操作的。該特性主要用於錯誤檢查。這是一種內建在語言中的工具,可以讓樣式表編寫者驗證臨時(變量)或最終狀態的原子值和節點的模式類型。還可用於從輸入源樹和臨時樹中選擇特定模式類型的節點,在 XML Schema 規范定義的內置原子類型以外創建原子值,用 instance of 運算符確認節點和原子值的類型,以臨時或最終形式創建模式驗證的節點。本系列 第 1 部分 中,表 1 介紹了 10 種可使用模式感知的語法項。
不要認為只有當使用 XML 模式時才需要該特性。在轉換過程中,只有當需要驗證和選擇類型化的節點或者使用用戶定義的模式類型時才需要模式(不論來自外部文件還是內嵌在樣式表中)。在不使用模式的情況下,如果要處理內置的原子類型如 xs:nonNegativeInteger 和 xs:token,仍然需要該特性。在樣式表中遇到這些類型時,非模式感知的處理程序將拋出類型錯誤。如果只需要處理類型化的原子值,而且這些值只需要用更常見的內置模式類型驗證,比如 xs:float 和 xs:boolean,那麼就不需要模式感知特性。關於非模式感知處理程序支持的完整類型列表,請參閱 XSLT 2.0 規范 3.13 節。
需要序列化嗎?
序列化是符合規范的 XSLT 處理程序不一定要支持的一個可選特性,盡管有些人可能感到意外。多數處理程序至少支持序列化特性的一個子集。如果 XSLT 2.0 處理程序聲明說完全支持該特性,則必須實現 xsl:output 和 xsl:character-map 聲明中定義的所有屬性,並且能夠序列化到 XML、HTML、XHtml 和文本輸出文件(或者輸出方法如字節流)。
允許處理程序擴展序列化功能。因此,處理程序可以支持自定義輸出方法、擴展屬性來控制序列化的某些方面,或者支持已有屬性的新值(只要規范允許)。如果想了解基本的序列化需求是否能滿足您的需要,請重新審查已有的樣式表和可能的轉換後處理機制。此外,還要看看 XSLT 2.0 處理程序供應商提供的文檔,了解是否存在能夠代替已有 1.0 擴展機制的序列化擴展,從而簡化向 XSLT 2.0 的遷移。還要看看文檔中關於 XML 1.1、規范化形式(如 NFD)和 @disable-output-escaping 的支持,因為這也是基本序列化特性需求的一部分。不要僅僅因為不支持 @disable-output-escaping 而否定一種處理程序。XSLT 2.0 不贊成使用該特性,用戶可容易地使用對 xsl:character-map 的標准支持來代替它(關於字符映射的更多信息,請參閱本系列的 第 1 部分)。
買方注意:處理程序可能還有其他變化
XSLT 處理程序的區別不僅在於是否提供上述的模塊特性,還可能在更小的附加特性上,處理程序供應商可以自行決定某些方面。許可的變化可能意味著一種實現在報告錯誤方面更嚴格,而另一種實現可能修復(如果允許的話)而不是拋出錯誤。下面詳細介紹可能對遺留樣式表的遷移影響較大的一些事先定義的方面。
仍然需要擴展機制嗎?
很多 XSLT 處理程序公開了接口或者提供這樣的方法,用於在特定應用程序框架中編寫的應用程序中作為一個組件來執行。一些處理程序可能還支持擴展設施(在應用程序框架所用的編程語言中),允許轉換引擎在執行過程中識別和處理擴展函數和指令。比如流行的開放源代碼 XSLT 1.0 處理程序 Xalan-J 支持一種擴展機制,允許在樣式表中實例化 Java 對象和調用 Java 方法。為了將 XPath 表達式的值映射到 Java 方法的參數或者返回值,Xalan-J 提供了 XSLT 類型到 Java 類型的映射。比如,樣式表中變量引用的結果樹片段在 Java 方法被作為 org.w3c.dom.DocumentFragment 對象。如果 1.0 樣式表使用了這類擴展設施,首先要分析擴展代碼執行的功能能否用 XSLT 2.0 和 XPath 2.0 所提供的新特性代替。比如說,如果實現了 F&O不支持的 random() 函數,就要看看 XSLT 2.0 處理程序是否提供了能夠最大限度地減少遷移到該處理程序的工作量的擴展設施。
EXSLT 是一種偽標准,提供了 XSLT 1.0 缺少的功能擴展。很多現有的 1.0 處理程序對 EXSLT 擴展提供了有選擇的支持。雖然很多擴展和 F&O 庫重復或這很容易用 XSLT 2.0 語法重寫,但某些擴展,如 evaluate() 和 script,需要處理程序在標准 XSLT 2.0 之外提供兼容的支持。某些 XSLT 處理程序提供的另一類擴展允許連接到 SQL 數據庫和檢索數據。如果樣式表沒有這些擴展就不能工作,請查看供應商提供的文檔。
需要命名空間軸嗎?
在 XSLT 2.0 中對命名空間軸(即 namespace::nodetest )的支持是可選的。因此,如果 1.0 樣式表大量使用了命名空間軸,而又不想改成調用 in-scope-prefixes() 和 namespace-uri-for-prefix(),就要看看 XSLT 2.0 處理程序上的文檔是否默認(以非 BC 模式運行)支持命名空間軸。如果准備利用 BC,就必須支持命名空間軸。
適應不同的環境
其他變化包括:
支持不同 URI 系統和 URI 中的片段標識符。它影響 xsl:import、xsl:include、doc() 和 unparsed-text() 等。
支持 XML 1.1。對輸入的源文檔沒有影響,但是如果希望序列化到 XML 1.1 文檔或者樣式表是 XML 1.1 文檔,則需要這種支持。
支持各種字符集、字符串的規范化形式和校驗。
和語言有關的數字和日期、校驗以及對不同歷法的支持。
支持的小數精度應超過最低要求(包括時間和時期值)。
trace() 函數表示的信息類型。
處理錯誤和警告的方式,包括 error() 函數做什麼。比如,如果特定結構的執行不可能成功,處理程序可以在執行模板前將類型錯誤作為靜態錯誤拋出。
collection() 函數必須返回一個節點序列,多次調用同一個函數必須返回同樣的結果,但除此以外具體的功能完全由實現定義。
如果上述問題對您來說非常重要,那麼在選擇 2.0 處理程序的時候一定要考慮它們。
啟動轉換的機制
為了在應用程序中啟動 XSLT,多數應用程序開發環境都公開了定制的接口和定制的方法,或者支持 JAXP (Java API for XML Processing) 這樣的標准轉換 API 來執行轉換。撰寫本文的時候,還沒有支持 XSLT 2.0 的標准接口。不過,除非使用標准 API,或者希望指定新的啟動選項來執行轉換,或重定向其他的結果(使用 xsl:result-document 和 @href),否則這不是個大問題。況且,處理程序供應商很可能在選擇的應用程序語言中提供初始化處理的某些支持。新的 XSLT 2.0 啟動選項能夠設置:
初始模式
初始命名模板
初始上下文節點
基准輸出 URI
現有的標准 API 還提供了設置樣式表參數值的方法。如果輸入源是一個節點(比如使用 DOM Source),那麼也可以設置初始上下文節點。需要注意的是,不能將 BC 模式設置為啟動選項,只有樣式表的內容才能觸發該模式。
檢查 XSLT 遺留代碼,確定需要 2.0 處理程序提供什麼功能
XSLT 樣式表仍然類似程序的一種特殊形式,從外部來看 2.0 中大部分都沒有變。但是很多方面有所改進,因為代碼更加清楚直接。(關於這一點的例子請參閱本系列的第 1 部分。)希望下表能夠幫助您了解需要修改的某些方面,還說明了如何修改以及可能影響處理程序選擇的一些允許的差異。也可幫助您從 1.0 遷移到 2.0。
表 1. 可能影響 2.0 處理程序選擇的 XSLT 特性
在 1.0 中使用的特性或者希望用 XSLT 達到的目標 最簡單的 2.0 解決方案 對選擇 2.0 處理程序的影響 使用命名模板執行一般操作,如:全部字母改為大寫
檢查一個長字符串是否以某個短字符串結束
發現集合中不重復的值
將數字四捨五入到某個小數位數(不能是零)
以及其他一般操作。
特別需要注意的是命名模板使用遞歸:
計算一組連續的整數
計算數字的最小值和最大值
按照分隔字符分解字符串
以及其他一般操作。
層層穿過節點集得到一個布爾值。
新增的函數和 XPath 能夠解決大部分這些問題。針對給出的示例,可以使用以下解決方案:upper-case()
ends-with()
distinct-values()
round-half-to-even()
使用 to 運算符的范圍表達式
min() 和max()
tokenize()
限定表達式(XPath 3.9 節)
如果沒有針對您的任務的內置函數,那麼可使用 xsl:function 來定義您自己的函數。
BC 模式允許進行某些自動類型轉換和自動選擇節點集的第一個成員。特別要指出的是,這適用於函數參數,這樣用戶就可使用更多的函數。 假設您希望馬上調用一個命名模板。可以在啟動轉換時傳遞樣式表參數,來確定先調用哪個模板。 2.0 把選擇初始命名模板作為一個新的啟動選項,但是仍然允許傳遞樣式表參數。(要注意:樣式表參數不是作為初始命名模板的參數傳遞的!) 指定初始模板 啟動轉換 的具體方法取決於具體的實現。 在樣式表中大量使用全局變量或模板參數作為通信工具。特別要注意:接收一些參數但是不做處理而傳遞給其他模板的模板。 如果不斷向模板堆棧中傳遞值,就意味著可能需要使用通道參數。如果需要,隱含文檔節點(IDN)可以攜帶整個值結構,減少了傳遞的參數個數。 在 2.0 中傳遞多余的參數(被調用模板中沒有聲明的輸入參數)會導致錯誤,但 BC 模式會排除該錯誤。 當需要模板參數的時候,依賴於不需編寫的內置 match-pattern 模板或使用 xsl:apply-imports 在多個模板處理中運行相同的代碼。 現在可以在 xsl:apply-imports 上使用 xsl:with-param。這些模板參數和以前一樣沒有被拋棄。最好使用明確定義的模板而不要依賴於內置模板。可以通過多個模板(包括內置模板)傳遞通道參數,而不會丟失參數。 無。 在指令序列中使用僅引用一次的局部變量。需要設置變量是因為(可能在命名模板中)使用了一系列“編程”指令來設置它的值。或者設置局部變量然後使用 xsl:for-each 來為內部計算改變視點。 IfExpr、RangeExpr、ForExpr 等新增加的函數大大減少了通過編程指令計算值的需要。ForExpr(XPath 3.7 節)解決了視點改變的問題。 BC 模式允許進行某些自動類型轉換和自動選擇節點集的第一個成員。具體來說,可用於函數參數,而且有更多的函數可用。2.0 中的數據類型更加正規,可能要求進行分析,模式的使用增加了仔細檢查的必要性。
使用 node-set() 擴展函數使結果樹片段(RTF)能夠導航和篩選。 直接使用 RTF,不用管隱含文檔節點(IDN)。關於 IDN 的更多信息,請參閱 第 1 部分。 無。 使用擴展函數(節點集除外)。通過擴展函數或命名模板來模擬數據類型(除了 XPath 1.0 類型以外,如 dateTime)。日期和時間值通常作為字符串處理,但如果需要加上時期值則作為數字處理。 新的函數和 XPath 能力滿足了很多需要。關於新的日期/時間/時期功能,請參閱本系列的 第 1 部分。
BC 模式也允許某些自動類型轉換。特別適用於函數參數。模式感知可以幫助管理自定義的數據類型。
使用極大或極小的數字或者模仿從數值類型派生的私有數據類型。 使用顯示的數據類型控制和新函數可以避免陷入困境。注意這些數字用科學計數法顯示的情況。 檢查供應商的文檔看看事先定義的精度是否達到了最低要求。BC 模式對數字比較的影響有兩個方面:允許選擇集合的第一個成員,對於非數字值返回 NaN 而不是拋出類型錯誤。詳情參閱 XPath 3.4 節。
2.0 中的數據類型更加正規,可能要求進行分析,模式的使用增加了仔細檢查的必要性。
小心處理字符串以便支持特定語言/國家的需要,尤其是對排序和顯示而言。 使用改進的 2.0 功能,避免使用 disable-output-escaping 屬性。format-date() 函數
format-time() 函數
format-dateTime() 函數
xsl:number 中的格式
xsl:sort 中可以進行更好的控制
default-collation 屬性
檢查供應商文檔對實現定義的地區支持和排列支持。有些供應商允許自己編寫排序器。應該仔細檢查序列化,特別是對編碼的支持。
很多希望接收一個數字或者原子字符串的函數和運算符依賴於 1.0 的行為,如果傳遞的參數是一個節點集,就會自動選擇節點集的第一個成員並提取其原子值。例外包括 =、!=、<、<=、>、>=、id()、 boolean()、 和 not()。
如果允許選擇多個節點,可使用 xsl:value-of @select="some node"。
如果不能使用 BC 模式,對於返回多個節點的節點集表達式可增加謂詞 [1]。第 1 部分曾經提到,xsl:value-of(2.0 中)將自動迭代值的序列,包括節點集。
如果處理程序支持 BC 模式,使用局部版本化啟用 version="1.0"。(和尋找需要 [1]的所有地方相比,這樣更簡單。)BC 模式影響 xsl:value-of 的迭代功能。 當匹配模式或者進行值比較時,需要檢查數據類型。比如,要保證比較的兩個值是數字而不是字符串。 使用 instance of 運算符。閱讀 XPath 2.0 規范的 3.5 節了解 eq 關系的嚴格用法。
新的匹配模式可能造成模板具有相近的默認優先級。如果這樣的話就明確地設置優先級。
使用 element() 和 attribute() 模式的雙參數形式。
BC 模式對數字比較的影響有兩個方面:允許選擇集合的第一個成員,對於非數字值返回 NaN 而不是拋出類型錯誤。詳情參閱 XPath 3.4 節。模式感知允許 instance of 根據模式中定義的所有類型確認節點的類型。也允許 schema-element() 和 schema-attribute() 節點測試。模式感知影響的語法項請參閱 第 1 部分 中的表 1。
可以讓選擇排列生效,影響關系運算符 eq、le 等。
把包含原子值(數字、布爾值或字符串)的變量和另一個此類變量或者文字原子值比較,希望一種類型自動轉換成另一種類型。以及向面向字符串的函數如 substring()、contains() 傳遞非字符參數等。例子:
contains($MyNumber, '0')
從輸入文檔中取回的值都轉化成 xs:untypedAtomic,從而能夠進行比較或者傳遞給函數,但是如果兩個操作數都標記了明確且不同的類型,比較的時候就會造成類型錯誤。將一個操作數包裝到 number()、boolean()、string() 或者其他新增的類型構造器函數中。如果函數需要字符串,則用 string() 包裝參數。
如果支持 BC 特性,可以設置 version 1.0 讓它在表達式求值的元素中生效,這樣就不用擔心類型匹配問題了。模式感知允許更具體的類型轉換。如果使用模式感知而且輸入文檔通過了模式驗證,標記為 2.0 新增類型(xs:date、xs:duration、xs:hexBinary 等)或者從這些基本類型派生的私有類型就不能自動轉換成字符串。可以手工轉換或者(最好)編寫新的使用這些類型的表達式。
查找第一個分組鍵並作為組成分組的觸發器來對元素分組。常用的形式如下:generate-id(key(...))
preceding-sibling axis
xsl:for-each select="key(...)"
使用 xsl:for each-group(可能代替 xsl:for-each),請參閱本系列 第 1 部分 的說明。 無。 直接操作源文檔中的命名空間節點並希望控制輸出中的命名空間聲明。通過使用命名空間軸或者復制元素來得到其命名空間來訪問這樣的節點。在輸出端,可以創建一個屬性以它的名義添加命名空間聲明。注意:2.0 處理程序可以發現語法無效的 Namespace URI。
使用 xsl:namespace 創建命名空間節點。了解處理命名空間、QNames 和 URI 的新函數,特別是本文命名空間軸一節提出的兩條建議。不通過命名空間軸,使用新的函數應該也能檢索需要的信息。
如果序列化 XML,一定要注意命名空間修正。需要新的 URI 來代替那些無效的 URI。
查看供應商文檔了解 2.0 處理程序是否支持命名空間軸,因為在 2.0 中是可選的特性。支持 BC 要求支持命名空間軸。如果處理程序能夠創建 XML 1.1,可以取消帶前綴的命名空間聲明。
使用導入或包含樣式表,尤其是如果確實需要條件包含的話。 問問自己:為何要劃分模塊?其中一些是工具模板嗎?要獲得關於模板使用條件的信息(無論是否要導入/包含),請查看 use-when。 如果使用 http 之外的 URI 方案,請查看供應商文檔中提取下層樣式表的方法。BC 允許混合使用 1.0 和 2.0 模塊。比如,2.0 主樣式表可以包含 1.0 樣式表。
XSLT 2.0 處理程序是全新的
即使您是無意中使用了 2.0 的行為,2.0 處理程序仍將按照 2.0 規范的要求操作。2.0 文檔沒有按通常的方式結合 1.0;全部內容都從零開始重新定義,包括向後兼容行為。支持 BC 特性的 2.0 處理程序將在 BC 模式下運行 1.0 樣式表,但並沒有忽略 xsl:for-each-group 這樣的新指令。閱讀本系列的其他部分、2.0 規范和其他資料的時候要記住這一點。
升級到 2.0 將使遞歸的使用更少、RTF 更少、隨意的參數傳遞更少,以及 1.0 中其他類似的不愉快更少。