Xsl模板应用基础(二、程序转换)

前文简要介绍了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   "&#160;">
 4     <!ENTITY copy   "&#169;">
 5     <!ENTITY reg    "&#174;">
 6     <!ENTITY trade  "&#8482;">
 7     <!ENTITY mdash  "&#8212;">
 8     <!ENTITY ldquo  "&#8220;">
 9     <!ENTITY rdquo  "&#8221;"> 
10     <!ENTITY pound  "&#163;">
11     <!ENTITY yen    "&#165;">
12     <!ENTITY euro   "&#8364;">
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'>&lt;!DOCTYPE html&gt;</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:ExtensionFuncurn:后面的字符串表示传入对象的命名空间,可以随意定义(如果有多个对象,需保证唯一),另一个参数是当前服务器时间,参数名为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)大一些。

 

posted @ 2015-06-10 08:50  蔡大卫  阅读(1301)  评论(8编辑  收藏  举报