使用 XSLT 作为 HTML 的样式表

从名称即可看出,可扩展样式表转换语言 (Extensible Stylesheet Language Transformations, XSLT) 可用作一个样式表。像级联样式表 (CSS) 一样,XSLT 有助于将样式从内容中分离。可以使用 XSLT 简化和扩充 XHTML 文档,它减轻了 XHTML 文档的导航负担。在本文中,我们将学习如何使用 XSLT 作为样式表,这些样式表可在服务器上或每个目前常用的Internet浏览器中执行。其中一些示例展示了如何开始开发您自己的 XSLT 样式表。

Jürgen M. Regel, 高级软件工程师, TUI InfoTec GmbH

Jürgen M. Regel 是位于德国汉诺威的 TUI InfoTec GmbH 的 Architecture Management & Software Engineering 部门的一名高级软件工程师。他主要研究旅游行业中的企业应用程序集成 (EAI)。



2012 年 10 月 25 日

简介

当听到样式表这个词时,您可能会想到 CSS 样式表。XSLT 样式表通常用于 XML 转换,比如在 Web 服务之间映射数据。因为 XSLT 非常适合此用途,所以创建了顶层元素 <stylesheet><xsl:transform> 别名,虽然这很少使用。这种 XSLT 转换的输入结构与输出结构有很大的不同。最重要的是,命名空间的不同。

XSLT 样式表的输入结构与输出结构相似,但却更简单些。其中已经扩充了一些标记,但大部分标记只是原样复制到输出。输入和输出的命名空间是相同的 (HTML)。输入文档也可以包含样式表指令(比如创建脚注),这些指令属于另一个命名空间,不会传递到输出中。

常用缩略语

  • CSS:级联样式表
  • XHTML:可扩展超文本标记语言
  • XPath:XML 路径语言
  • XSLT:可扩展样式表语言转换

在本文中,我们将学习如何使用 XSLT 样式表扩充 XHTML 文档。文中的示例展示了如何使用指令,如何引用其他源文档的部分,以及如何使用链接在主文档中导航。此外,我们还探索了页面的解释和编译之间的区别。

CSS 样式表的局限性

XSLT 样式表不会阻止您使用其他技术,比如 JavaScript 或 CSS。CSS 适用于字体、加粗、颜色、间距等。它不适合将来自不同位置的信息组合在一起,比如脚注、模块或生成一个目录。这正是 XSLT 的用武之地,它补充而不是替代了 CSS。


XSLT 用途示例

实际上,您可以将 XSLT 代码集中在一个文件中。为了简单起见,本文中的每个示例均位于一个独立的 XSLT 文件中,除了一些必要的代码。清单 1 给出了必需的代码。

清单 1. 必需的代码(位于 samples/common.xml 中)
<s:stylesheet
	xmlns="http://www.w3.org/1999/xhtml"
	xmlns:h="http://www.w3.org/1999/xhtml"
	xmlns:a="http://sourceforge.net/projects/arbalo/"
	xmlns:s="http://www.w3.org/1999/XSL/Transform"
	exclude-result-prefixes="a h"
	version="1.0"
>
	<s:template match="h:head">
		<s:copy>
			<s:apply-templates select="@*|node()"/>
			<meta
				http-equiv="content-type"
				content="text/html;charset=UTF-8" />
			<link
				href="common.css"
				rel="stylesheet"
				type="text/css" />
		</s:copy>
	</s:template>
	
	<s:template match="*">
		<s:copy>
			<s:copy-of select="@*"/>
			<s:apply-templates/>
		</s:copy>
	</s:template>
</s:stylesheet>

XHTML 的命名空间定义了两次:默认定义和 h:。默认命名空间用于编写输出 XHTML 标记,其中应该避免使用命名空间前缀。h: 用在 XPath 表达式中。

本文使用 XSLT 1.0 版本。目前,大部分浏览器都无法解释 XSLT 2.0。但是,如果 XSLT 运行在服务器上,那么它可能是一个实用的选择。XSLT 2.0 还提供了:

  • XPATH 2.0(if…then…else 和许多内置的函数)
  • 内置和用户编写的 XPATH 函数
  • 分组

清单 1 中:

  • s:template match="head" 扩充了源文档的 head 一节,添加了一个 CSS 样式表的链接。即使 UTF-8 是在 XML 中的默认编码,一些浏览器也需要内容类型才能呈现它。
  • s:template match="*" 是默认的详细副本。原则上,所有内容都会复制到目标文档中。如果遗漏了此模板,只会将标记的文本内容复制到目标文档。不会复制处理指令节点。

本文中的所有其他示例都是导入 common.xsl 的独立文件。

扩充

通过扩充,添加了一个未在源文档中显式请求的特性。一个示例是 清单 1 中的 CSS 样式表的链接。尝试另一个示例,向每个内部链接添加一个小箭头 (^ v),指明目标在它之前还是之后。清单 2 给出了该样式表。

清单 2. 样式表(在 samples/linkUpDown.xsl 中)
<s:stylesheet
	xmlns="http://www.w3.org/1999/xhtml"
	xmlns:h="http://www.w3.org/1999/xhtml"
	xmlns:s="http://www.w3.org/1999/XSL/Transform"
	version="1.0"
>
	<s:import href="common.xsl"/>
	<s:template match="h:a[starts-with(@href,'#')]">
		<s:copy>
			<s:copy-of select="@*"/>
			<s:variable name="name" select="substring-after(@href,'#')"/>
			<s:choose>
				<s:when test="preceding::h:a[@name=$name]">
					<s:text>^</s:text>
				</s:when>
				<s:when test="following::h:a[@name=$name]">
					<s:text>v</s:text>
				</s:when>
			</s:choose>
			<s:apply-templates/>
		</s:copy>
	</s:template>
</s:stylesheet>

首先,导入清单 2 中的通用样式表。模板与内部链接(以 '#' 开头)相匹配。如果链接指向的锚点位于链接之前,那么使用一个向上箭头扩充该链接(如果情况相反,则使用向下箭头)。

s:copy-ofs:apply-templates 可确保不会沿途丢下任何内容。

清单 3 给出了一个示例文档(其中包含内部链接),它经过了清单 2 中的样式表进行扩充。

清单 3. 源文档(在 samples/linkUpDown.xml 中)
 <?xml-stylesheet href="linkUpDown.xsl" type="text/xsl"?>
 <html xmlns="http://www.w3.org/1999/xhtml">
	<head/>
	<body>
		<a name="a"/>
		<p>This link goes <a href="vb">downward.</a></p>
		<br/>
		<p>Reduce the size of the window to verify the link really works.</p>
		<br/>
		<a name="b"/>
		<p>This link goes <a href="^a">upward.</a>
		</p>
	</body>
</html>

目标文档看起来相同,除了清单 4 中的条目。

清单 4. 目标文档(在 samples/linkUpDown.html 中)
   … <a href="#b">v downwards.</a> …
   … <a href="#a">^ upwards.</a> …

指令

您可以在源文档中添加一些指令,告诉样式表执行何种操作。它们属于另一个命名空间(在本例中为前缀 a:),不会被复制到目标文档。

在清单 5 中,源文档中任何地方的指令标记 a:ref 都会创建一个脚注。

清单 5. 样式表(在 samples/footnote.xsl 中)
<s:stylesheet
	xmlns="http://www.w3.org/1999/xhtml"
	xmlns:a="http://sourceforge.net/projects/arbalo/"
	xmlns:h="http://www.w3.org/1999/xhtml"
	xmlns:s="http://www.w3.org/1999/XSL/Transform"
	version="1.0"
>
	<s:import href="common.xsl"/>
	<s:template match="h:body">
		<s:copy>
			<s:apply-templates select="@*|node()"/
			<!-- put the footnotes at the end
				if there is no a:references directive -->
			<s:if test="not(descendant::a:references)">
				<s:call-template name="references"/>
			</s:if>
		</s:copy>
	</s:template>
	<!-- Create a footnote -->
	<s:template match="a:ref">
		<s:variable
			name="number"
			select="count(preceding::a:ref) + 1"/>
		<a name="ref-{$number}"></a>
		<a class="footnote" href="#reference-{$number}">
			<s:value-of select="concat('v ',$number)"/>
		</a>
	</s:template>
	<!-- if a:reference is missing, assume it at the end of the body -->
	<s:template match="a:references" name="references">
		<hr/>
		<s:for-each select="//a:ref">
			<s:variable name="number" select="count(preceding::a:ref) + 1"/>
			<p>
				<a name="reference-{$number}"></a>
				<a class="footnote" href="#ref-{$number}">
					<s:value-of select="concat(' ^',$number)"/>
				</a>
				<s:apply-templates/>
			</p>
		</s:for-each>
	</s:template>
</s:stylesheet>

使用源文档中的 a:references 指令,名为 references 的模板会在模板与该指令匹配的地方分配脚注。如果缺少这样一个指令,第一个与 body 匹配的模板会在 body 的末尾分配脚注,方法是调用名为 references 的相同模板。在两种情况下,都会列出脚注的内容,并生成一个由向上箭头表示的向上链接。

第二个模板(匹配 a:ref)使用向下箭头创建脚注的链接。脚注具有编号。这里忽略了它的内容。

class="footnote" 属性在 XSLT 转换之后由一个 CSS 样式表解析,该样式表链接在 XSLT 样式表 common.xsl 中。

清单 6 中的源文档使用 a:ref 指令创建脚注。

清单 6. 源文档(在 samples/footnote.xml 中)
<?xml-stylesheet href="footnote.xsl" type="text/xsl"?>
<html
	xmlns="http://www.w3.org/1999/xhtml"
	xmlns:a="http://sourceforge.net/projects/arbalo/"
>
	<head/>
	<body>
		<p>
			This example looks a little scientific
			<a:ref>
				From Latin
				<em>scientia</em>
			</a:ref>
			and academic
			<a:ref>From Greek akademia</a:ref>.
		</p>
		<p>
			Do you know why?
			<a:ref>
				It uses
				<em>footnotes</em>.
			</a:ref>
		</p>
		<p>Reduce size of window to verify links are generated.</p>
		
	
	</body>
</html>

目标文档将脚注列表包含在底部,如清单 7 所示。

清单 7. 目标文档(在 samples/footnote.html 中)
<html
	xmlns="http://www.w3.org/1999/xhtml"
   xmlns:h="http://www.w3.org/1999/xhtml"
   xmlns:a="http://sourceforge.net/projects/arbalo/">
   <head><link type="text/css" rel="stylesheet" href="common.css"/></head>
   <body>
      <p>This example looks a little scientific
         <a name="ref-1"/><a href="#reference-1" class="footnote">v 1</a>
         and academic.
         <a name="ref-2"/><a href="#reference-2" class="footnote">v 2lt;/a>
      </p>
      <p>Do you know why?
         <a name="ref-3"/><a href="#reference-3" class="footnote">v 3</a>
      </p>
      <p>Reduce size of window to verify links are generated.</p>
      br/><br/>
   <hr/>
   <p><a name="reference-1"/><a href="#ref-1" class="footnote"> ^1</a>
      From Latin
      <em>scientia</em>
   </p>
   <p><a name="reference-2"/>
      <a href="#ref-2" class="footnote"> ^2</a>From Greek akademia</p>
   <p><a name="reference-3"/><a href="#ref-3" class="footnote"> ^3</a>
      It uses
      <em>footnotes</em>.
   </p>
   </body>
</html>

突破源文档的边界

也可引用其他源文档的其中一些部分。a:include 指令包含一个可能属于另一个源文档的元素并转换它,如清单 8 所示。

清单 8. 样式表(在 samples/include.xsl 中)
<s:stylesheet
	xmlns="http://www.w3.org/1999/xhtml"
	xmlns:a="http://sourceforge.net/projects/arbalo/"
	xmlns:s="http://www.w3.org/1999/XSL/Transform"
	version="1.0"
>
	<s:import href="common.xsl"/>
	<s:template	match="a:include">
		<s:choose>
			<s:when test="0!=string-length(@src)">
				<s:apply-templates
					select="document(@src)//*[@id=current()/@refid]"/>
			</s:when>
			<s:when test="not(@src) and //a:default[1]/@src">
				<s:apply-templates
select="document(//a:default[1]/@src)//*[@id=current()/@refid]"/>
			</s:when>
			<s:when test="0=string-length(@src) or not(//a:default[1]/@src)">
				<s:apply-templates
					select="//*[@id=current()/@refid]"/>
			</s:when>
		</s:choose>
	</s:template>
</s:stylesheet>

源文档中的一个 a:include 指令引用源元素的 id。包含该元素的文档可在一个 src 属性中命名。如果缺少该属性,将使用 a:default 指令的 src 属性。如果在任何地方都没有 src 属性,则使用同一个源文档。因此,refid 会引用 id 来避免无限的递归。

导入的元素可能具有一种复杂的类型,并在包含 (apply-templates)之后进行转换。清单 9、清单 10 和清单 11 给出了示例。

清单 9. 源文档(在 samples/include.xml 中)
<?xml-stylesheet href="include.xsl" type="text/xsl"?>
<html
	xmlns="http://www.w3.org/1999/xhtml"
	xmlns:a="http://sourceforge.net/projects/arbalo/">
	<head>
		<a:default src="includedY.xml"/>
	</head>
	<body>
		<p>The following text is included:</p>
		<a:include refid="x" src="includedX.xml"/>
		<a:include refid="y1"/>
		<p id="i">double</p>
		<a:include refid="y2"/>
		<a:include refid="i" src=""/>
	</body>
</html>
清单 10. 源文档的部分(在 samples/includeY.xml 中)
	<h2 id="y2">I'm the <em>included</em> h2</h2>
	<h1 id="y1">I'm the <em>included</em> h1</h1>
清单 11. 目标文档(在 samples/include.html 中)
	<body>
		<p>The following text is included:</p>
		<p id="x">I'm the <em>included</em> paragraph.</p>
		<h1 id="y1">I'm the <em>included</em> h1</h1>
		<p id="i">double</p>
		<h2 id="y2">I'm the <em>included</em> h2</h2>
		<p id="i">double</p>
	</body>
</html>

主文档和导航

如果您有一个包含多个页面的演示,有一个主文档包含页面标题及其链接。您可以生成完整的导航,从每个页面到任何其他页面,以及到前一个和后一个页面。这些细节不属于本文的介绍范围,但 参考资料 中提供了使用主文档的 HTML 演示的链接。可将 .xml 替换为 .html 来获得编译后的版本。让浏览器向您显示 .xml 的整洁源代码。您会对它生成的源代码量感到惊奇。


解释与编译的对比

解释意味着页面为 XML 格式(其文件扩展名为 .xml,其内容类型为文本/xml 或应用程序/xml),并且处理指令所引用的 XSLT 样式表可在浏览器中执行。

编译意味着浏览器看到的是 HTML(其文件扩展名为 .html,内容类型为文本/html),它是在请求页面之前从您的开发环境中或服务器上的 XML 转换而来的。Xalan 和 Saxon 都是著名的 XSLT 处理器。

解释是未来的发展方向。所有现代浏览器都支持 XSLT,并且它具有一些优点:

  • 当测试时,您会立即获得结果。只需在您测试的每个浏览器中按下 F5,即可反映源页面、CSS 和 XSLT 样式表的更改。
  • 要传递给客户端的信息量减少了。
  • 客户端看到的是一个干净、整洁的网页,因为还未生成扩充内容。

但也要注意一些缺点:

  • 有一些旧浏览器可能不支持 XSLT。如果向一个受控的环境(内部网)发布页面,就不会出现问题。
  • 一些现代浏览器禁止 XSLT 样式表引用另一个目录中的另一个样式表。
  • 将 XSLT 域其他功能相结合(比如 SVG 或 iframe)可能在一些浏览器中导致问题。
  • 因为大部分浏览器都不支持 XSLT 2.0 或即将推出的 3.0,所以您无法使用新功能。没有 XPath 2.0 if () then … else,也没有用户编写的 XPath 功能。

无论进行编译还是解释,页面的其他转换 (CSS、JavaScript) 都会在 XSLT 转换之后执行。


结束语

在本文中,您学习了如何使用 XSLT 样式表来扩充 XHTML 文档。您可以使用本文中的示例作为起点,构建您自己的 XSLT 样式表。


下载

描述名字大小
文章和 XML 示例1xsl-stylesheets.zip29KB

注意:

  1. 下载文件包含:

    - 一个使用 XSLT 样式表的历史演示示例(德语版本,但提供了 XSLT 框架生成的任何其他语言的翻译版本的链接):http://zocher-regel.gmxhome.de/ArbaloSchlacht/11Drusus.xml

    - 同一个文件,但编译为 XHTML:http://zocher-regel.gmxhome.de/ArbaloSchlacht/11Drusus.html

    - 本文中使用的 XSLT 框架的描述,使用它所描述的框架:http://home.arcor.de/juergen.regel/Arbalo/index.xml

    - W3C XML 路径语言 (XPath):http://www.w3.org/TR/xpath/

    - W3C XSL 转换 (XSLT):http://www.w3.org/TR/xslt


参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development, XML
ArticleID=842865
ArticleTitle=使用 XSLT 作为 HTML 的样式表
publish-date=10252012