级别: 初级
Benoit Marchal, 软件工程师, Pineapplesoft
2001 年 1 月 01 日
无论是 XSLT 的初学者还是经验丰富的程序员,都一定会发现 Benoit Marchal 的这五种技巧将改进编码并带来新的思路。这些技巧涵盖了利用 XSL 样式表(包括 HTML 实体)使用 CSS、合并客户机端 JavaScript、使用多个输入文档,以及使用 XSLT 自动生成样式表。本文包括了供您改编和再使用的样本代码。
XML 和 XSLT 的组合已在大中型网站的网站管理员之间逐渐流行起来。在 XSLT 出现之前,更改网站显示是一项重要任务:有一个人必须重新访问并更改站点上的每一个页面。但 XSLT 可以自动执行这个过程,这就大大节省了时间。
技巧 1:级联样式表、表和 XSLT
在讨论 XSLT 的文章中以关于 CSS 的技巧作为开始似乎有些奇怪,但有人经常问我“这两种样式表语言是否兼容?”答案是响亮的“是”。
清单 1 中的 products.xml 说明了这一点,它是以 XML 格式编写的产品列表。请花一点时间来熟悉 products.xml,因为我将使用它来说明这五种技巧。 清单 1. products.xml,XML 格式的产品清单
<?xml version="1.0"?>
<products>
<product href="http://www.playfield.com/text">
<name>Playfield Text</name>
<price currency="usd">299</price>
<description>Faster than the competition.</description>
<version>1.0</version>
</product>
<product href="http://www.playfield.com/virus">
<name>Playfield Virus</name>
<price currency="eur">199</price>
<description>Protect yourself against malicious code.</description>
<version>5.0</version>
</product>
<product href="http://www.playfield.com/calc">
<name>Playfield Calc</name>
<price currency="usd">299</price>
<description>Clear picture on your data.</description>
<version>1.5</version>
</product>
<product href="http://www.playfield.com/db">
<name>Playfield DB</name>
<price currency="cad">599</price>
<description>Organize your data.</description>
</product>
</products>
|
要将此文档的格式转换成 HTML,可以使用清单 2 中的 table.xsl。将 table.xsl 应用到 products.xml的结果如 图 1 所示。可以看到,每隔一行都有一个灰色背景,这就提高了可读性。在 table.xsl 中,这是由于使用了级联样式表而实现的。 清单 2. table.xsl,使用 CSS 的 XSLT 样式表
<?xml version="1.0"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"
indent="no"/>
<xsl:template match="/products">
<html>
<head>
<title>Cascading Style Sheet</title>
<link rel="stylesheet" type="text/css" href="table.css" title="Style"/>
</head>
<body>
<table>
<tr class="header">
<td>Name</td>
<td>Price</td>
<td>Description</td>
</tr>
<xsl:apply-templates/>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="product[position() mod 2 = 1]">
<tr class="odd">
<td><xsl:value-of select="name"/></td>
<td><xsl:value-of select="price"/></td>
<td><xsl:value-of select="description"/></td>
</tr>
</xsl:template>
<xsl:template match="product">
<tr class="even">
<td><xsl:value-of select="name"/></td>
<td><xsl:value-of select="price"/></td>
<td><xsl:value-of select="description"/></td>
</tr>
</xsl:template>
</xsl:stylesheet>
|
|
小技巧:更小的 HTML 文件
请注意 清单 2 中 table.xsl 的 <xsl:output> 元素中以红色突出显示的 indent="no" 属性。这个属性告诉 XSLT 处理器不要缩排 HTML 文档,这通常会生成一个较小的 HTML 文件,可以使下载更快。 | |
图 1. HTML 格式的产品列表
Name |
Price |
Description |
Playfield Text |
299 |
Faster than the competition. |
Playfield Virus |
199 |
Protect against malicious code. |
Playfield Calc |
299 |
Clear picture on your data. |
Playfield DB |
599 |
Organize your data. |
它是如何工作的? table.xsl 样式表将 HTML <link> 元素插入到输出中。 <link> 会装入级联样式表:
<link rel="stylesheet" type="text/css" href="table.css" title="Style"/>
|
CSS 和 XSLT 之间没有冲突,因为不会同时使用它们。首先将 XSLT 样式表应用到 XML 文档 products.xml 。这会生成传递给浏览器的 XML 文档。实际上,HTML 文档装入级联样式表与此阶段无关。仅当浏览器装入 HTML 文档时,才会使用 CSS。
为了使用 CSS,XSLT 样式表 table.xsl 会在适当的位置再插入 class 属性。实际上,表中的元素被标记成 odd 或 even 类。那如何识别 XSLT 中的奇数行呢?只要将一个条件添加到 match 属性,如从清单 2 中的样式表中摘录的这些行中所示:
<xsl:template match="product[position() mod 2 = 1]">
<tr class="odd">
<td><xsl:value-of select="name"/></td>
<td><xsl:value-of select="price"/></td>
<td><xsl:value-of select="description"/></td>
</tr>
</xsl:template>
|
清单 3 中的简单级联样式表 table.css 在表的偶数行上放置灰色背景。 清单 3. table.css,图 1 中表的 CSS
.header { background-color: #999999; font-weight: bold; }
.odd { background-color: normal; }
.even { background-color: #dfdfdf; }
|
什么时候应该混合 CSS 和 XSLT?我认为此组合适用于以下情况:
- 要获取更多的显示控制,因为 CSS 的功能比原始 HTML 更强大
- 要生成更小的 HTML 文件,以便可以更快地下载
但是请注意,当组合这两种样式表时,在可维护性方面就会有所损失。事实上,设计人员最初是因为用一个文件就能控制整个网站的设计才会钟情于 CSS。在那方面,XSLT 与 CSS 一起使用简直就是多余,XSLT 就已经可以使一次性重新格式化几个文档变得很容易。
小心! table.xsl 和本文中的其它样式表需要使用符合标准的 XSLT 处理器。Internet Explorer 5.0 和 5.5 附带的处理器并不符合标准。如果需要一个合适的 XSLT 处理器,可以尝试使用 Xalan,它是 Apache 项目中的 XSLT 处理器,或者将 IE 中的处理器升级到版本 3.0(请参阅 参考资料)。
技巧 2:HTML 实体
开发人员常问的另一个有关编写 XSLT 样式表的问题是如何插入 HTML 实体。尤其是,如何插入 实体(不可中断空格)。在其它样式表中, 空格用于在表中创建非空单元。
糟糕的是,这种直观的解决方案不起作用:
为什么?XSLT 样式表是一个 XML 文档。对它进行分析时,会解析实体,而且会将 实体当作 XML 实体来进行解析。由于没有在 HTML 中定义 ,因此它会导致一个错误。
在清单 4 的 nbsp.xsl 中说明了变通方法,并且以红色突出显示了有关元素。可以看到,每次编写 HTML 格式的实体时,它使用的字符比您预期输入的字符要多。但如果您知道了代码之后,只要在适当位置剪贴这段代码即可。 清单 4. nbsp.xsl,演示如何在样式表中插入 HTML 实体
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="html" indent="no"/>
<xsl:template match="/">
<html>
<head><title>HTML Entities</title></head>
<body>
<table border="1">
<tr>
<td>Name</td>
<td>Price</td>
<td>Description</td>
<td>Version</td>
</tr>
<xsl:apply-templates/>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="product[version]">
<tr>
<td><xsl:value-of select="name"/></td>
<td><xsl:value-of select="price"/></td>
<td><xsl:value-of select="description"/></td>
<td><xsl:value-of select="version"/></td>
</tr>
</xsl:template>
<xsl:template match="product">
<tr>
<td><xsl:value-of select="name"/></td>
<td><xsl:value-of select="price"/></td>
<td><xsl:value-of select="description"/></td>
<td>
<xsl:text disable-output-escaping="yes"> </xsl:text></td>
</tr>
</xsl:template>
</xsl:stylesheet>
|
如清单 4 中所摘录的,不可中断空格实体的正确代码是:
<xsl:text disable-output-escaping="yes"> </xsl:text>
|
那行代码中发生了什么?诀窍是使用 <xsl:text> 元素以及 disable-output-escaping="yes" 属性。 <xsl:text> 在输出 HTML 中创建了一些文本。属性告诉处理器不要将文本内容转义。文本本身只是 和要转义成 XML 格式的 & 字符。
再次声明,样式表是当作 XML 文档读取的,因此 就是 。如果告诉处理器不要转义输出 HTML 中的 & ,那么它会写下 。
|
小技巧:多个输出文档
相反情况会怎么样,即需要多个输出文档?根据一个样式表创建几个输出文档会如何?不幸的是,在撰写本文时,还没有标准解决方案。大多数 XSLT 处理器都有一些多个输出文件支持,如 Xalan 中的 org.apache.xalan.xslt.extensions.Redirect 。不要对标准解决方案绝望,XSLT 1.1(目前仍在开发中)将支持多个输出文档。 | |
技巧 3:多个输入文档
典型的 XSLT 样式表将一个 HTML 文档转换成另一个 XML 文档,或者转换成 HTML 文档。有时,这太过于局限性。(有关如何处理相反情况的简要介绍,请参阅侧栏 多个输出文档。)
例如,请注意在 products.xml 中, <price> 元素有一个 currency 属性。货币不是用普通文字表示,而是使用代码(例如, usd 代表美元,或者 cad 代表加元)。您可能想要在显示代码之前先转换代码。
由于全世界有一百多种货币,而许多应用程序会处理这些货币,的确应该在一个独立的 XML 文档中存储货币符号列表。清单 5 中的 codes.xml摘录了这样的文件。 清单 5. codes.xml,带货币符号代码的 XML 文档
<?xml version="1.0"?>
<currencies>
<currency>
<code>eur</code>
<name>Euros</name>
</currency>
<currency>
<code>usd</code>
<name>Dollars</name>
</currency>
<currency>
<code>cad</code>
<name>Canadian dollars</name>
</currency>
</currencies>
|
实际上,现在示例有两个 XML 文件, products.xml和 codes.xml ,您需要组合这两个文件来创建 HTML 文档。幸好,XSLT 使组合几个输入文件变得更容易,如清单 6 中 multi.xsl所说明的。
multi.xsl 中有两个重要步骤。首先,样式表打开 codes.xml (使用 document() 函数),然后将它赋值给 currencies 变量:
<xsl:variable
name="currencies"
select="document('codes.xml')/currencies"/>
|
然后,样式表可以通过变量从代码清单中抽取信息。当然,可以使用 XPath 来查询货币文档:
<xsl:value-of select="$currencies/currency[code=$currency]/name"/>
|
清单 6. multi.xsl,组合几个 XML 文档的样式表
<?xml version="1.0"?>
<xsl:stylesheet
version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="no"/>
<xsl:variable
name="currencies"
select="document('codes.xml')/currencies"/>
<xsl:template match="/">
<html>
<head><title>Multiple documents</title></head>
<body>
<table>
<tr bgcolor="#999999">
<td>Name</td>
<td>Price</td>
<td>Description</td>
<td>Version</td>
</tr>
<xsl:apply-templates/>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="product">
<xsl:variable name="currency" select="price/@currency"/>
<tr>
<td><xsl:value-of select="name"/></td>
<td>
<xsl:value-of select="price"/>
<xsl:text> </xsl:text>
<xsl:value-of select="$currencies/currency[code=$currency]/name"/>
</td>
<td><xsl:value-of select="description"/></td>
<td><xsl:value-of select="version"/></td>
</tr>
</xsl:template>
</xsl:stylesheet>
|
技巧 4:XSLT 和客户机端 JavaScript
虽然 XSLT 功能强大,但它还不能应付一些情况。例如,您也许想要使用客户机端脚本,如 JavaScript、JScript 或 VBScript。
如同 CSS,XSLT 并没有对所生成的 HTML 有所限制 -- 它还可以使用脚本。而且,正如清单 7 中 javascript.xsl 所说明的,可以将值从 XSLT 传递到 JavaScript。 清单 7. javascript.xsl,生成客户机端 JavaScript 的样式表
再次声明,要理解 javascript.xsl ,需要记住在哪个位置发生了什么事。首先应用 XSLT,生成 HTML 文件。文件包括了由 XSLT 生成其内容的 <script> 元素。接着,浏览器装入 HTML 文件,并执行脚本。请记住,脚本是由浏览器执行的,而不是样式表。
例如,在 javascript.xsl 中以红色突出显示的行中,样式表初始化 JavaScript 数组;实际上,是将值传递给脚本。
稍后,样式表在函数中生成调用,并再次将值传递给脚本(通过 XSLT position() 函数),如 清单 7 中以蓝色突出显示的行所示。
清单 8 显示了在 HTML 中生成的内容。当用户单击 <a> 标记时,浏览器将执行这个脚本,如下所示: 清单 8. 根据 javascript.xsl 生成的 HTML
<ul>
<li><a href="javascript:doSelect(1)">Playfield Text</a></li>
<li><a href="javascript:doSelect(2)">Playfield Virus</a></li>
<li><a href="javascript:doSelect(3)">Playfield Calc</a></li>
<li><a href="javascript:doSelect(4)">Playfield DB</a></li>
</ul>
|
能否从 XSLT 样式表中,而非 HTML 文档中调用 JavaScript 脚本?当然可以,但要使用 XSLT 扩展名。糟糕的是,XSLT 1.0 中并没有完全标准化 XSLT 扩展名。XSLT 1.1 将改进这个支持。
技巧 5:自动创建样式表
这种技巧是所有这五种技巧中最具挑战性的。
有些情况下,您必须编写许多样式表,但似乎可以使用一个样式表来创建它们。其实,这并不如您认为的那样困难,它特别适用于用不同语言编写的网站,或者那些拥有大量网页、但仅是具体内容有所区别的站点。
清单 9 中的 start.xsl 样式表说明了这一点。乍一看,它有点象典型的 XSLT 样式表。如果仔细观察,将会发现诸如 <link> 和 <para> 之类的特殊元素并不是 HTML 元素(HTML 元素应该是 <a> 或 <p> )。而且, <xsl:output> 元素没有 method 属性。 清单 9. start.xsl,普通 XSLT 样式表
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output/>
<xsl:template match="/">
<page title="XSLT Through Generation">
<xsl:for-each select="products/product">
<para>
<link href="{@href}"><xsl:value-of select="name"/></link>
</para>
</xsl:for-each>
</page>
</xsl:template>
</xsl:stylesheet>
|
诀窍是使用清单 10 中的另一个样式表 generate_html.xsl将 start.xsl转换成更典型的样式表。 清单 10. generate_html.xsl,用于将 start.xsl 改写成 HTML 的样式表
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="xsl:output">
<xsl:copy>
<xsl:attribute name="method">html</xsl:attribute>
</xsl:copy>
</xsl:template>
<xsl:template match="page">
<html>
<head><title><xsl:value-of select="@title"/></title></head>
<body>
<h1><xsl:value-of select="@title"/></h1>
<xsl:apply-templates/>
</body>
</html>
</xsl:template>
<xsl:template match="para">
<p><xsl:apply-templates/></p>
</xsl:template>
<xsl:template match="link">
<a href="{@href}"><xsl:apply-templates/></a>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
|
generate_html.xsl样式表将 start.xsl 当作 XML 文档处理,并将它转换成另一个 XML 文档。实际上,start.xsl 本身就是一个 XSLT 样式表,并且对于 generate_html.xsl 没有意义。例如,以下规则将 <link> 元素转换成 <a> :
<xsl:template match="link">
<a href="{@href}"><xsl:apply-templates/></a>
</xsl:template>
|
这段代码通过添加 method 属性来修正 <xsl:output> 元素:
<xsl:template match="xsl:output">
<xsl:copy>
<xsl:attribute name="method">html</xsl:attribute>
</xsl:copy>
</xsl:template>
|
不经修改复制样式表中的其它元素(包括 <xsl:template> 、 <xsl:for-each> 和其它 XSLT 指令):
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
|
将 generate_html.xsl 应用到 start.xsl 会生成一个样式表 generated.xsl 。创建 HTML 文档的是 generated.xsl,而不是 start.xsl。
这个技巧并不是带着您绕圈子,它是个非常好的示例,体现了使 XSLT 的逻辑达到最大化带来的好处。实际上,由于 XSLT 非常适用于将 XML 文档转换成其它 XML 文档,当然它也适用于转换 XSLT 样式表本身。 清单 11. generated.xsl,演示如何将 start.xsl 转换成 HTML
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html"/>
<xsl:template match="/">
<html><head><title>XSLT Through Generation</title></head>
<body><h1>XSLT Through Generation</h1>
<xsl:for-each select="products/product">
<p>
<a href="{@href}"><xsl:value-of select="name"/></a>
</p>
</xsl:for-each>
</body></html>
</xsl:template>
</xsl:stylesheet>
|
当网站需要创建许多样式表时,您可能想要应用这个技巧。例如,通过将 generate_html.xsl替换成 generate_wml.xsl,可以自动将 start.xsl 改编成 WML 格式(WML 是无线智能电话的标记语言)。 清单 12. generate_wml.xsl,它将 start.xsl 改编成 WML
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="xsl:output">
<xsl:copy>
<xsl:attribute name="method">xml</xsl:attribute>
<xsl:attribute name="doctype-public">-//WAPFORUM//DTD WML 1.1//EN</xsl:attribute>
<xsl:attribute name="doctype-system">http://www.wapforum.org/DTD/wml_1.1.xml
</xsl:attribute>
</xsl:copy>
</xsl:template>
<xsl:template match="page">
<wml>
<card title="{@title}">
<xsl:apply-templates/>
</card>
</wml>
</xsl:template>
<xsl:template match="para">
<p><xsl:apply-templates/></p>
</xsl:template>
<xsl:template match="link">
<anchor><go href="{@href}"/><xsl:apply-templates/></anchor>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
|
本文寓意:如果寻求大型网站的最大自动化,根据 XML 文档自动生成 HTML 文档是不够的,还应该自动生成 XSLT 样式表。 |