基于Xml的可擴(kuò)展樣式表語言Xsl及其轉(zhuǎn)換Xslt實現(xiàn)了將Xml源文件從一種格式到另一種格式的轉(zhuǎn)換。通過在xsl文件中定義一系列遵守規(guī)則的模板,匹配這些模板的Xml文檔部分就能夠被轉(zhuǎn)換為可顯示的Html頁面文件、符合其他需要的Xml文件或除此之外其他結(jié)構(gòu)的文件。當(dāng)在Xslt文檔中這樣做時,沒有明顯的流程控制,也沒有傳統(tǒng)編程意義上的算法,甚至不需要任何程序代碼和編程語言。絕大多數(shù)時候,這能夠很好地完成任務(wù)。
任何一門語言都不可能絕對完善,而Xsl畢竟只是一門標(biāo)記語言,單純依靠它并不能完成所有任務(wù)。如同沒有XPath、Xslt時,Xml充其量只能是一些標(biāo)記的集合,什么也不能做一樣。尤其,如果我們所需求的不僅僅是改變文檔的顯示方式或結(jié)構(gòu),我們還需要對文檔進(jìn)行更多的控制時,單純使用Xslt的局限性就暴露出來了。比如,獲取用戶鍵入的參數(shù)以控制轉(zhuǎn)換輸出、連接外部數(shù)據(jù)源以及引用某種計算的結(jié)果等等,這些情況下,單純使用Xslt這樣的描述語言無法提供滿意的解決方案。而我們知道,程序代碼會很輕易地解決這些問題。
使用嵌入式腳本擴(kuò)展Xslt樣式表功能
Xml是易于擴(kuò)展的,這也正是其短時間內(nèi)能被廣泛接受的原因之一。而Xslt完全基于xml實現(xiàn),它應(yīng)該也是易于擴(kuò)展的。事實的確如此,W3C推薦標(biāo)準(zhǔn)的Xslt提供了針對不同處理器/解析器的專門的擴(kuò)展?;旧?,不同處理器/解析器可以在Xpath表達(dá)式、Xslt模板主體內(nèi)容或頂級元素中使用擴(kuò)展函數(shù)。而且,同任何程序語言的函數(shù)/方法一樣,這些擴(kuò)展函數(shù)可以用來完成所要求的子功能,它們也能夠“一次定義,多次調(diào)用”。更重要地,這些嵌入腳本函數(shù)不僅能在xslt標(biāo)記語言中調(diào)用,還能夠運用在高級語言中。腳本語言所提供的組功能比純Xslt所提供的功能更豐富,經(jīng)??梢杂脕韺ξ臋n內(nèi)容進(jìn)行更復(fù)雜的操作(例如對其應(yīng)用科學(xué)計算或訪問外部信息源等),這個特性大大擴(kuò)展了xslt的功能。
實現(xiàn)時,需要在一個指定的命名空間中限定所創(chuàng)建的擴(kuò)展函數(shù),并且在特定的Xslt處理器中提供實現(xiàn)。之所以必須使用命名空間,是因為命名空間不僅可以防止不同來源的Xslt名稱間的沖突。更重要的是它還將擴(kuò)展予以標(biāo)識,這樣其他的處理器就可以忽略這個專門針對特定處理器的實現(xiàn),并依靠這個命名空間在Xslt其他部分引用擴(kuò)展函數(shù)。
MSXML XSLT處理器使用<msxsl:script>元素及其屬性implements-prefix實現(xiàn)并擴(kuò)展函數(shù)以提供腳本級支持。微軟特別指定的命名空間允許MSXML XSLT處理器在樣式表內(nèi)定位腳本代碼。<msxsl:script>元素的language屬性告訴運行期腳本使用哪個解釋程序,而implements-prefix屬性值是擴(kuò)展函數(shù)的前綴,這個前綴xslt文檔的其他地方被聲明。定義這些屬性之后,Xslt處理器就能夠使用這些信息調(diào)用解釋器運行時腳本(如C#編譯器)并在<msxsl:script>元素中實現(xiàn)擴(kuò)展函數(shù)的代碼編寫。在樣式表的其他地方,依靠<xsl:value-of>元素提供輸出的模板通過seletct屬性與擴(kuò)展函數(shù)相關(guān)聯(lián),比如,執(zhí)行并輸出擴(kuò)展函數(shù)的計算結(jié)果。
<msxsl:script>元素定義如下:
<msxsl:script language="language-name" implements-prefix="prefix of user‘s namespace"></msxsl:script>
其中:
language屬性表示語言選項,它與html頁面上script元素的language屬性極為類似,其值提供函數(shù)/方法定義所用的腳本語言。不過,language屬性不是必須的,但如果指定,則它的值必須是下列語言之一:C#、VB、JScript、JavaScript、VisualBasic或CSharp。如果未指定,則默認(rèn)語言為JScript。與其他Xslt元素及屬性嚴(yán)格區(qū)分大小寫不同的是,這里的語言名稱不區(qū)分大小寫,因此,"JavaScript"和"javascript"是等效的。
implements-prefix屬性是強制的。用于指定命名空間前綴并將其與腳本塊關(guān)聯(lián)。屬性的值表示命名空間前綴。前綴所代表的命名空間必須在樣式表中的某個位置定義。
同其他任何Xml元素標(biāo)記一樣,<msxsl:script>元素中的"msxsl"表示命名空間前綴。不過,名字"msxsl"并不重要,也就是說,并不一定非是"msxsl"不可,你一樣可以命名為其他的字符串,比如:
xmlns:myxsl="urn:schemas-microsoft-com:xslt" xmlns:myns="urn:schemas-microsoft-com:xslt"
這樣,命名空間前綴變?yōu)榱?myxsl"或者"myns",相應(yīng)地,元素<msxsl:script>就將改為<myxsl:script>或<myns:script>。這種改變不會對結(jié)果有什么影響,如果你在你的xslt文檔所有部分保持一致的話。(在本文中,所有出現(xiàn)<script>元素的地方使用了msxsl,是為了與微軟默認(rèn)設(shè)置保持一致)
不知你是否已經(jīng)注意到,上面的語句中,不管命名空間前綴怎樣改變,其值卻始終是urn:schemas-microsoft-com:xslt。這是必須的。要正確擴(kuò)展xslt樣式表功能,就必須指定這個命名空間。命名空間urn:schemas-microsoft-com:xslt符合W3C推薦標(biāo)準(zhǔn),并在微軟MSXML3.0以上處理器中提供支持。使用它不僅能在xslt中進(jìn)行嵌入腳本編程,還能對其他擴(kuò)展功能提供支持。比如,在xsl中使用node-set()函數(shù)。
通常,在<xsl:stylesheet>元素內(nèi)定義<msxsl:script>元素,然后在<msxsl:script>元素內(nèi)定義擴(kuò)展函數(shù),還可以在<msxsl:script>中實例化COM對象,這將大大擴(kuò)展純Xslt的功能。(不過,用戶的安全設(shè)置或許會阻止你的腳本實例化客戶端對象)
擴(kuò)展函數(shù)包含在<msmsl:script>元素所定義的腳本塊中。樣式表可包含多個腳本塊,各腳本塊的操作相互獨立。也就是說,如果在腳本塊的內(nèi)部執(zhí)行,則無法調(diào)用在其他腳本塊中定義的函數(shù),除非該腳本塊聲明為具有同一命名空間和同一腳本語言。由于每個腳本塊都可以使用自己的語言,因此腳本塊的分析將遵照該語言分析器的語法規(guī)則進(jìn)行。使用的語法對于所用的語言而言必須是正確的。例如,如果使用的是 C# 腳本塊,則在該塊中使用 XML 注釋節(jié)點 <!-- an XML comment --> 是錯誤的,應(yīng)該使用C#注釋符"http://"或者"/*…*/"。
下面的例子建立一個附帶名字空間前綴"mycompany"的腳本塊,腳本中包含一個"sum"的函數(shù),該函數(shù)接受兩個double類型參數(shù),并計算它們的和,然后轉(zhuǎn)換成字符串輸出。隨后,xsl元素<xsl:value-of>的select屬性調(diào)用這個函數(shù)并輸出結(jié)果。
<?xml version="1.0" encoding="utf-8" ?><xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:mycompany="http://mycompany.com/mynamespace" version="1.0"> <msxsl:script language="c#" implements-prefix="mycompany"> private string sum(double a,double b) { return (a+b).ToString(); } </msxsl:script> <xsl:template match="/"> <xsl:value-of select="mycompany:sum(5.0,10.0)"/> </xsl:template></xsl:stylesheet>
為演示效果,在<xsl:value-of select="mycompany:sum(5,10)" />中調(diào)用擴(kuò)展函數(shù)sum時,硬性編碼了兩個double值5.0和10.0,實際中,參數(shù)取值應(yīng)該來自于xml源文檔或者通過自定義參數(shù)傳入。
鑒于xml元素內(nèi)容的特殊要求,即它不能直接包含"、‘、<、>、&等字符以及所有非顯示字符,而給定語言的運算符、標(biāo)識符或分隔符恰好可能包含這些字符,它們有可能被錯誤地解釋為XML。而將所有這些字符完全替換成對應(yīng)的實體(標(biāo)準(zhǔn)實體和字符實體)引用將降低xml文件的可讀性。所以,在使用 msxsl:script 元素時,強烈建議無論使用何種語言,都將腳本放置在 CDATA 節(jié)內(nèi)。例如,下面的 XML 顯示放置代碼的 CDATA 節(jié)的模板。
<msxsl:script implements-prefix=‘myScript‘ language=‘c#‘> <![CDATA[ <!--在這里放置代碼--> ]]></msxsl:script>
例如,下面的示例顯示如何在腳本中使用邏輯 AND 運算符。
<msxsl:script implements-prefix=‘myScript‘ language=‘c#‘> public string book(string abc, string xyz) { if ((abc== abc)&&(abc== xyz)) return bar+xyz; else return null; }</msxsl:script>
由于"&"符沒有轉(zhuǎn)義,因此這將引發(fā)異常。將文檔作為 XML 加載,并且不對 msxsl:script 元素標(biāo)記之間的文本運用任何特殊處理。
.Net中使用帶擴(kuò)展功能的Xslt樣式表
.Net中的Xslt轉(zhuǎn)換對象提供了對在Xslt文檔中用腳本擴(kuò)展元素來嵌入腳本語言的支持。XslTransform類專門為此提供實現(xiàn)。它包含的方法使用 Xslt樣式表轉(zhuǎn)換 Xml 數(shù)據(jù)為指定的輸出。其中,用于載入Xslt轉(zhuǎn)換文檔的XslTransform..Load()方法接受包含嵌入腳本的樣式表,實際執(zhí)行轉(zhuǎn)換的XslTransform.Transform()方法使用這個樣式表將Xml源文檔轉(zhuǎn)換為指定的輸出。在這里,源Xsl文檔是否包含嵌入腳本并不影響程序代碼的編寫,你不需要為此額外添加代碼。也就是說,在高級語言中編寫程序代碼時,包含嵌入腳本的Xsl文檔與不包含嵌入腳本的Xsl文檔使用一樣的轉(zhuǎn)換語句。
XslTransform類支持使用<msxsl:script>元素的嵌入腳本撰寫。加載樣式表時,任何已定義的函數(shù)均通過包裝在類定義中被編譯為Microsoft中間語言(MSIL),因此不會有任何性能損失。
綜合上面的知識,下面使用Visual Studio.Net SDK創(chuàng)建了一個控制臺應(yīng)用程序UseXsltScript,該程序調(diào)用包含嵌入腳本的trans.xsl轉(zhuǎn)換文檔,對data.xml源文檔執(zhí)行轉(zhuǎn)換,并將結(jié)果輸出到一個新的newxml.xml文件。該例中的Xslt嵌入腳本用來計算已知三角形底邊及該邊對應(yīng)高度的情況下,計算此三角形的面積。
using System;using System.Xml;using System.Xml.Xsl;using System.IO;namespace MyCompanyUri.UseXsltScript{ class Xslt { private const string xmlfile = "../../data.xml";//xml源文檔 private const string xslfile = "../../trans.xsl";//xsl轉(zhuǎn)換文檔 private const string newxml="../../outxml.xml";//轉(zhuǎn)換后的xml文檔 public static void Main() { XmlDocument doc=new XmlDocument();//實例化xml文檔對象 doc.Load(xmlfile);//加載xml文檔 XslTransform xslt = new XslTransform(); //實例化XsltTransform轉(zhuǎn)換類對象 xslt.Load(xslfile);//加載xslt轉(zhuǎn)換樣式表 FileStream myStream=new FileStream(newxml,FileMode.OpenOrCreate);//以流方式打開文件 XmlTextWriter writer = new XmlTextWriter(myStream,null);//實例化xml編寫器并提供控制臺輸出 writer.Formatting = Formatting.Indented;//設(shè)置輸出的縮進(jìn)格式 xslt.Transform(doc, null, writer);//轉(zhuǎn)換xml文檔并輸出 writer.Close(); } }}
xml源文件data.xml:
<?xml version="1.0" encoding="utf-8" ?><triangles> <triangle> <size> <width>2</width> <height>4</height> </size> </triangle> <triangle> <size> <width>13</width> <height>8</height> </size> </triangle></triangles>
trans.xsl文件:
<?xml version="1.0" encoding="utf-8" ?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:myscript="urn:myns"> <msxsl:script language="C#" implements-prefix="myscript"> <![CDATA[ public double triangleArea(double width,double height){ double area = width*height/2;//面積公式:(底*高)/2 return area;//返回三角形面積 } ]]> </msxsl:script> <xsl:template match="http://triangles"> <triangles> <xsl:for-each select="triangle/size"><!--選定size節(jié)點--> <triangle> <xsl:copy-of select="../size" /><!--復(fù)制size節(jié)點 --> <area> <xsl:value-of select="myscript:triangleArea(width,height)" /><!--調(diào)用函數(shù)--> </area> </triangle> </xsl:for-each> </triangles> </xsl:template></xsl:stylesheet>
執(zhí)行這個控制臺程序,在應(yīng)用程序目錄的相應(yīng)地方將生成outxml.xml文件。它將包含給定三角形的底邊、高度尺寸及對應(yīng)的面積。
另外,運行這個應(yīng)用程序的時候你能夠明顯感覺到程序產(chǎn)生了短暫的延遲。這是因為在裝入樣式表處理嵌入式腳本之前,必須先裝入c#編譯器并運行該嵌入式c#腳本,這將花費2秒左右的時間。
需要注意的是:Xslt嵌入腳本中擴(kuò)展函數(shù)的參數(shù)及函數(shù)返回值必須是 W3C XPath 類型之一,這些類型并不與.Net所支持的類型完全一致(關(guān)于這些W3C Xpath類型和.Net類型的對應(yīng)情況請參見MSDN)。如果腳本函數(shù)使用其他的類型,或者,如果函數(shù)在樣式表加載到 XslTransform 對象中時不進(jìn)行編譯,則將引發(fā)異常。