XML 檔案:使用 JScript、C# 和 Visual Basic.NET 擴充 XSLT

來源:互聯網
上載者:User

XSL 轉換 (XSLT) 因其可使困難的事情變容易,以及使容易的事情變困難而廣為人知。它可以簡化用其他方式很難實現的複雜轉換邏輯。但與此同時,XSLT 的函數編程模型有時會使執行小型商務邏輯變得極其困難。通常,使用傳統語言(如 VBScript、JScript 或 Microsoft .NET 支援的任何語言)擴充 XSLT 可提供這兩個方面的最佳功能(有關 XSLT 的簡介,請參閱 2000 年 8 月刊中我與 Don Box 和 John Lam 合著的文章)。

例如,請考慮以下 XML 文檔,其中包含一系列要執行的操作:

<equation><add>3</add><sub>1</sub><mul>6</mul><add>8</add><div>4</div></equation>

Let's say that the goal is to evaluate the list of operations, top to bottom, assuming no operator precedence and an initial value of 0. In this case, it's like evaluating the following equation:

((((3 - 1) * 6) + 8) / 4) = 5

此問題可通過多種方法來解決。根據您的背景、過去處理此問題的方法甚至通常使用的語言,您可能會發現某些解決方案非常直觀,而另一些則相當晦澀。

JScript 解決方案

您可能已經在腦海裡想出了如何結合使用命令性程式設計語言(如 Visual Basic、C++ 或 JScript)和 DOM 來解決此問題。使用允許您聲明和更新變數的命令性語言可以大大簡化任務。圖 1 中的 JScript 函數可提取表示 equation 元素子級的節點列表,並計算結果。

對許多人來說,此代碼非常直觀,這是因為 JScript 允許您聲明其值可在每個迴圈迭代中更新的結果變數。此函數可按如下方式進行調用:

var doc = new ActiveXObject("MSXML2.DOMDocument.4.0");doc.Load("numbers.xml");var result = processEquation(doc.selectNodes("/equation/*"));

XSLT 解決方案

現在,假設您使用的是允許聲明變數的語言,但是當您為它們賦值後,將無法修改這些值。換句話說,它們實際上不是變數,而是在運行時指定的常量。函數程式設計語言是一類共用此特性的程式設計語言。如 Scheme、ML、Haskell、XSLT 及其他幾種語言就屬於此類別。

因為函數語言是模組化的且十分靈活,所以它們常用於解決極其複雜的問題(AI 就是這樣一個樣本)。除此之外,函數語言實現可以一種目前採用的主流命令性語言無法使用的方式進行最佳化。但是,所有這些都是需要一定代價的,因為對於那些不習慣使用函數模型的人來說,學習過程是比較困難的。

例如,以圖 2 中的 XSLT 程式為例,它使用另一種演算法來提供相同的功能。processEquation 模板可產生與圖 1 中所示的 JScript 函數相同的結果。此樣本不同於 JScript 版本,因為它不使用 for 迴圈,並且在處理每個運算之後不更新變數。相反,它使用遞迴來處理列表,並在每次運算之後依靠呼叫堆疊來跟蹤增量結果。

XSLT + JScript 解決方案

如果您能理解圖 2 中的代碼,並感覺它與圖 1 中的代碼同樣簡單或更簡單,則您可能不需要閱讀本專欄的其餘部分。但是,如果您與大多數開發人員一樣,就會認為它不是最直觀的編程模型,特別是對於這種簡單任務的類型。任何人都不會反對使用 XSLT 來實現複雜的 XML 轉換邏輯,但是當這些轉換需要某些類型的商務邏輯時,這些實現相對於其價值來說可能會過於複雜。

在這些情況下,結合使用 XSLT(用於基於樹的轉換邏輯)和另一種程式設計語言(用於某個商務邏輯)可提供這兩個方面的最佳功能。XSLT 1.0 規範綜合了使用非 XSLT 代碼擴充 XSLT 程式的慣例,這些非 XSLT 代碼可以用給定 XSLT 處理器支援的任何程式設計語言來編寫。

圖 3 中的 XSLT 程式顯示如何將圖 1 中的 JScript 函數添加到圖 2 中的 XSLT 程式,以替換 processEquation 模板。這種方法不僅可以簡化 XSLT 代碼,而且在許多情況下,還能夠實現其他方法無法實現的某種功能。例如,如果您需要執行 XSLT 語言本身不支援的某項數學運算,則必須使用自訂擴充(我將在後面介紹這樣的樣本)。如果您使用 XSLT 來執行複雜的轉換,則通常會遇到需要自訂擴充的情況。

這種方法的缺點是,擴充後的功能將只適用於支援所用語言的處理器。如果您的 XSLT 不需要處理器之間的可移植性,則不必關心這個問題。如果它們需要處理器之間的可移植性,則您必須克服困難並確定如何用一般的 XSLT 代碼來實現該功能,或者,如果這不可行,則必須為需要支援的每個處理器提供其他實現方法。

.NET 和 MSXML XSLT 處理器均提供一個簡單機制,用於添加以其他語言編寫的代碼,這可以在 XSLT 文檔中直接實現,或通過連結到現有的 COM 物件或 .NET 程式集來實現。在本專欄的其餘部分中,我將討論用 Microsoft 實現來擴充 XSLT 程式的細節。

XSLT 1.0 擴充機制

XSLT 1.0 規範定義了兩種類型的擴充:擴充元素和擴充函數。這兩種類型的擴充均在標準語言的基礎上提供了附加功能,並可以像其他任何 XSLT 1.0 元素(包括 xsl:transform、xsl:template、xsl:value-of 等)或 XPath 1.0/XSLT 1.0 函數(如 string、substring-before、sum、document 等)那樣使用。

由於 XSLT 1.0 具有基於模板的模型,因此 XSLT 處理器需要用其他額外的資訊來正確地區分靜態內容元素和代表其他附加行為的擴充元素。例如,請考慮以下 XSLT 轉換:

<xsl:transform version="1.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform"xmlns:out="http://www.w3.org/1999/xhtml"xmlns:ext="http://example.org/extension"><xsl:template match="/"><out:html><ext:doSomeFunkyMagic/>•••</out:html></xsl:template></xsl:transform>

在此例中,處理器將與 http://www.w3.org/1999/XSL/Transform 命名空間關聯的元素(transform 和 template)識別為語言特定的指令,將其他元素(html 和 doSomeFunkyMagic)識別為執行個體化模板時應輸出的內容。

現在,假設 doSomeFunkyMagic 元素被某類處理器指定為擴充元素。為了讓處理器能夠找出此元素,它需要知道哪個命名空間包含正在使用的擴充元素。這可通過可在 stylesheet/transform 元素上使用的 extension-element-prefixes 屬性來完成,如下所示:

<xsl:transform version="1.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform"xmlns:out="http://www.w3.org/1999/xhtml"xmlns:ext="http://example.org/extension"extension-element-prefixes="ext"><xsl:template match="/"><out:html><ext:doSomeFunkyMagic/></out:html></xsl:template></xsl:transform>

現在,當處理器執行轉換時,它可以確定 doSomeFunkyMagic 代表擴充元素,而不僅僅是一般的輸出。

這不是擴充函數的問題,因為,正如我剛才闡釋的那樣,它們不會與輸出內容相混淆。這意味著,如果您僅使用擴充函數,則無需使用 extension-element-prefixes 元素,但仍需要用擴充命名空間來限定函數名:

<xsl:transform version="1.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform"xmlns:out="http://www.w3.org/1999/xhtml"xmlns:ext="http://example.org/extension"><xsl:template match="/"><out:html><xsl:value-of select="ext:doSomeFunkyMagic()"/></out:html></xsl:template></xsl:transform>

所有的內建 XPath 1.0 和 XSLT 1.0 函數名均未限定,這意味著它們始終被序列化為 NCName 並假設不來自任何命名空間。因此,如果處理器遇到帶首碼的函數名,會自動假設它是擴充函數。

XSLT 1.0 規範還提供一個允許轉換向處理器查詢擴充支援的機制。特別是,它定義了元素可用和函數可用的函數,以便在嘗試使用執行處理器之前確定它是否確實支援擴充。將這些函數與 if 或 choose 元素結合使用,可以編寫能利用某些擴充的 XSLT 轉換,而又不會完全犧牲可移植性, 4 所示。

XSLT 1.0 還提供一個 fallback 元素,並將它用作提供備份功能的較明確替換方法。將後備功能放在 fallback 元素內部之後,您便可以在可能不受支援的任何擴充元素內部使用該功能。圖 5 顯示被移植以使用 fallback 元素的上一個樣本。

儘管規範為使用處理器特定的擴充提供了支援,但並未定義實現它們的標準機制。此機制因處理器而異,但現代的大多數實現目前都提供這種支援(未來的 XSLT 規範中可能會提供一個標準方法)。讓我們看一下此機制在 Microsoft MSXML 4.0 和 .NET XSLT 實現中是如何工作的。

msxsl:script

當前的 Microsoft XSLT 處理器可以直接在 XSLT 文檔中或在帶外擴充項物件內部實現擴充。本專欄的簡介中已經包含了第一種方法的一些樣本。正如樣本所闡釋的那樣,您可以通過 Microsoft urn:schemas-microsoft-com:xslt 命名空間的 script 元素(從現在開始,我將稱之為 msxsl:script 元素),將擴充代碼嵌入 XSLT 文檔中。msxsl:script 元素本身就是一個擴充元素,因此您可以像上一節中討論的那樣測試它是否受到支援。

下面列出在 MSXML 4.0 和 .NET 中,您對 msxsl:script 所必須使用的文法:

<msxsl:scriptlanguage = "language-name"implements-prefix = "prefix of user's namespace"></msxsl:script>

language 屬性指定在指令碼標記中使用何種語言,而 implements-prefix 屬性控制所包含函數位於哪個命名空間。您必須使用限定名在 XPath 運算式中調用這些使用者定義的函數中的一個。

在 MSXML 和 .NET 實現中,可以使用的語言會有所不同。在 MSXML(從 2.0 版開始)中,您可以使用 VBScript 或 JavaScript。但是,在 .NET 中,您可以像從前那樣使用 .NET 支援的任何語言,包括 C#、Visual Basic、JScript.NET 甚至 JScript。使用強型別語言(如 C# 和 Visual Basic .NET)的能力使之更加具有吸引力。

請回過去看一 3,該圖顯示了 msxsl:script 與 JScript 的結合使用。請注意,processEquation 與 urn:the-xml-files:xslt 命名空間相關聯,urn:the-xml-files:xslt 命名空間綁定到 transform 元素的命名空間聲明中的 usr 首碼。無論何時使用函數,其名稱都必須帶有首碼 usr。在那個樣本中,函數是在 value-of select 運算式中被調用的,如下所示:

<xsl:value-of select="usr:processEquation(/equation/*)"/>

由於這兩種 Microsoft XSLT 實現都支援 JScript,因此圖 3 中所示的文檔可用於 MSXML 或 .NET 版本。

使用這兩種 Microsoft 實現,您可以通過在單個 XSLT 文檔中使用多個 msxsl:script 元素,以不同的語言來編寫擴充函數(有關樣本,請參見圖 6)。此操作唯一的問題是,每個 msxsl:script 元素都必須在 implements-prefix 屬性中指定一個不同的命名空間。您不能在多個 msxsl:script 元素中為同一個命名空間編寫擴充代碼。在這兩種實現中,有關 msxsl:script 的其餘細節會有所不同。

在 XSLT 和 MSXML 之間映射類型

圖 6 中的代碼顯示沿兩個方向傳遞的資訊。對象作為參數傳遞到函數中,並作為傳回值從每個函數中返回。您必須瞭解 XPath 類型系統如何映射到 JScript 和 VBScript 類型系統,以便正確地設計自己的擴充函數。

圖 7 中的表格描述每個 XPath 類型在用於函數參數時,在 JScript 和 VBScript 中映射的類型。前三個類型是非常直接的。XPath 中的字串、數字和布爾值直接映射到 JScript 和 VBScriptin 中的字串、數字和布爾值。作為一個樣本,圖 6 中使用的函數將 JScript 和 VBScript 函數中的字串返回到 XSLT value-of 調用運算式。

最後兩種類型將需要更多的解釋。XPath-節點集會映射到實現 DOM NodeList 介面 (IXMLDOMNodeList) 的 JScript 和 VBScript 對象。IXMLDOMNodeList 在本質上只是一般 DOM 節點的集合介面,它支援基本的遍曆和長度查詢。

讓我們看一個如何在擴充函數中使用 IXMLDOMNodeList 的樣本。假設您需要在以下 XML 文檔中計算兩點之間的距離:

<line><point><x>10</x><y>10</y></point><point><x>20</x><y>20</y></point></line>

這無法通過標準的 XSLT 來實現,但是藉助於擴充函數和更進階的數學庫,此任務會變得易如反掌。圖 8 中顯示的擴充函數會得到一個代表兩點集合的 IXMLDOMNodeList 對象。它可使用內建的 JScript Math 對象來計算兩點之間的距離:

然後,可以按如下方式調用此擴充函數:

distance: <xsl:value-ofselect="math:CalcDistance(/line/point)"/>

XSLT 結果樹片段也會映射到 IXMLDOMNodeList 對象,但是它們只包含 NODE_DOCUMENT_FRAGMENT 類型的一個節點。換句話說,您必須定位到 IXMLDOMDocumentFragment 對象的子級才能執行實際的處理。圖 9 中的 XSLT 闡釋如何處理傳遞到函數的結果樹片段。

這個版本的擴充函數會收到一個結果樹片段。結果樹片段是通過 XSLT 變數或 param 元素產生的。以下 XSLT 片段闡釋了如何產生結果樹片段,以及如何調用此新版本的擴充函數:

•••<xsl:variable name="points"><point><x>10</x><y>10</y></point><point><x>20</x><y>20</y></point></xsl:variable><xsl:value-of select="math:CalcDistance($points)"/>•••

列在圖 7 中列出的類型是可用在函數參數中的唯一類型。由於 JScript 和 VBScript 不是強型別的,您必須明確知道在調用函數時傳遞的物件類型。請考慮以下擴充函數:

<xsl:value-ofselect="usr:doSomething(string(./author/name),number(./price), .)"/>

如果函數將分別得到 string、number 和 IXMLDOMNodeList 類型的參數,您必須在調用方法時明確強制它們,如下所示:

<xsl:value-ofselect="usr:doSomething(string(./author/name),number(./price), .)"/>

在本例中,Xpath 的 string 和 number 函數用於將前兩個節點集明確強製為預期類型。最後一個參數不需要強制,因為節點集會自動對應到 IXMLDOMNodeList 對象。

對於 MSXML 中的函數傳回值會有更多的限制。從函數返回的類型只能是字串和數字(請參見圖 10)。如果返回的是數字,它會被強製為 JScript 和 VBScript Number。如果返回的是其他基元類型,它會被強製為 JScript/VBScript String。嘗試返回對象會引發異常。

例如,由於以下擴充函數返回的是數字,所以是可接受的。

<ms:script language="VBScript" implements-prefix="vb"><![CDATA[function AuthorsByState(state)Set doc = CreateObject("MSXML2.DOMDocument.4.0")doc.Load "authors.xml"// returns the number of authors for given stateAuthorsByState = doc.selectNodes("//author").lengthend function]]></ms:script>

但是,以下稍作修改的版本嘗試向調用方返回一個 IXMLDOMNodeList 對象,因此會引發異常:

<ms:script language="VBScript" implements-prefix="vb"><![CDATA[function AuthorsByState(state)Set doc = CreateObject("MSXML2.DOMDocument.4.0")doc.Load "authors.xml"// returns the authors collectionFindAuthorsByState = doc.selectNodes("//author")end function]]></ms:script>

您可以在擴充函數內部執行個體化和調用對象,但不能將它們返回給調用方。

在 XSLT 和 .NET 之間映射類型

構建 .NET 擴充函數的機制類似我剛才討論的 MSXML 的機制。但是,.NET 實現由於以下幾個原因而比 MSXML 更進階。首先,您有更多的語言可以選擇,包括強型別語言,如 C# 和 Visual Basic .NET。其次,擴充函數可以是強型別函數,這可讓處理器在調用函數時代替您處理某些強制。最後,對象(string 和 number 除外)可用作傳回值。

圖 11 描述了 XPath 和 .NET 類型系統之間的映射。此映射應用於兩個方向:函數參數和傳回值。前三個類型映射到 .NET System.String、System.Boolean 和 System.Double 類型,而節點集和結果樹片段分別映射到 XPathNavigator 和 XPathNodeIterator(有關這些類的詳細資料,請參閱 2001 年 9 月的 XML Files 專欄。Int16、UInt16、Int32、UInt32、Int64、UInt64、Single 和 Decimal 類型會自動被強製為 Double,從而映射到 W3C XPath number 類型。如果在函數參數或傳回值中使用任何其他類型,會引發異常。這些類型映射也可應用於 XSLT 全域參數。

請考慮以下版本的 CalcDistance 擴充函數,該函數是在 C# 中實現的:

<ms:script language="C#" implements-prefix="math"><![CDATA[double CalcDistance(XPathNodeIterator points) {points.MoveNext();double xdelta =(double)points.Current.Evaluate("x - following::x");double ydelta =(double)points.Current.Evaluate("y - following::y");return Math.Sqrt(Math.Pow(xdelta, 2) +Math.Pow(ydelta, 2));}]]></ms:script>

請注意,此函數可接受 XPathNodeIterator 參數並返回一個雙精確度值。此函數可以像在 JScript 中實現的上一個函數那樣進行調用:

<xsl:value-of select="math:CalcDistance(/line/point)"/>

此樣本還利用了 System.Math 類。但是,請注意,代碼中未包含任何使用語句的 C#。.NET 實現會使圖 12 中列出的命名空間在 msxsl:script 擴充中自動可用。目前,您只能使用 msxsl:script 擴充中的命名空間類型。如果您需要使用其他類型,則必須依賴 XSLT 擴充項物件(我將在以後小節中討論它們)。

可重用性

在 XSLT 文檔中直接嵌入擴充代碼很方便,但是如果設計不當,會限制可重用性。要重用 msxsl:script 中定義的自訂函數,一種方法就是通過 XSLT 的 include 元素。您可以在 XSLT 文檔中建立只包含擴充函數的全域庫。例如,圖 13 中的 XSLT 文檔只包含一個 msxsl:script 元素,而該元素本身包含了幾個函數。

假設此檔案的名稱為 global-extensions.xsl,下例顯示如何將此檔案包括在另一個 XSLT 文檔中:

<xsl:transform version="1.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:include href="global-extensions.xsl"/>•••</xsl:transform>

這很像使用 ASP include 元素來重用嵌入到 ASP 頁面中的指令碼。儘管此方法可行,但實現可重用性的首選方法是 XSLT 擴充項物件。

XSLT 擴充項物件

MSXML 和 .NET 均支援在編譯的組件中實現 XSLT 擴充函數。使用 MSXML 可以調用編譯的 COM 組件,而使用 .NET 可以調用編譯的程式集。具體細節視您希望使用的實現方式而異。

在 MSXML 實現中,COM 擴充項物件支援設定和查詢屬性以及調用函數。與 msxsl:script 擴充一樣,函數和屬性中使用的類型必須遵循前面在圖 7 和圖 10 中描述的映射規則。

要在 XSLT 文檔中使用擴充項物件,您需要在執行轉換之前將它添加到處理器的上下文中。您可以在 MSXML 中通過 IXSLProcessor 介面公開的 addObject 方法來實現此目的。其文法如下:

objXSLProcessor.addObject(obj, namespaceURI);

addObject 將得到一個與對象關聯的命名空間標識符。這等同於使用 msxsl:script 元素的 implements-prefix 屬性。將對象添加到處理器的上下文並與所提供的命名空間關聯之後,即可以從 XPath 運算式調用它( 13 中所示)。

在 XSLT 中,您應通過在屬性名稱前面加上後跟括弧的 get- 或 put- 來訪問屬性。例如:

get-age(), put-age(33))

讓我們來看一個定義和使用擴充項物件的完整樣本。讓我們假設以下 Visual Basic 類定義:

' Class: PersonPublic name As StringPublic age As DoublePublic Function SayHello(ByVal other as String) As StringSayHello = "Hello " & other & ", I'm " & nameEnd Function

要將一個 Person 對象用作 XSLT 擴充,您必須在執行轉換之前將它添加到處理器的上下文中。圖 14 闡釋了如何使用 MSXML 4.0 和 Visual Basic 來執行此操作。由於在調用 Transform 之前,已經將 p 引用的對象添加到處理器的上下文中,因此只要 Person 的屬性名稱和函數名稱由 urn:person 命名空間限定,XSLT 文檔就可以訪問 Person 的屬性和函數,如下所示:

<xsl:transform version="1.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform"xmlns:obj="urn:person"><xsl:output method="text"/><xsl:template match="/">name: <xsl:value-of select="obj:get-name()"/>age:  <xsl:value-of select="obj:get-age()"/>greeting: <xsl:value-of select="obj:SayHello('Michi')"/></xsl:template></xsl:transform>

.NET 實現中的模型幾乎相同,除了現在需要調用 .NET 對象之外。主要區別在於如何將對象與處理器的上下文相關聯。您可以通過 XsltArgumentList 類來完成,如下所示:

XslTransform xslt = new XslTransform();xslt.Load(stylesheet);XPathDocument doc = new XPathDocument(filename);XsltArgumentList xslArg = new XsltArgumentList();//Add a Person objectPerson obj = new Person();obj.name = "Michi";obj.age = 6;xslArg.AddExtensionObject("urn:person", obj);xslt.Transform(doc, xslArg, Console.Out);

這個用於調用該 .NET 擴充項物件的 XSLT 文檔看上去與上一個 MSXML 樣本中的文檔相同。

小結

XSLT 是功能強大的函數程式設計語言,它可以使困難的任務變容易,也會使容易的任務變困難。在某些情況下,結合使用 XSLT 的函數編程模型和以命令性語言(如 JScript、C# 或 Visual Basic .NET)編寫的部分自訂代碼,可以簡化整個 XSLT 文檔。Microsoft 在 MSXML 和 .NET 中的 XSLT 實現均為建立這種類型的混合 XSLT 應用程式提供了優異的支援。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.