XSLT用于将XML文档转换为我们想要的文档类型,最常见的是转换为HTML。XSLT本身也是一个XML文档,XSLT规范中定义了用于XSLT的一些元素。下面的表格展示了一些常用的元素,注意说明中提到的样式都是指XSLT样式而非用于HTML的CSS样式。
元素 |
说明 |
xsl:apply-imports |
应用来自导入样式表中的模版规则。导入样式表中的模板将覆盖源样式中的模板。 |
xsl:apply-templates |
向当前元素或当前元素的子元素应用模板。可以通过在select属性中指定XPath表达式来选择要应用模板的元素 |
xsl:attribute |
向输出元素添加属性。 |
xsl:attribute-set |
创建命名的属性集,可以被不同的元素共享。 |
xsl:call-template |
调用一个指定的模板。 |
xsl:choose |
与<xsl:when>以及<xsl:otherwise>协同使用,来表达多重条件测试。类似C#中的switch..case |
xsl:comment |
在结果树中创建注释节点。 |
xsl:copy |
创建当前节点的一个备份(无子节点及属性)。 |
xsl:copy-of |
创建当前节点的一个备份(带有子节点及属性)。 |
xsl:decimal-format |
定义当通过 format-number() 函数把数字转换为字符串时,所要使用的字符和符号。 |
xsl:element |
在输出文档中创建一个元素节点。 |
xsl:fallback |
假如处理器不支持某个XSLT元素,定义一段备用代码来运行。这常用于在使用某个较新版本的XSLT时,为了让有些不支持这个版本的处理器(常见的是浏览器)也能解析而提供一些备用代码。 |
xsl:for-each |
遍历指定的节点集中的每个节点。 |
xsl:if |
包含一个模板,仅当某个指定的条件成立时应用此模板。 |
xsl:import |
用于把一个样式表中的内容倒入另一个样式表中。倒入的样式表比源样式表优先级高。 |
xsl:include |
把一个样式表中的内容包含到另一个样式表中。这两个样式表的优先级相同。 |
xsl:key |
声明一个命名的键。用于key()函数 |
xsl:message |
向输出写一条消息(用于错误报告)。 |
xsl:namespace-alias |
把样式表中的命名空间替换为输出中不同的命名空间。 |
xsl:number |
测定当前节点的整数位置,并转为数字格式。 |
xsl:otherwise |
定义 <xsl:choose> 元素的默认动作。 |
xsl:output |
定义输出文档的格式。 |
xsl:param |
声明一个局部或全局参数。局部参数的作用域是参数所在的模板 |
xsl:preserve-space |
用于定义保留空白的元素。 |
xsl:processing-instruction |
生成处理指令节点(将处理指令写入输出)。 |
xsl:sort |
对结果进行排序。与<xsl:for-each>或<xsl:apply-templates>共同使用。 |
xsl:strip-space |
定义应当删除空白字符的元素。 |
xsl:stylesheet |
定义样式表的根元素。这个元素必须是XSLT文档中最外层元素并且必须包含一个与XSLT规范相关联的命名空间和一个版本属性。 |
xsl:template |
为匹配特定模式的的节点定义一个应用的模板,这个模板是可重用的。 |
xsl:text |
通过样式表生成文本节点。 |
xsl:transform |
定义样式表的根元素。 |
xsl:value-of |
提取选定节点的值写入输出。 |
xsl:variable |
声明局部或者全局的变量。 |
xsl:when |
定义 <xsl:choose> 元素的动作。 |
xsl:with-param |
定义被传入某个通过<xsl:call-template>调用的模板的参数的值。 |
接下来是XSLT使用的函数:
函数 |
说明 |
current() |
返回只有当前节点的节点集。 |
document() |
用于访问外部 XML 文档中的节点。允许从初始化数据输入流之外的源中访问数据。 |
element-available() |
测试 XSLT 处理器是否支持指定的元素。 |
format-number() |
把数字转换为字符串。 |
function-available() |
测试 XSLT 处理器是否支持指定的函数。 |
generate-id() |
返回唯一标识指定节点的字符串值。 |
key() |
检索使用 <xsl:key> 语句标记的元素。 |
node-set() |
将树转换为节点集。产生的节点集总是包含单个节点并且是树的根节点。 |
system-property() |
返回系统属性的值。 |
unparsed-entity-uri() |
返回未解析实体的 URI。 |
除了在XSLT中使用上面列表中列出的它自身的函数,还可以使用XPath的函数。了解XSLT中的元素和函数接下来展示下如何定义XSLT,如何在XML中使用XSLT。
首先我们给一个简单的例子展示如何通过XSLT把一个XML转为HTML:
如下是带转换的XML文档:
1 <?xml version="1.0" encoding="utf-8" ?> 2 <bookstore> 3 <book genre="autobiography"> 4 <title>Essential C# 4.0</title> 5 <author> 6 <first-name>Mark</first-name> 7 <last-name>Michaelis</last-name> 8 </author> 9 <price>49.99</price> 10 </book> 11 <book genre="autobiography"> 12 <title>CLR via C#</title> 13 <author> 14 <first-name>Jeffrey</first-name> 15 <last-name>Richter</last-name> 16 </author> 17 <price>59.99</price> 18 </book> 19 <book genre="autobiography"> 20 <title>Effective C#</title> 21 <author> 22 <first-name>Bill</first-name> 23 <last-name>Wagner</last-name> 24 </author> 25 <price>39.99</price> 26 </book> 27 </bookstore>
接着是一个简单的XSLT来处理上面的XML:
1 <?xml version="1.0" encoding="utf-8"?> 2 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 3 <xsl:output method="html"/> 4 <xsl:template match="/"> 5 <html> 6 <title>XSL Transformation</title> 7 <body> 8 <h2>BookShelf</h2> 9 <table border="1"> 10 <tr bgcolor="Azure"> 11 <th align="left">Title</th> 12 <th align="left">Price</th> 13 </tr> 14 <xsl:for-each select="bookstore/book"> 15 <tr> 16 <td> 17 <xsl:value-of select="title"/> 18 </td> 19 <td> 20 <xsl:value-of select="price"/> 21 </td> 22 </tr> 23 </xsl:for-each> 24 </table> 25 </body> 26 </html> 27 </xsl:template> 28 </xsl:stylesheet>
当我们使用后文价绍的引用XSLT的代码后,在浏览器中我们可以看到如下输出:
最后我们来解释一下这个XSLT文档,文档最开始是所有XML文档都必须的声明,第二行就是这个XSLT的根元素。而 <xsl:template> 元素标志着模板的开始,其中的属性match=”/”表示将模板关联到XML源文档的根元素上,在这个例子中即<bookstore>。剩下部分比较值得一提就是通过 <xsl:for-each> 元素定义了一个循环处理一个节点集中的所有元素。接着我们看一下更复杂的情况:
在输出时对XML元素进行排序
我们要对上面的例子进行一些改造,假如我们想要对输出到表格中的书籍按名称进行排序,我们只需添加一个<xsl:sort>元素到<xsl:for-each>元素中,并使用select属性制定排序列即可,代码段:
1 <xsl:for-each select="bookstore/book"> 2 <xsl:sort select="title"/> 3 <tr>
这样输出结果变为:
按条件输出模板
这个例子中我们打算只输出价格大于$50的书籍,这也很简单只需要在模板的外侧套上一层<xsl:if>元素就可以了。这个元素的test属性中我们设置判断是否输出模板的条件,见代码:
1 <xsl:for-each select="bookstore/book"> 2 <xsl:if test="price > 50"> 3 <tr> 4 <td> 5 <xsl:value-of select="title"/> 6 </td> 7 <td> 8 <xsl:value-of select="price"/> 9 </td> 10 </tr> 11 </xsl:if> 12 </xsl:for-each>
这样输出变为:
处理多个条件
这个场景中我们,我们将不同范围的价格所在的单元格填充不同的背景色。这里我们将用到<xsl:choose>元素及用于这个元素内部的<xsl:when >与<xsl:otherwise>。我们将在输出价格单元的模板外应用这些元素,这段代码也是比较好理解的:
1 <xsl:for-each select="bookstore/book"> 2 <tr> 3 <td> 4 <xsl:value-of select="title"/> 5 </td> 6 <xsl:choose> 7 <xsl:when test="price < 40"> 8 <td bgcolor="LightGreen"> 9 <xsl:value-of select="price"/> 10 </td> 11 </xsl:when> 12 <xsl:when test="price > 40 and price < 50"> 13 <td bgcolor="Green"> 14 <xsl:value-of select="price"/> 15 </td> 16 </xsl:when> 17 <xsl:otherwise> 18 <td bgcolor="DarkGreen"> 19 <xsl:value-of select="price"/> 20 </td> 21 </xsl:otherwise> 22 </xsl:choose> 23 </tr> 24 </xsl:for-each>
效果图:
其他有用的元素
<xsl:variable>这个元素可以定义一个XSLT变量,虽然称其为变量但其只能赋值一次不能更改,类似于C#中的常量。其主要作用是用来保存一些会被反复用到的值。XSLT变量的定义很简单:
1 <xsl:variablename="var_name"select="var_value" />
其中name属性指定了变量的名称,而select属性指定变量的值。
XSLT变量有作用域的概念,直接定义在<xsl:stylssheet>下的为全局变量,否则为局部变量,在不同作用域内的局部变量名称是可以相同的。
要引用一个XSLT变量只需使用$+变量名称即可。另外通过<xsl:param>元素定义参数的方式与变量完全一致,XSLT的参数本质上为参数化的变量,同样参数引用方式也与使用变量相同。
在将XSLT应用于XML有两种方式,一种是静态方式,即在XML中引用样式表,二是动态方式,即通过程序应用样式表。下面是两个例子:
1. 静态方式引用
1 <?xml version="1.0" encoding="utf-8" ?> 2 <?xml-stylesheet type="text/xsl" href="books.xsl"?> 3 <bookstore> 4 </bookstore>
只要books.xsl与xml文件在一个文件夹下(或者使用其他相对路径),当使用如IE浏览器等具有XSLT解析功能的工具打开XML时,将会展示被XSLT转换后的结果。
2. 动态方式引用
这种方式需要借助.NET Framework的支持,.NET框架下System.Xml程序集中提供了XSLT的处理器,用于通过XSLT将XML转换成另外的结构。最主要的一个类就是.NET2.0开始提供的XslCompliedTransform类。这个类有两个核心方法Load(),用于加载Xslt文件,Transform()用于加载XML源文件并进行转换处理,转换输出的结果根据不同的重载分别会输出到Stream、TextWriter或XmlWriter。下面的一小段代码演示了这个过程:
1 string xmlPath = MapPath(@"App_Data\Books.xml"); 2 string xslPath = MapPath(@"App_Data\Books.xslt"); 3 XPathDocument xpathDoc = new XPathDocument(xmlPath); 4 XslCompiledTransform transform = new XslCompiledTransform(); 5 transform.Load(xslPath); 6 transform.Transform(xpathDoc, null, Response.Output);
当我们把其放在一个页面的Page_Load函数中,运行这个页面我们会得到与前文一样的效果。
另一个需要介绍的重要的类是XsltArgumentList类,其可以将参数和扩展对象传递给样式表。我们直接通过示例来说明这个类的使用:
首先我们要改造一下前文给出的样式表:
首先添加一个全局参数变量:
<xsl:paramname="discount"select="0.75" />
并添加一列显示折扣价格的模板,使用参数计算折扣后的价格:
<xsl:value-ofselect="price * ($discount)"/>
我们可以很方便的通过ASP.NET页面向<xsl:param>元素传递参数值,只需要添加这样两行代码:
1 XsltArgumentList argsList = new XsltArgumentList(); 2 argsList.AddParam("discount", "", "0.15");
当然还要传入Transform方法
1 transform.Transform(xpathDoc, argsList, Response.Output);
通过.NET扩展XSLT功能
由于XSLT提供的方法功能有限,有时我们需要通过扩展给转换过程添加更复杂的逻辑,我们将详细介绍两种方式:
1. 通过扩展对象
同样我们通过一个例子介绍完整的流程,场景需求和之前一样,我们需要计算一个折扣价格。首先我们需要完成扩展对象的定义,其被定义于一个名为Discount的类中。
1 public class Discount 2 { 3 public Discount() 4 { 5 } 6 public string ReturnDiscount(string price) 7 { 8 decimal priceValue = Convert.ToDecimal(price); 9 return (priceValue * 15 / 100).ToString(); 10 } 11 }
我们要做的就是将Discount对象传入XSLT样式表中并调用ReturnDiscount()方法。这个仍然需要借助XsltArgumentList类来完成(XsltArgumentList提供AddExtensionObject方法用于完成扩展对象的添加):
1 XsltArgumentList argsList = new XsltArgumentList(); 2 Discount obj = new Discount(); 3 argsList.AddExtensionObject("urn:myDiscount", obj);
接着我们需要在XSLT文件根元素中添加一个自定义的命名空间,命名空间的值即我们传入AddExtensionObject方法的Uri(命名空间的名称可随意定)。
1 <xsl:stylesheetversion="1.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform"xmlns:myDiscount="urn:myDiscount">
这样我们就可以在xslt模板中通过命名空间方便的调用ReturnDiscount方法:
前文中:
1 <td> 2 <xsl:value-ofselect="price * ($discount)"/> 3 </td>
可以被如下代码等效替换:
1 <td bgcolor="Green"> 2 <xsl:value-ofselect="myDiscount:ReturnDiscount(price)"/> 3 </td>
扩展对象有两个主要优势,提供更好的类封装和重用(多个样式表共享一个扩展对象),另外可以使样式表更小,更具可维护性。
2. 在XSL样式表中嵌入脚本
除了使用上文介绍的扩展对象,还可以通过msxsl:script元素直接在XSL样式表中潜入脚本来实现自定义的逻辑。下面的例子中实现了与上文相同的功能但这次是通过嵌入脚本来完成的。这里只给出与扩展对象方式示例代码不同的部分(这段元素放在根元素的下一级中):
1 <msxsl:script language="C#" implements-prefix="myDiscount"> 2 <![CDATA[ 3 public string ReturnDiscount(string price) 4 { 5 decimal priceValue = Convert.ToDecimal(price); 6 return (priceValue * 15 / 100).ToString(); 7 } 8 ]]> 9 </msxsl:script>
另外我们还需要通过XsltSettings告知XslCompiledTransform来启用嵌入脚本代码块支持(构造函数第二个参数设为true),并将其传入XslCompiledTransform对象的Load方法:
1 XPathDocument xpathDoc = new XPathDocument(xmlPath); 2 XsltSettings settings = new XsltSettings(false, true); 3 4 XslCompiledTransform transform = new XslCompiledTransform(); 5 transform.Load(xslPath, settings, null); 6 transform.Transform(xpathDoc, null, Response.Output);
嵌入脚本唯一的缺点就是不能在多个样式表之间共享。
XsltSettings类
上文我们用到了XsltSettings类,这个类用于指定在XSLT样式表执行过程中所要支持的XSLT功能,其有下面一些属性:
属性 |
说明 |
Default |
静态属性,默认设置下禁用 document() 函数和嵌入脚本的支持 |
EnableDocumentFunction |
在XSLT样式表中启用XSLT的 document() 函数 |
EnableScript |
可以在XSLT样式表中使用脚本(前文我们就通过构造函数使用这个功能) |
TrustedXslt |
该属性启用 document() 函数和嵌入脚本的支持 |
最后来介绍一下XPath以及.NET对XPath的支持
XPath表达式语言
/ |
从一个根节点开始选择的绝对路径 |
// |
从任何地方开始选择结点的相对路径 |
@ |
选择节点的一个属性 |
* |
选择路径中的所有节点 |
| |
联合操作符,返回两个路径的联合的结果 |
. |
表示当前(默认)节点 |
.. |
表示当前节点的父节点 |
[] |
定义选择标准,条件可以是属性值或节点 如/book[@gener=”it”]表示具有值为it的gener属性的book元素 |
starts-with |
这个选择器根据元素名称的开头字符进行过滤,如/book/author[name,”z”]会返回名称以z开头的所有作者(author)。 |
position |
这个选择器根据位置获得元素 /book[position()=2]表示选择第二个book元素 |
count |
此过滤器可以计算元素的个数,接受的参数为元素的名称或*(代表全部元素) /book/author[count(name)=1]获取只有一个name子元素的author元素。 |
在.NET2.0中通过XmlNode(及其子类XmlDocument)类的 SelectNodes() 和 SelectSingleNode() 方法传入XPath来查找Xml中的元素。另外.NET2.0中提供了XPathNavigator对使用XPath查询进行了优化,下面是一段简单的示例代码,演示了XPathNavigator的使用:
1 //加载文档 2 XPathDocument document = new XPathDocument(xmlPath); 3 //通过document创建navigator 4 XPathNavigator navigator = document.CreateNavigator(); 5 //将XPath表达式编译成XPathExpression对象 6 XPathExpression expr = navigator.Compile("xpath expression ..."); 7 //得到匹配的节点 8 XPathNodeIterator nodes = navigator.Select(expr); 9 while (nodes.MoveNext()) 10 { 11 //... 12 }
随着LINQ的出现,我们可以用LINQ to XML方便的实现与XPath功能一致的查询,并且这个查询可以通过强类型化方式完成。详情可参见MSDN文档《XPath和LINQ to XML的比较》