UML软件工程组织

 

 

准备 XML 及相关技术认证考试,第 4 部分: XML 转换
 
作者:Mark Lorenz  来源:IBM
 

XML 转换

转换 XML 通常涉及到下列技术:

  • XSLT 1.0,将 XML 文档转换为其他形式
  • XPath 1.0,搜索遍历 XML 文档,并提供了数学和字符串操作
  • CSS,格式化 XML 文档

XML 实例文档

本教程继续使用第 3 部分中的 DVD 目录示例 XML 文档(请参阅参考资料)。为了方便起见,清单 1 重新列出了该文档。本教程中编写的大部分转换都是针对这个文档的。

清单 1. DVD 目录 XML 实例文档

<?xml version="1.0"?>
<catalog>
<dvd code="_1234567">
<title>Terminator 2</title>
<description>A shape-shifting cyborg is sent back
from the future to kill the leader of the
resistance.</description>
<price>19.95</price>
<year>1991<year>
</dvd>
<dvd code="_7654321">
<title>The Matrix</title>
<price>12.95<price>
<year>1999<year>
</dvd>
<dvd code="_2255577" genre="Drama">
<title>Life as a House</title>
<description>When a man is diagnosed with terminal
cancer, he takes custody of his misanthropic
teenage son.</description>
<price>15.95</price>
<year>2001</year>
</dvd>
<dvd code="_7755522" genre="Action">
<title>Raiders of the Lost Ark</title>
<price>14.95</price>
<year>1981</year>
</dvd>
</catalog>

后面还有一个关于网站地图的 XML 实例文档。

 XSLT

奇妙的是我们可以看到这些树,不再感到无所适从。 -- Ralph Waldo Emerson

有时候我们说 XML 不是一种编程语言。如果不包括 XSLT 这样说就完全 对了 —— XSLT 本身是 XML,并且已经证明是图灵完整的。这就是说,您可以使用 XSLT 执行现代计算机能够完成的任何计算。

XSLT 本质上是一种声明性系统,它声明了在 XML 文档中遇到特定的元素类型时会发生什么。XSLT 不是编译的,相反,它以及 XML 输入文档一起由样式表处理程序解释,比如 Xalan 或 Microsoft XML Core Services (MSXML)。可以把它的用法看作一个数学公式:XSLT( XML ) = 输出。

由于 XSLT 的名称中有样式表 这个词,编程社区中有人就认为 XSLT 不具备编程语言那样的强大功能,仅仅是某种类似级联样式表(CSS)的东西。并不是看不起 CSS,不过啧啧……虽然不像其他多数语言那样简洁——很大程度上是因为必须保证结构良好性——XSLT(与 XPath 相结合,后者提供了搜索和遍历 XML 树结构以及执行字符串和数学操作的手段)也能提供丰富的功能。后面讨论递归的时候就会看到也可以写出非常优美的代码。

下面的章节将说明如何采用 XSLT 和 XPath 从 XML 文档中检索数据。多数例子中,数据最终被格式化为 HTML。

Web 浏览器内部的转换

虽然下面的示例代码都假定转换在服务器上或者 XMLSpy 之类的环境下执行,不过这些转换只要稍加修改或者不做修改也能在支持 XSLT 的最新版本的浏览器中完成,比如 Mozilla Firefox 和 Microsoft Internet Explorer。在这些浏览器中查看 XML 文档需要类似下面的指令,这条指令应该放在 XML 输入文档的文档序言中,<?xml version="1.0"?> 标签的下方。将 href 属性改为适当的值,可以是绝对或相对 URL:

<?xml-stylesheet type="application/xml" href="http://www.ibm/com/xslt/foo.xslt"?>

根元素

任何 XSL 转换的根元素或顶层元素——所有其他子节点都嵌套在其中,都是 xsl:stylesheet 或 xsl:transform 元素。可以使用其中的任何一个,它们的意义一样。可以写为:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

或者:

<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

然后应该在文档的最后分别使用 </xsl:stylesheet 或 </xsl:transform> 关闭这些标签。因此,本教程中交换使用样式表 和转换 两个词。

模板元素

xsl:template 元素包含一组规则,可应用于 XML 输入文档中的特定元素。每个 xsl:stylesheet 或 xsl:transform 必须至少包含一个 xsl:template 元素。XSLT 编程的丰富性主要源于可使用分别服务于不同目的的多个模板作为逻辑模块。可以通过 match 属性来触发模板元素或者通过 name 属性直接调用。

match 属性的用法很简单。遇到 match 属性值所规定的范式时即执行该规则。比方说,可使用下列规则确定文档中包含字符串“Another DVD”的每个 dvd 元素:

<xsl:template match="dvd">Another DVD</xsl:template>

转换的输出结果如下:

<?xml version="1.0" encoding="UTF-8"?>

<p>Another DVD</p>
<p>Another DVD</p>
<p>Another DVD</p>
<p>Another DVD</p>

要注意,对输入文档中每个 dvd 元素都会执行一次模板规则。每次在输入文档中遇到 dvd 元素时都会调用该规则。

xsl:apply-templates 和 xsl:value-of

如果能建立与除 dvd 之外的其他元素对应的模板规则将非常有用。事实上,也能建立与其他元素匹配的模板:

<xsl:template match="price"><xsl:value-of select="."/></xsl:template>
<xsl:template match="title"><xsl:value-of select="."/></xsl:template>

无论哪种情况,<xsl:value-of/> 标签的 select 属性值都是一种范式,它给出了当前分析元素或者上下文节点 的文本值。上面 select 属性值中“.”所标记的 XPath 表达式是反身轴。 模板标签中的 match 属性值也是一个 XPath 表达式。这里的上下文节点由模板匹配确定。设置上下文节点的其他方式包括使用 xsl:apply-templates 和 xsl:for-each。

上面两个模板不仅检索 title 和 price 的值,XML 输入文档中的其他内容也会显示。如果要仅仅显示 title 和 price 元素的值,还需要增加一条模板规则:

<xsl:template match="dvd">
<p>
<xsl:apply-templates select="title"/>- $<xsl:apply-templates select="price"/>
</p>
</xsl:template>

这里还增加了一点 HTML 格式化成分(<p/> 标签)。输出结果如下:

<?xml version="1.0" encoding="UTF-8"?>

<p>Terminator 2 - $19.95</p>
<p>The Matrix - $12.95</p>
<p>Life as a House - $15.95</p>
<p>Raiders of the Lost Ark - $14.95</p>

调用模板

除上述方法以外,还可以明确调用 显示 dvd 的子元素 title 和 price 的模板,这种方法带来了新的可能性。这种情况下,完整的转换如下所示:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="dvd">
<p>
<xsl:call-template name="Title"/>
<xsl:call-template name="Price"/>
</p>
</xsl:template>
<xsl:template name="Title"><xsl:value-of select="title"/> - </xsl:template>
<xsl:template name="Price">$<xsl:value-of select="price"/></xsl:template>
</xsl:stylesheet>

为了增加可读性,标题中的连字符和价格中的美元符号移到了命名模板 Title 和 Price 之中。下面的输出结果与上一个转换完全相同:

<?xml version="1.0" encoding="UTF-8"?>

<p>Terminator 2 - $19.95</p>
<p>The Matrix - $12.95</p>
<p>Life as a House - $15.95</p>
<p>Raiders of the Lost Ark - $14.95</p>

xsl:call-template 标签的 name 属性引用 name 属性值与其相同的模板,不论是“Title”还是“Price”。通过 name 属性而不是 match 属性调用的模板称为命名模板。通过命名模板可以实现代码的模块化。后面将看到,结合使用命名模板和 <xsl:import/> 或 <xsl:include/>(用于添加外部文件)可以根据需要把模板换进换出。XSLT 中命名模板的另一个重要用途是实现递归编程。

迭代

继续使用 catalog.xml 作为例子输入文档,我们看如何对一组元素重复使用一个模板规则。这种情况下,上下文节点是根元素 catalog。不用上述通过匹配元素来激活模板规则应用的方法,可以通过 <xsl:for-each/> 标签使用迭代。

首先,我们来看看如何使用 xsl:for-each 循环遍历目录中的每个 dvd 元素:

<xsl:template match="catalog">
<html>
<body>
<xsl:for-each select="dvd">
<xsl:call-template name="DVD"/>
<xsl:for-each>
<body>
</html>
</xsl:template>

注意,为了便于在浏览器中显示,其中增加了一些 HTML。观察 xsl:for-each 标签的内容,可以看到调用了一个命名模板 DVD。DVD 模板可能包含下面的显示逻辑:

<xsl:template name="DVD">
<xsl:variable name="label" select="@code"/>
<p>
<img src="images/{$label}.gif" alt=""/>
<xsl:value-of select="title"/>
</p>
<xsl:template>

上面的例子引出了几个新的概念。首先,有一个名为 label 的 xsl:variable。要注意,该变量取得了 dvd 元素的 code 属性值,使用了前缀“@”。此外,还应注意 xsl:for-each 按照顺序将上下文节点转移到每个 dvd 元素。这一点可由上面的 DVD 模板看出来,因为能够直接引用 @code 属性和 title 元素值,这两个都是 dvd 元素的孩子。这些相对 XPath 表达式表明,xsl:for-each 将上下文节点转移到了每个 dvd 元素。

其次,新建的 label 变量用于创建 <img/> 标签,灵活地设置新的 HTML 格式。(这里假设每张 DVD 在 images/ 目录中都有对应的图片。每幅图片都是用 DVD 的 code 属性值作为自己的文件名。)要注意为了得到变量的值在其前面加上了美元符号(“$”),并且和 xsl:value-of 元素类似用花括号(“{”和“}”)包围起来。(当然,该例中根本不需要创建变量,可以直接在 img 标签中引用 @code 属性的值:<img src="images/{@code}.gif" alt=""/>。)

将这些结合在一起:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="catalog">
<html>
<body>
<xsl:for-each select="dvd">
<xsl:call-template name="DVD"/>
</xsl:for-each>
</body>
</html>
</xsl:template>
<xsl:template name="DVD">
<xsl:variable name="label" select="@code"/>
<p>
<img src="images/{$label}.gif" alt=""/>
<xsl:value-of select="title"/>
</p>
<xsl:template>
<xsl:stylesheet>

上述转换得到的结果如下:

<html>
<body>
<p>
<img alt="" src="images/_1234567.gif">Terminator 2</p>
<p>
<img alt="" src="images/_7654321.gif">The Matrix</p>
<p>
<img alt="" src="images/_2255577.gif">Life as a House</p>
<p>
<img alt="" src="images/_7755522.gif">Raiders of the Lost Ark</p>
</body>
</html>

格式化输出

您可能注意到,上面的输出前面没有了 <?xml?> 标签。有两个原因,任何一个都会导致输出作为 HTML 处理。首先,紧跟在 <xsl:stylesheet> 开始标签之后增加了 <xsl:output method="html"/>。xsl:output 的 method 属性通常取值为 html 或 xml。xsl:output 标签另外一个重要的标签是 encoding,它定义了输出的字符集,在 method 属性值为 xml 时使用。<?xml?> 标签消失的另一个原因是多数样式表处理程序都注意匹配模板中是否使用了 HTML 标签,如 <html/> 和 <body/>。如果有的话,则把输出 method 自动作为 HTML 处理。

排序结果

xsl:for-each 有一个有用附件 xsl:sort。(也可用于 xsl:apply-templates。)可以使用该标签作为 xsl:for-each 的第一个元素,根据某个键使结果按照文档顺序 之外的其他顺序排列。比方说,上例中的 xsl:for-each 可使用 xsl:sort 按照 title 属性值来排列 DVD 列表:

<xsl:for-each select="dvd">
<xsl:sort select="title"/>
<xsl:call-template name="DVD"/>
</xsl:for-each>

将上面粗体显示的文本 xsl:sort 插入到前例中,可得到如下结果:

<html>
<body>
<p>
<img alt="" src="images/_2255577.gif">Life as a House</p>
<p>
<img alt="" src="images/_7755522.gif">Raiders of the Lost Ark</p>
<p>
<img alt="" src="images/_1234567.gif">Terminator 2</p>
<p>
<img alt="" src="images/_7654321.gif">The Matrix</p>
</body>
</html>

可以看到 DVD 现在按照标题的字母顺序排列。在第一个之后还可以加上其他 xsl:sort 元素来对结果集进一步排序。

递归

通过递归实现前面的 DVD 列表转换看起来有点怪,因为:

上述使用 xsl:for-each 的迭代解决方案,对于熟悉 Java? 这类命令式 编程语言的程序员来说更直观。
这个特殊的例子更适合简单的 for-loop,因为只需要遍历一组兄弟节点。
但是,在元素可能嵌套任意多层的情况下,递归更适合这样的任务。比如,文件系统以及后面嵌在网站中的主题页面都是很好的例子。为了便于比较,下面的代码显示了上例的递归解决方案,没有排序:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="catalog">
<html>
<body>
<xsl:call-template name="DVD">
<xsl:with-param name="dvdCount" select="count(dvd)"/>
</xsl:call-template>
</body>
</html>
</xsl:template>
<xsl:template name="DVD">
<xsl:param name="position" select="1"/>
<xsl:param name="dvdCount"/>
<xsl:variable name="label" select="dvd[$position]/@code"/>
<p>
<img src="images/{$label}.gif" alt=""/>
<xsl:value-of select="dvd[$position]/title"/>
</p>
<xsl:if test="$position < $dvdCount">
<xsl:call-template name="DVD">
<xsl:with-param name="position" select="$position+1"/>
<xsl:with-param name="dvdCount" select="$dvdCount"/>
</xsl:call-template>
</xsl:if>
<xsl:template>
</xsl:stylesheet>

该转换和迭代解决方案的结果相同:

<html>
<body>
<p>
<img alt="" src="images/_2255577.gif">Life as a House</p>
<p>
<img alt="" src="images/_7755522.gif">Raiders of the Lost Ark</p>
<p>
<img alt="" src="images/_1234567.gif">Terminator 2</p>
<p>
<img alt="" src="images/_7654321.gif">The Matrix</p>
<body>
</html>

这里,匹配模板中在转换的开始通过调用 DVD 模板开始递归。然后 DVD 模板再调用自身,直到处理到最后一个 dvd 元素。

这个例子没有多少值得注意的地方。只有一点,上下文节点没有转移到任何一个 dvd 元素上。这是因为 xsl:call-template 没有像 xsl:for-each 那样改变上下文节点。因此,DVD 模板中定义的变量必须在 xsl:param、position 的帮助下引用当前 dvd 元素,后者保存了当前处理的 dvd 元素的位置。参数可以通过 <xsl:with-param/> 标签从模板传递给它调用的命名模板。<xsl:with-param> 只能作为 <xsl:call-template/> 标签的子元素。被调用的模板通过 <xsl:param/> 标签得到这些参数,后者必须紧跟在 <xsl:template/> 起始标签之后。

您可能注意到,DVD 模板中的 xsl:param、position 带有通过 select 属性提供的默认值。因此,匹配模板中第一次调用 DVD 模板的时候不一定要使用对应的 xsl:with-param。使用 xsl:param 标签的默认值是一种好习惯,可以避免在被调用模板中发生意料之外的行为。(如果不是为了比较两个参数,dvdCount 也应该有默认值。)

此外要注意,在 DVD 模板最后的 xsl:call-template 中,将 position 参数传递给下一个 DVD 模板的实例之前增加了一。在 <xsl:if/> 的 test 属性中可以看到,这两个参数的值用在了递归停止条件中。

在 DVD 模板中还使用 position 参数表明当前处理的是哪一个 dvd 元素。这是通过 xsl:variable 和 xsl:value-of 标签 select 属性中用于限定 dvd 的谓词 实现的。两者都是 XPath 表达式,本教程后面还将进一步讨论。XPath 谓词放在方括号(“[”和“]”)之间,紧跟在所限定的元素之后。该例中,position 参数在谓词中用于表示当前所处理的 dvd 元素的位置:dvd[$position]。

<xsl:if/> 元素的用法很简单。它有一个条件,通过计算必需的 test 属性得到,结果必须是一个 Boolean 值(true 或 false)。如果想知道,我可以告诉您,没有 <xsl:else/> 或 <xsl:elseif/> 标签。但是可以使用 <xsl:choose/>,后面还要讲到。最后,要注意 <xsl:if/> test 属性中的小于符号是 XML 转义的(&lt;>)。如果使用非转义的小于号(<),样式表处理程序将抛出异常,因为这个字符以及右尖括号或大于号(>)用于表示 XML 元素的开始和结束。

网站地图中的递归

现在看一个更适合采用递归方法的例子。清单 2 中的 XML 示例文档显示了一个虚构网站的地图。我们需要构造一个转换来呈现站点地图,从最上层的页面开始递归地列出所有子页面,以及子页面的子页面——无论多少层。转换的结果是一个 HTML 文档。

清单 2. 网站地图 XML 实例文档

<?xml version="1.0"?>
<site>
<page label="A" href="0.html">
<page label="AA" href="0_0.html">
<page label="AAA" href="0_0_0.html">
<page label="AAAA" href="0_0_0_0.html"/>
<page label="AAAB" href="0_0_0_1.html"/>
<page label="AAAC" href="0_0_0_2.html"/>
</page>
<page label="AAB" href="0_0_1.html"/>
<page label="AAC" href="0_0_2.html"/>
</page>
<page label="AB" href="0_1.html"/>
<page label="AC" href="0_2.html"/>
</page>
<page label="B" href="1.html"/>
<page label="C" href="2.html"/>
</site>

清单 2 中的示例站点地图有 4 层深,并暗示 HTML 页面是按主题组织的。最上层的三个页面,label 属性值分别为 A、B 和 C。页 A 有三个子页面(AA、AB 和 AC),它的第一个子页面又有三个子页面(AAA、AAB 和 AAC)。最后,页面 AAA 也有自己的子页面(AAAA 到 AAAC)。这棵树需要呈现为 HTML。

为了显示站点地图,我们首先建立一组嵌套的 HTML 无序列表,分别对应每个导航层级。创建这个列表,需要从最上层开始,每次迭代遍历一个导航层。如果发现某个页面有子页面,则将其子页面的列表添加到当前页面之后,然后继续循环遍历当前导航层次。算法的伪代码如下:

Build List;

Build List {
For each page {
Write page;
If (page has child pages)
Build List;
}
}

该算法使用很少的代码(至少对 XSLT 来说)就能处理任意多层深的导航树。所需 XSLT 代码如此简洁表明它非常适合处理此类任务。下面给出了按照上述逻辑编写的转换,增加了一点 HTML 语法。其中的注释说明了算法的思想:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="site">
<html>
<body>
<!-- Make the initial template call -->
<xsl:call-template name="BuildList"/>
</body>
</html>
</xsl:template>
<xsl:template name="BuildList">
<ul>
<xsl:for-each select="page">
<!-- Write the current page -->
<li>
<a href="{@href}">
<xsl:value-of select="@label"/>
</a>
</li>
<!-- If the page has child pages, -->
<xsl:if test="count(page) > 0">
<!-- Build that list of pages, starting the process again... -->
<xsl:call-template name="BuildList"/>
</xsl:if>
<xsl:for-each>
</ul>
</xsl:template>
</xsl:stylesheet>

要注意前面的转换是尾递归的。同样,如果当前页面没有子页面,则满足递归的停止条件,于是就不再递归调用 BuildList 模板了。该转换的 HTML 输出如下:

<html>
<body>
<ul>
<li><a href="0.html">A</a></li>
<ul>
<li><a href="0_0.html">AA</a></li>
<ul>
<li><a href="0_0_0.html">AAA</a><li>
<ul>
<li><a href="0_0_0_0.html">AAAA</a><li>
<li><a href="0_0_0_1.html">AAAB</a><li>
<li><a href="0_0_0_2.html">AAAC</a></li>
</ul>
<li><a href="0_0_1.html">AAB<a></li>
<li><a href="0_0_2.html">AAC</a><li>
<ul>
<li><a href="0_1.html">AB</a></li>
<li><a href="0_2.html">AC</a><li>
</ul>
<li><a href="1.html">B<a></li>
<li><a href="2.html">C</a><li>
<ul>
</body>
</html>

把链接展开后,HTML 输出在浏览器中的呈现结果如下:

  • A
    • AA
      • AAA
        • AAAA
        • AAAB
        • AAAC
      • AAB
      • AAC
    • AB
    • AC
  • B
  • C

站点导航模块的递归

可以增加比较当前页面(上下文节点)和网站地图中所有元素的逻辑,从而把上述转换做成一个树导航模块。这是因为网站中的每个页面都需要根据自身在树中的位置确定自己的导航模块版本。还需要一些 JavaScript 来控制兄弟页面集合的展开与折叠。完成后呈现的模块看起来类似于 Microsoft Windows Explorer 和 Eclipse Navigator 视图中树控件。

开始呈现的时候,代表顶层页面之外的所有其他页面的 HTML 元素样式属性 display 都设置为 "none"(详见后面的 CSS)。然后使用浏览器上的 JavaScript 逻辑展开代表上下文节点所有祖先页面的 HTML 标签,即将其样式属性 display 的值设为“block”。为保证这种行为对每个页面是不同的,可以假设每个页面元素的 @href 属性都是惟一的。然后使用 xsl:variable 保存上下文节点 @href 属性的值。然后,当每个 page 元素通过每一导航层的迭代接收到上下文时,就可以比较其 @href 属性和保存有正在呈现的页面 @href 的 xsl:variable。这样,当前页面及其上所有页面的链接都会显示出来。

如果有的话,它也非常适合显示当前页面的子页面。可用上述 HTML 无序列表元素或者 HTML <div/> 标签来保存每个导航层。这些元素要求惟一的 id 属性值,以便能够通过编程使其样式属性在 display:none 和 display:block 之间切换。如果使用 <div/> 标签,则需要大于零的 padding-left 或 margin-left 样式值来提供缩进。这些实现留给读者作为练习。为了美观增加几幅图片之后,转换结果可能看起来类似图 1 中所示的树导航控件。该图是递归 XSLT 转换生成 HTML 后的呈现结果,实现了前面给出的大部分提示。不过我没有使用嵌套元素,而是通过两个属性用指向父节点的指针来表示兄弟元素。图 1 中显示的输出是在 Internet Explore 6.0 中的呈现结果。

图 1. 呈现的导航模块
 

在显示的 HTML 中(不是图 1 所示的图片),单击图 1 中看到的加号和减号可以折叠和展开相应的子树,单击节点文本则导航到对应的页面。(为了显示整棵树,图 1 中的节点全部展开了。)目标页面加载到浏览器中以后,最初所有的节点都是折叠的,因此只能看到最上层的页面。然后,从当前页面开始向上(实际上是向左)展开树直到顶层,显示出当前页面的所有祖先页面。如果有子页面,从当前页展开树也将同时显示其子页面。

由于 XML 文档的嵌套特性,再加上 xsl:variable 元素是不可变的,可以发现对 XSLT 的大多数问题按照递归的方法思考和编写代码大有裨益。一般来说,采用递归方法的关键在于设计的模板,像 BuildList 模板那样足够一般化,能用于所要解决问题的任何情况。只要做到这一点,就可以在需要的时候调用自身。

条件逻辑

再回到 DVD 目录的例子,假设需要根据价格编写关于库存 DVD 的报表。但目的不是显示价格,而是需要按照价格对 DVD 分类。为此需要用到 xsl:choose。

我们来修改上面使用的迭代解决方案,通过调用前面编写的 Price 模板来显示每个 dvd 元素:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="catalog">
<html>
<body>
<xsl:for-each select="dvd">
<xsl:call-template name="DVD"/>
</xsl:for-each>
</body>
</html>
</xsl:template>
<xsl:template name="DVD">
<p>
<xsl:value-of select="title"/>
<xsl:call-template name="Price"/>
</p>
</xsl:template>
<xsl:template name="Price">$<xsl:value-of select="price"/><xsl:template>
</xsl:stylesheet>

现在修改 Price 模板,让它显示三个形容词(“贵!”、“便宜!”和“一般般”)而不是实际价格:

<xsl:template name="Price">
<xsl:choose>
<xsl:when test="price < 15.00"> - Cheap!</xsl:when>
<xsl:when test="price > 19.00"> - Pricey!</xsl:when>
<xsl:otherwise> - So so</xsl:otherwise>
</xsl:choose>
</xsl:template>

xsl:choose 有两个子元素:一个是必需的,即 xsl:when,与 xsl:if 很相似;另一个是可选的 xsl:otherwise。这个使用 xsl:choose 的转换输出结果如下:

<html>
<body>
<p>Terminator 2 - Pricey!</p>
<p>The Matrix - Cheap!</p>
<p>Life as a House - So so<p>
<p>Raiders of the Lost Ark - Cheap!</p>
</body>
</html>

与大多数 XSLT 编写者一样,您可能不会感到很兴奋,因为没有 xsl:else 和 xsl:else-if 元素,而 xsl:choose 又这么罗嗦。XSLT 2.0 提供了一种解决方法,但我不准备在本教程中讨论。XML in a Nutshell, 3rd Edition(请参阅参考资料)有很多章节专门讨论XSLT 和 XPath 1.0 与 2.0 版的区别。

导入和包含其他文件中的模板

xsl:import 与 xsl:include 这两个元素可以在转换中插入其他文件中的模板。这两个元素只能作为 <xsl:stylesheet/> 或 <xsl:transform/> 元素的孩子出现,而且必须出现在任何其他顶层元素之前。语法如下:

<xsl:import href="URI"/>

<xsl:include href="URI"/>

href 属性的 URI 值指向包含所要添加的转换的文件的路径和名称。URI 值可以是相对路径也可以是绝对路径。这两个元素的区别是,通过 xsl:import 引入的模版允许出现名称冲突,如果发生这种情况则忽略导入的模板。而属于由 xsl:include 引入的转换的命名模板,不能与导入它的转换中的任何命名模板冲突。这些文件在 xsl:include 出现的位置直接复制到当前转换中。无论哪个元素,被导入和导入文件之间都不允许存在循环引用。和 XML 中的多数事物一样,允许通过 xsl:import 和 xsl:include 嵌套文件。

XSLT 小结

XSLT 是一种功能全面的编程语言,而且奇妙的是它恰恰也是结构良好的。在下一节中将看到,XSLT 与 XPath 结合起来可以解决多数计算和格式化问题。掌握 XSLT 的窍门是学会使用嵌套的命名模板调用和熟悉递归的思想。

XPath

藏在苹果心中的一粒种子是一片看不见的果园。 -- Welsh proverb

上一节中,您已经看到了如何使用 XPath 引用 XML 示例文档中的元素。比方说,为了访问树中的元素,我们使用了 XPath 表达式如 /(文档根)、page(需要的子元素类型)以及 ..(上下文节点的父元素)。后面我们将看到,使用适当的 XPath 表达式组合能够访问 XML 文档中的任何部分。

XPath 还提供了集合、字符串和数学函数。虽然从简洁性和功能上来说不一定比得上其他多数脚本语言,如 Perl 或 JavaScript,但多数情况下,通过构造适当的 XSLT 模板,这些函数再加上合适的逻辑能够完成用其他语言所能完成的任何工作。这一节主要是解释 XSLT 一节中所看到的 XPath 表达式,然后阐述几个概念。

XPath 中的一个关键概念是轴。XPath 中的轴 是位于上下文节点上方(如 parent 和 ancestor 元素)、下方(child 和 descendant 元素)、左侧(preceding 和 preceding-sibling 元素)或右侧(following 和 following-sibling 元素)的一组节点。preceding 和 following 轴分别指向那些按照文档顺序出现在上下文节点之前和之后的元素,而 preceding-sibling 和 following-sibling 指向和上下文节点具有相同父元素的元素。self 轴表示上下文节点自身,可与另外两个轴结合起来组成 ancestor-or-self 和 descendant-or-self。除此以外,再加上 attribute 和 namespace 轴就可以保证能够访问到 XML 文档的任何部分了。

简写和非简写语法

一些 XPath 轴可以但不一定表示成简写形式,有点类似于 UNIX? 文件系统的 shell 语法。其中包括孩子(用星号(*)或更具体的子元素类型表示)、双亲(..)、自身(.)、属性(@")和 descendant-or-self 轴(//)。其他轴只能使用非简写 形式的语法。在后面的一些例子中可以看到这种语法。

位置步和位置路径

位置步 是位置路径 的基础。位置步的分隔符是斜杠(/)。位置路径由一系列上述轴的实例串在一起组成。比方说,从上下文节点开始,位置路径可以向上两层,发现由谓词指定的某个元素,然后沿子树向下寻找某个元素并返回其属性:

../../page[starts-with(@label, 'A')]//page[count(ancestor::page) = 3]/@href

XPath 函数

可以使用 XPath 提供的函数完成字符串和数学运算。比方说,如果要将两个 xsl:variable 元素 x 和 y 的值相加,可以使用:

<xsl:value-of select="$x + $y"/>

返回一个数字,这样的 XPath 函数还有很多。XPath 提供了下列数学运算的函数:加(+)、减(-)、乘(*)、除(div)以及模或者求余(mod)。

XPath 函数也可以返回 Boolean 值(true 或 false)。这是条件逻辑所必需的:

<xsl:if test="$x > $y"> ... </xsl:if>

XPath 函数还可以返回字符串:

<xsl:value-of select="concat($x, ' - ', $y)"/>

这里,concat() 函数可以接受大于 1 的任意多个参数。另一个常用的字符串函数是 normalize-space(string),它去掉前导空格和尾部空格,并把中间的空白字符序列压缩为一个。下表列出了其他与字符串有关的 XPath 函数:

  • starts-with(string, string):如果第一个参数以第二个参数开始则返回 true,否则返回 false。前面的例子说明了其用法。
  • contains(string, string):如果第一个参数包含第二个参数则返回 true,否则返回 false。
  • substring-before(string, string):返回第一个参数中第二个参数出现之前的子字符串。
  • substring-after(string, string):返回第一个参数中第二个参数出现之后的子字符串。
  • substring(string, number, number?):返回第一个参数的子字符串,第二个参数说明起始位置,第三个参数则给出了子字符串的长度。要注意,该函数中字符的起始位置是 1 而不是 0。
  • string-length(string?):返回一个数字表示可选参数的长度。如果没有传递参数则使用上下文节点的值。
  • translate(string, string, string):按照第三个参数中指定的字符映射用第二个参数替换第一个参数中的字符,然后返回第一个参数。

XPath 函数还可以返回节点集。比如在 sitemap.xml 这个例子中,可以这样得到带有子页面的页面个数:

<xsl:value-of select="count(//page[page])"/>

这里外层的函数返回一个数字,而其内部返回的是一个节点集。XPath count() 函数返回的数字是 3;包括 lable 值为 A、AA 和 AAA 的三个 page 元素,它们都有子 page 元素。请注意,上面的代码使用了 descendant 轴的简写语法(//)。如果使用 // 的时候前面没有任何内容则从根元素开始进行递归搜索;在大型文档中这种搜索可能很费时,因此用的时候要小心。在方括号([ 和 ])中是作用于这些 page 元素的谓词,要求它们必须有子 page 元素。这个谓词本身的内容是一个 XPath 表达式,即子轴上的“page”。此外,还可以把这种逻辑扩展成下面的表达式,即子页面还有子页面的页面个数,其中嵌套了另一个谓词:

<xsl:value-of select="count(//page[page[page]])"/>

您可能已经猜到,结果是 2,即 page 元素 A 和 AA。

假设需要知道给定的上下文节点在其 XML 树中嵌套的深度(用站点地图那个例子的话来说就是导航的层次)。仍然可以使用 count() 函数,但是这一次要沿不同的轴:

<xsl:value-of select="count(ancestor::page)"/>

使用 ancestor 轴确定上下文节点的深度。下面的模板说明了其用法:

<xsl:template match="/">
<xsl:for-each select="//page">
Page: <xsl:value-of select="@label"/>
,level: <xsl:value-of select="count(ancestor::page)"/>
</xsl:for-each>
</xsl:template>

注意,xsl:for-each 是由其 select 属性得到的节点集驱动的。输出结果如下:

<?xml version="1.0" encoding="UTF-8"?>Page: A, level: 0
Page: AA, level: 1
Page: AAA, level: 2
Page: AAAA, level: 3
Page: AAAB, level: 3
Page: AAAC, level: 3
Page: AAB, level: 2
Page: AAC, level: 2
Page: AB, level: 1
Page: AC, level: 1
Page: B, level: 0
Page: C, level: 0

select="//page" 属性返回的节点集包括从根元素开始的所有 page 元素,按照文档顺序排列。xsl:for-each 依次将每个 page 元素设置为上下文节点。如果 xsl:for-each 不能设置上下文节点,xsl:value-of 元素中的表达式就不能起作用。

XPath 小结

XPath 对于 XSLT 至关重要。它提供了进行数学和字符串运算以及搜索和遍历 XML 输入的手段。没有 XPath,XSLT 就成了没有牙的老虎。

CSS

仁者见仁,智者见智。 -- William Blake

规则、规则,还是规则

XML 本质上是上下文相关的。就是说,标签、标签的顺序及其嵌套的方式在很大程度上说明了文档的含义。但除了排列以外,没有任何信息说明 XML 内容应该如何格式化为可视的形式(或者其他媒体形式)。XML 文档在文本编辑器或浏览器中的默认视图是显示所有的标签,任何元素都没有特殊的可视化处理,包括根元素。但是如果希望某些元素用大的粗体字显示,另一些元素用符号列表或者有序列表显示该怎么办?或者需要把一组信息放在页面的相同位置上,并且使这组信息的背景颜色不同于周围环境该怎么办?为此需要把表示规则通过 CSS 映射到元素。

CSS 表示规则允许把信息(XML 元素)和表示分离开来,就是说,如果需要可以在不同情况下对一组数据使用不同的外观。请注意,CSS 无法与 XSLT 转换 XML 的能力相比,在不同的平台和 Web 浏览器上的解释也不尽相同——尤其是设置 XML 的样式,后面您将看到。之所以在这里讨论 CSS,是因为这是一种轻型的方法,有时候用它就足够了。CSS 采用一种非常简单的非 XML 语法,也很容易使用。本教程中 CSS 文档被简称为“样式表”。

浏览器中的 XML

用 Internet Explore 打开一个 XML 文档可以看到类似图 2 的结果。

图 2. Internet Explorer 6.0 中显示的 XML 文档
 

单击加号和减号图标可以折叠或展开那些有子元素的元素,功能完整,但是只有那些主修计算机科学的学生也许会喜欢这种外观。Firefox 也好不了多少,但至少提供了一个理由,如图 3 所示。

图 3. Firefox 中显示的 XML 文档
 

Firefox 显示的文档树上面的消息暗示如果有 与该 XML 文档关联的样式信息的话,可能更好看一点。好。怎么做呢?

在 HTML 文档中可通过下列形式包含样式信息:

  • 在使用它的 HTML 标签中,按 CSS 的说法叫行内 样式
  • 在 HTML 文档内部的 <style/> 标签中
  • 在外部的 其他文件中,通过 HTML <link/> 标签引用

这些方法也可用于编写 XSL 转换从 XML 生成 HTML。不过这里关心的仅仅是为 XML 增加一些可视化格式。问题是如果随意为元素增加行内 样式属性可能造成 XML 文档是无效的。此外,XML 文档的 DTD 或者 XML 模式可能没有 script 元素,因此也不能用内部 方法。此外,这两种方法都要修改 XML,这是我们不希望的。惟一可行的方法就是使用外部 样式——通过一个或多个后缀为 .css 的文件将可视化处理应用于 XML 文档。但是又不能使用 HTML <link/> 标签(除非模式或者 DTD 允许)。因此,可使用下面这种浏览器指令把一个或多个 CSS 文件和 XML 文档关联起来:

<?xml-stylesheet type="text/css" href="catalog.css"?>

把这行指令放在 XML 文档的开始位置(序言部分),紧跟在 <?xml version="1.0"?> 指令之后。该样式表指令引用的 CSS 文件 catalog.css 和 XML 文件在同一个目录中。href 属性的值也可以是绝对的。上面的指令中还可使用其他属性,其中之一是 media。该属性的值可以是 all、braille、embossed、handheld、print、projection、screen、speech、tty 和 tv。

因此,可以在同一个 XML 文档中引用不同的级联样式表规则。该特性允许把 XML 格式化成要查看(或者“听”)的媒体类型。可以通过 @media 规则测试当前使用的媒体(从而用同一个样式表为其服务)。下面的代码给出了一个例子:

@media print {
body { font-size: 10pt; }
}
@media screen {
body { font-size: x-small; }
}

语法

级联样式表中定义样式的基本语法为:selector {property: value;}。选择器(selector)即需要根据一个或多个属性(property) 的值(value)定义其外观的 XML 元素。可以将多个选择器组织在一起,中间用逗号分开。

也可使用通用选择器,用星号(*)表示;相应的规则应用于 XML 文档中未明确定义规则的所有内容。属性值用允许的度量单位表示。这些单位表示颜色、长度等。一般而言,CSS 中的空白无关紧要,因此可以按照喜欢的方式调整文件的格式。但是属性的值 和限定的单位 中间不能有空白。

在 CSS 中可以灵活地定义选择器。比方说,如果要设置 dvd 的子元素 title 的样式规则,可以使用:

dvd title {font-weight: bold;}

它声明了 DVD title 元素的文本值用粗体显示。不一定要使用这种语法(可以更简单地声明 title 作为选择器),但是要记住可能需要在不同的上下文中重新设置元素的样式(这里是作为一个元素的孩子出现的,但也可能作为其他元素的孩子出现)。图 4 显示了在 Internet Explore 6.0 中的输出结果。

图 4. CSS 用于 XML,第一次
 

为了增强可读性,现在我们把每个 dvd 元素作为一段来显示,周围加上一点空白:

dvd {
display: block;
padding: 5px;
}

后面将讨论这些属性和它们的值。如果需要突出显示那些包含 genre 属性的 dvd 元素,可使用下面的规则:

dvd[genre] {color: blue;}

细心的读者会发现这种语法和 XPath 谓词有相似之处。虽然 Internet Explore 6.0 不能识别这种语法,但是 Firefox 1.0.7 给出了图 5 所示的呈现结果。

图 5. Firefox 中的属性谓词
 

现在,一位讨人喜欢的电影收集者希望用她喜爱的颜色来突出显示戏剧电影:

dvd[genre="Drama"] {color: purple;}

这条规则,以及那条显示带 genre 属性的 dvd 元素的更一般的规则都遵循了,如图 6 所示。同样只在 Firefox 中有效而不能用于 Internet Explore 6.0。

图 6. Firefox 中的属性谓词
 

现在看起来好多了,但是 DVD 的单价有点难看,是不是?我们在每个 price 元素的值前加上美元符号:

price:before {content: "$";}

再把电影制作的年份放在括号中。这里可以使用与 price 元素所使用的相同的伪元素 技术:

year:before {content: " (";}
year:after {content: ")";}

图 7 显示了上述规则在 Firefox 中的呈现结果。

图 7. 伪元素的呈现
 

Internet Explore 6.0 不能识别 selector:before 和 selector:after 伪元素,因此这里没有提供它的呈现结果。

在前面的基础上我们再增加一些格式:

清单 3. 格式化 DVD 目录 XML 文档的样式表

/*
** catalog.css
** This stylesheet visually formats our DVD collection XML doc.
*/

catalog {
font-family: Verdana, Arial, Helvetica, Sans-serif;
font-size: x-small;
padding: 25px;
background-color: #DDDDCC;
}

dvd {
display: block;
padding-bottom: 8px;
border-top: 1px solid gray;
border-left: 2px solid #666666;
border-right: 2px solid white;
border-bottom: 1px solid white;
background-color: #E8E8FF;
}

dvd:first-child {
border-top: 2px solid #666666;
}

dvd:last-child {
border-bottom: 2px solid white;
}

dvd title {
display: block;
font-weight: bold;
}

dvd[genre] {
color: blue;
}

dvd[genre="Drama"] {
color: purple;
}

dvd[genre="Action"] {
display: none;
}

price:before {content: "$";}

year:before {content: " (";}
year:after {content: ")";}

这里为 catalog(根元素)增加了几个属性来定义整个文档。其中包括背景颜色、边框、font-family 和 font-size 的总体设置。在呈现结果中可以发现,这些字体属性被 catalog 的所有子元素继承 了。因为 catalog 是根元素,这些规则应用于整个文档。嵌套元素也可有自己的字体属性规则,这些规则将覆盖为其父元素设置的规则。

然后为 dvd 元素增加了一些规则,使其与背景(catalog 元素)区分开。这里使用了两种不同的方式描述颜色:名称和十六进制串,CSS 中的颜色一节将对此简要地讨论。对 catalog 和 dvd 元素的 display 属性使用值 "block",让它们单独占一行并实现了 padding。此外,还使用 first-child 和 last-child 伪类 选择器对第一个和最后一个 dvd 元素使用加粗的 border。(伪类选择器与元素的条件而不是元素名匹配。)最后,那位讨人喜欢的电影收集者要求隐藏动作类的电影,这是通过 display: hidden 属性值实现的。图 8 和图 9 分别说明了 Firefox 和 Internet Explore 6.0 如何处理这个样式表。

 图 8. Firefox 呈现的样式表

图 9. Internet Explorer 6.0 呈现的样式表
 

请注意,Firefox 中通过将其 display 属性设置为 "none",成功隐藏了 genre 属性值为 Action(Raiders of the Lost Ark)的 DVD。事实上,Firefox 完美地执行了样式表中的所有规则。但是在 Internet Explore 6.0 中失败了。将 background-color 属性应用于 catalog 元素之后尤其明显,即覆盖了图 9 中大部分内容的灰色团。从此可见,不怎么坚持标准的 Internet Explore 6.0 在 CSS 对 XML 的支持方面赶不上 Firefox。

最后,从上一个样式表(清单 3)的开始可以看出,CSS 文件中允许使用注释。注释必须以 /* 开始,以 */ 结束,与 C 及 Java 编程中一样。

颜色

CSS 中的颜色通常用红绿蓝三原色按照顺序表示,有下列三种不同的方法:

  • 十六进制:这种方法使用从 0 到 F(等于十进制中的 15)的十六进制数。十六进制颜色符号以井号(#)开始,后面跟着各有两位的红绿蓝颜色分量。比如,#FF0000 表示大红,#000000 表示黑,#336699 表示暗绿色。
  • 十进制:这种方法用从 0 到 255 的十进制整数表示红绿蓝三原色,格式如下:rgb(rr,gg,bb)。比如 rgb(255,0,0) 表示大红。
  • 百分比:这种方法和十进制类似,但每个颜色分量使用 0 到 100 的百分数。比如,rgb(100%,0%,0%) 表示大红。
    还可以使用颜色名称 表示颜色。比如 red、white、blue 等等。

显示

如清单 3 所示,display 属性允许以不同的方式显示一个元素,可以取下列值:

  • block:使选择器出现在一个矩形区域中。内容之前和之后有一个空行。
  • inline:使元素的文本值和相邻元素出现在同一行中,前后没有断行。但是如果内容很长,可能在内部出现断行。这也是默认的显示行为。
  • none:不仅元素的内容消失不见,而且为其保留的空格也被删除;这一点和 visibility: hidden 属性不同,后者保留分配的空白。
  • list:以这种方式显示的元素用于组成有序或无序列表。

Table 元素为 CSS 替代 HTML 表格提供了某些支持,如果正确嵌套的话可以使元素看起来像 HTML table、tr 和 td 元素。但是先忘掉它吧,如果需要与 HTML 表格 rowspan 或 colspan 等价的功能,非表格样式的元素必须满足这种情况。

长度

很多 CSS 属性中用到了长度。宽、高、字体大小、边框、填白、页边距和定位都使用某种长度单位来描述它们的值。长度可以是绝对量也可以是相对量。绝对单位适合于打印媒介(但除此以外很少),包括点、活字、毫米、厘米和英寸。相对单位更常用于屏幕媒介,包括像素、百分比、ems 和 exes。

初看起来可能以为像素是绝对单位,但像素是相对于显示设备的分辨率而言的。像素通常用于度量与位图图像有关的块类型元素,但是应避免用于字体大小。如果使用像素单位描述字体大小,则将其视作绝对单位,为了改善可读性,多数浏览器不允许用户用它描述字体大小。

百分比相对于其他某个对象描述事物的长度。这个对象通常是最大可用空间。值为 60% 的宽度属性就表明最多可占用可用区域的 60%。比如,可以将上述 catalog 元素的规则改为:

catalog {
font-family: Verdana, Arial, Helvetica, Sans-serif;
font-size: x-small;
background-color: #DDDDCC;
width: 60%;
}

这个样式表例子中的其他规则尽量保持简单,在 Firefox 中得到的显示结果如图 10 所示。

图 10. 相对父元素的百分比宽度
 

从中可以看到 catalog 元素确实占据了可用空间(即浏览器窗口)的 60%,与新增加的 width 属性所规定的一样。

Ems 和 exes 非常适合相对于父字体大小指定当前字体的大小。使用这两种单位的关键在于必须知道父元素可比属性的大小。

表 1 概括了绝对和相对长度单位。

 表 1. 长度单位

字体

字体属性控制着显示的字形。可用的字体属性包括:

  • font-family:逗号分隔的字体系列的名称列表,优先级高的在前。常用的值有 Arial、Verdana、Sans-serif、Times New Roman 和 Serif。由多个单词组成的字体系列名应该用引号括起来。列表中的最后一个字体系列必须是 Sans-serif 或 Serif,根据需要选择。
  • font-size:可采用相对或绝对长度单位。除了表 1 中列出相对和绝对长度单位以外,还可使用 xx-small、x-small、small、medium、large、x
  • large 和 "xx-large"。虽然这些值有时候被称为绝对大小,但浏览器会根据用户的偏好缩放。
  • font-style:通常有 normal(默认)和 italic。
  • font-weight:控制显示的字体是 normal(默认)还是 bold。一些浏览器还用 bolder 和 lighter 提供了字体加粗的尺度。

文本

为了控制字体属性不能处理的其他属性,可使用文本属性。其中包括:

  • color:描述了前面颜色一节所提到的值。
  • background-color:设置包围文本的区域的颜色。可以使用 padding: 属性加大这个区域,参见填白一节的描述。
  • text-align:允许的值为 left(默认)、right、center 或 justify。
  • text-decoration:通常用 none(默认)或 underline。其他可能的值包括 overline 和 line-through。(还有传言说可以用 blink,但按照正统的说法这里不再讨论。)

背景

可以通过 background-color 属性或使用背景图像为各种行内、块或表格元素增加背景处理。background-color 属性的取值遵循前面关于颜色的说明。不过图像的使用可能有点复杂。用于描述背景图像的属性包括:

  • background-image:需要使用形如 url(' image URI ') 的语法指定图像位置。
  • background-position:指定背景图像相对于元素的方向。其取值需要用到两个:left 或 right 和 top 或 bottom.
  • background-repeat:控制背景图像是否要重复以及在垂直还是水平方向上重复。不重复使用 no-repeat,repeat-x 表示水平重复,repeat-y 表示垂直重复。

比如下面的例子:

background-image: url("../images/tubeTile.gif");
background-position: left top;
background-repeat: repeat-x;

填白

填白属性用于行内和块元素。可以使用单个 padding: 属性,也可以分别指定四个方向中的一个或多个:padding-top:、padding-left:、padding-right: 和 padding-bottom:。使用的单位参见长度一节。使用单个 padding: 属性,仍然要按照一定的顺序指定四个方向上留白的数值。比如:

  • 如果只给出一个长度,则四面都用该值。
  • 如果给出两个长度,则上下使用第一个值,左右使用第二个值。
  • 如果给出三个值,则分别用于上、左右和下。
  • 如果给出四个值,则分别用于上、右、下和左。

长度单位可以任意组合,但是不能使用负数。该属性不能从父元素继承。

边框

与 padding: 属性类似,可以同时描述元素的四面 border: 或者分别描述四个方向上的边框:border-top:、border-left:、border-right: 和 border-bottom:。值的语法格式为:length border-style color。长度单位参见前面的长度一节。边框类型可以是:dashed、double、dotted、groove、inset、outset 或 solid。比如下面的例子:

border-left: 1px solid #EEEEEE;
border-right: 1px solid #999999;

定位

使用 CSS 的 position 属性,可以说明元素在文档中显示的具体位置。比方说,如果需要规定目录中的第一个 dvd 元素出现在相对于所有其他元素某个固定的位置,可使用下面的代码:

dvd:first-child {
border-top: 2px solid #666666;
position: absolute;
left: 80px;
top: 150px;
}

请注意 position 属性和它的值 absolute。这样指定之后,就可以描述希望该元素相对于页面左上角的绝对位置了。上例中声明的位置是距离左侧边界 80 像素,距离上边界 150 像素。图 11 显示了在 Firefox 中的呈现结果。

图 11. Firefox 中的绝对定位
 

在 图 11 中,Terminator 2 按照文档顺序是第一个 dvd 元素,因此收到了为第一个 dvd 元素声明的绝对定位规则。除了 left 和 top 属性外,还可以声明 right 和 bottom,分别用于相对于布局的右边界和下边界定位元素。这些属性可以任意组合使用,只要合理就行。定位属性的另一个值是 relative,它依据通常的元素呈现位置按 top、left、right 和 bottom 属性定位。比方说,可使用下列代码将第一个 dvd 元素放在通常位置偏左 10 个像素,偏上 10 个像素:

dvd:first-child {
border-top: 2px solid #666666;
position: relative;
left: -10px;
top: -10px;
}

图 12 显示了在 Firefox 中的呈现结果。

图 12. Firefox 中的相对定位
 

让一个元素重叠在其他元素上面显示是可能的。可以通过 z-index: 属性控制哪个元素在上面。这里使用整数值,值越大越在上面。

另一种定位元素的绝对方式是通过固定定位。position: absolute 相对于页面中其他元素的布局定位元素,而 position: fixed 根据浏览器窗口确定元素的位置。不使用可滚动的例子很难说明其用法,不过您可以自己尝试,语法就是 position: fixed。top、left、right 和 bottom 属性可用于设置元素驻留(即便页面滚动了)的位置。

CSS 小结

虽然通常不作为设置 XML 文档样式的首选方式,也没有 XSLT 那样进行计算和转换的能力,但 CSS 至少为可视的格式化 XML 元素提供了一种轻型的方法。由于各种浏览器对 CSS 的支持不一致(对 XML 尤其如此),至少将 CSS 用于屏幕媒体时要非常小心。不过如上所述,Mozilla Firefox 对 CSS 在 XML 中的应用提供了强大的支持。关于将 CSS 用于打印媒体的文章表明非常成功,如果想避免 XSLT 的复杂性,CSS 是一种不错的选择。

结束语

总结

现在我们结束了 XML 转换这个主题关于 XSLT、XPath 和 CSS 的讨论。希望能够为那些刚接触这个主题的读者提供了足够的信息。只要认真掌握本文中提供的材料及所附的参考资料,对于准备考试 142 “XML 及相关技术”中 XML 转换部分的内容来说绰绰有余了。

 

组织简介 | 联系我们 |   Copyright 2002 ®  UML软件工程组织 京ICP备10020922号

京公海网安备110108001071号