前文簡要介紹了Xsl語言與轉換流程,本文將演示使用C#實現Xml處理器,再回顧一下轉換流程:
從上圖可知,Xml處理器其實就是一個轉換方法,有輸入輸出,還有過程參數。下面是核心代碼片段:
1 using System.Xml; 2 using System.Xml.Xsl; 3 ...... 4 5 /// <summary> 6 /// 使用xsl格式化xml,結果寫入到Result參數。 7 /// </summary> 8 /// <param name="Xdom"></param> 9 /// <param name="XslFile"></param> 10 /// <param name="Result">結果寫入到此流中</param> 11 public static void ConvertXsl(XDocument Xdom, string XslFile, Dictionary<string, object>Params, Stream Result) 12 { 13 XslCompiledTransform Process = GetXslProcess(XslFile); 14 XsltArgumentList Arguments = InitArgusList(Params); 15 using (StreamWriter Writer = new StreamWriter(Result, Encoding.UTF8)) 16 { 17 Process.Transform(Xdom.CreateReader(), Arguments, Writer); 18 } 19 } 20 21 /// <summary> 22 /// 初始化Xsl轉換過程需要的過程參數 23 /// </summary> 24 /// <param name="Argus"></param> 25 /// <returns></returns> 26 private static XsltArgumentList InitArgusList(Dictionary<string, object> Argus) 27 { 28 if (Argus == null) return null; 29 XsltArgumentList Arguments = new XsltArgumentList(); 30 foreach (string key in Argus.Keys) 31 { 32 if (key.StartsWith("urn:")) 33 { 34 Arguments.AddExtensionObject(key, Argus[key]); 35 } 36 else 37 { 38 Arguments.AddParam(key, String.Empty, Argus[key] == null ? String.Empty : Argus[key]); 39 } 40 } 41 return Arguments; 42 } 43 44 /// <summary> 45 /// 獲取指定Xsl模板編譯后的轉換器 46 /// </summary> 47 /// <param name="XslFile"></param> 48 /// <returns></returns> 49 private static XslCompiledTransform GetXslProcess(string XslFile) 50 { 51 string CacheKey = XslFile; 52 if (HttpRuntime.Cache[CacheKey] != null) 53 { 54 return (XslCompiledTransform)HttpRuntime.Cache[CacheKey]; 55 } 56 57 XmlReaderSettings Setting = new XmlReaderSettings(); 58 Setting.ProhibitDtd = false; 59 Setting.XmlResolver = new XmlUrlResolver(); 60 Setting.IgnoreComments = true; 61 Setting.IgnoreWhitespace = true; 62 63 XslCompiledTransform Xct = new XslCompiledTransform(); 64 XsltSettings xs = new XsltSettings(true, true); 65 XmlUrlResolver resolver = new XmlUrlResolver(); 66 resolver.Credentials = System.Net.CredentialCache.DefaultCredentials; 67 68 //最后一次訪問緩存后,緩存對象繼續存活的時間,超過后自動被回收。 69 TimeSpan MaxAge = TimeSpan.FromMinutes(20); 70 using (FileStream Fs = new FileStream(XslFile, FileMode.Open, FileAccess.Read)) 71 { 72 using (XmlReader Xr = XmlReader.Create(Fs, Setting)) 73 { 74 Xct.Load(Xr, xs, resolver); 75 System.Web.Caching.CacheDependency Dep = new System.Web.Caching.CacheDependency(XslFile); 76 HttpRuntime.Cache.Insert(CacheKey, Xct, Dep, System.Web.Caching.Cache.NoAbsoluteExpiration, MaxAge); 77 } 78 } 79 return Xct; 80 }
上面提供一個公共方法 ConvertXsl() 供調用,第一個傳入的參數也可以改造為 XmlDocument 類型的實例,看個人喜好了。
第二個參數是XslFile文件的完整磁盤路徑。該參數在私有方法GetXslProcess()中使用,內部使用HttpRuntime.Cache容器緩存Xsl編譯后的實例對象,並依賴於Xsl文件的改動(一旦Xsl文件被修改,該緩存自動失效)。由於引入了緩存,使得Xsl的轉換變得非常快速高效。網上有些文章批評Xsl轉換低效,所舉例子大都沒有使用緩存機制,每次轉換都完整加載一次Xsl,效率自然低下。
第三個參數是轉換過程參數。為了簡化調用,參數類型設為Dictionary<string, object>。轉換參數有兩種,一種是值類型,內部通過AddParam(key, value)方法加入,value雖然是object類型,但一般是int、string、NodeSet簡單類型。另一種參數是通過AddExtensionObject(key, value)方法加入,key是Uri類型,value可以是類的實例,在Xsl文件中可以調用該實例的成員方法,這一點很酷,相當於在Xsl中調用C#的方法,幾乎就可以為所欲為了(查看MSDN中的XsltArgumentList )。上面封裝的方法中,對傳入第二種參數有一約定,即參數名為urn:開頭的參數,將被認作擴展對象加入。
下面是調用方法:
1 public class Student 2 { 3 public string Name; 4 public int Age; 5 public string Say(string Words) 6 { 7 return "我是" + this.Name + ",今年" + this.Age.ToString() + "歲了。我想說:" + Words; 8 } 9 } 10 11 public void Main () { 12 Dictionary<string, object> Params = new Dictionary<string, object>(); 13 Student s = new Student(); 14 s.Name = "摩羅叉"; 15 s.Age = 13; 16 Params.Add("urn:ExtensionFunc", s); 17 Params.Add("time", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")); 18 XDocument Xdom = XDocument.Load(Server.MapPath("student.xml")); 19 string XslFile = Server.MapPath("student.xsl"); 20 ConvertXsl(Xdom, XslFile, Params, Response.OutputStream); 21 }
student.xml 與前文提供的一致,僅去除第二行的處理指令。student.xsl則更新如下:
1 <?xml version="1.0" encoding="utf-8"?> 2 <!DOCTYPE xsl:stylesheet [ 3 <!ENTITY nbsp " "> 4 <!ENTITY copy "©"> 5 <!ENTITY reg "®"> 6 <!ENTITY trade "™"> 7 <!ENTITY mdash "—"> 8 <!ENTITY ldquo "“"> 9 <!ENTITY rdquo "”"> 10 <!ENTITY pound "£"> 11 <!ENTITY yen "¥"> 12 <!ENTITY euro "€"> 13 ]> 14 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:student="urn:ExtensionFunc" extension-element-prefixes="student"> 15 <xsl:output method="html"/> 16 <xsl:param name="time" /> 17 <xsl:template match="/"> 18 <xsl:text disable-output-escaping='yes'><!DOCTYPE html></xsl:text> 19 <html> 20 <head> 21 <title>學生清單</title> 22 </head> 23 <body> 24 <table border="1" cellpadding="5" cellspacing="0"> 25 <tr> 26 <th>ID</th> 27 <th>姓名</th> 28 <th>年齡</th> 29 <th>性別</th> 30 </tr> 31 <xsl:for-each select="/root/student"> 32 <tr> 33 <td> 34 <xsl:value-of select="@id"/> 35 </td> 36 <td> 37 <xsl:value-of select="name"/> 38 </td> 39 <td> 40 <xsl:value-of select="age"/> 41 </td> 42 <td> 43 <xsl:choose> 44 <xsl:when test="sex='male'"> 45 <xsl:value-of select="'男'"/> 46 </xsl:when> 47 <xsl:when test="sex='female'"> 48 <xsl:value-of select="'女'"/> 49 </xsl:when> 50 <xsl:otherwise> 51 <xsl:value-of select="'未知'"/> 52 </xsl:otherwise> 53 </xsl:choose> 54 </td> 55 </tr> 56 </xsl:for-each> 57 </table> 58 現在時間:<xsl:value-of select="$time"/><br/> 59 說點什么:<xsl:value-of select="student:Say('尼瑪!')"/><br/> 60 學生總數:<xsl:value-of select="student:GetTotal(/root/student)"/><br/> 61 書名:<xsl:value-of select="student:GetBooks()/root/book/@name"/><br/> 62 </body> 63 </html> 64 65 </xsl:template> 66 </xsl:stylesheet>
從C#中的Main()方法可知,轉換過程,傳入兩個參數,第一個是Student類實例,參數名為urn:ExtensionFunc,urn:后面的字符串表示傳入對象的命名空間,可以隨意定義(如果有多個對象,需保證唯一),另一個參數是當前服務器時間,參數名為time。這兩個參數在student.xsl文件中有相應的體現,xsl第16行<xsl:param name="time" />表示接收傳入值參數,參數名為time,調用時需使用 $time 才能得到參數值。另一個參數由於是類實例,需要在第14行先聲明接收的命名空間,定義前綴(簡稱),Xsl中聲明的命名空間必須與C#中定義的命名空間一致,然后在第59行使用簡稱調用 student:Say('xxx'),Say方法在 Student 類中定義。這就實現了Xsl的跨界引用,但凡是跨界,都能帶來無限的想象空間。
如果在Xsl中調用擴展方法,並且傳入當前上下文的節點,如(假如傳入的student實例實現了GetTotal方法):
1 <xsl:value-of select="student:GetTotal(/root/student)"/>
那么該方法將得到類型為 System.Xml.XPath.XPathNodeIterator 的參數:
1 public class Student 2 { 3 ...... 4 public string GetTotal(XPathNodeIterator Iterator) 5 { 6 return Iterator.Count.ToString(); 7 } 8 }
擴展方法還能返回XML節點與節點集合,供Xsl使用:
1 public XPathNavigator GetBooks() 2 { 3 XPathNavigator Nav = XDocument.Parse("<root><book name='金剛經'/></root>").CreateNavigator(); 4 Nav.MoveToRoot(); 5 return Nav; 6 }
Xsl調用GetBooks():
1 <xsl:value-of select="student:GetBooks()/root/book/@name"/>
用瀏覽器訪問,得到最終結果:
本文使用C#實現了Xml+Xsl=>Html 的轉換過程(下載例子),並且舉例轉換過程中傳入的兩種參數的應用。在實際應用過程中,由於頁面的構造完全使用Xsl格式化,不再需要使用 System.Web.UI.Page 類,因此推薦在一般處理程序(HttpHandle)中執行Xsl轉換,這樣可以得到完整的頁面HTML代碼,或可再做一次服務端緩存,或者做基於HTTP協議的客戶端緩存,甚至手工實現Gzip傳輸,自由度比普通頁面程序(aspx)大一些。