Mozilla® 一直在改进其旗舰浏览器,并且最新的主要发行版 Firefox® 3.0 几乎为每个人都提供了某种特性。肯定不会忽略 XML 开发人员 — 新版本改进了基本解析、DOM、XSLT、SVG 等等。在本文中,了解 Firefox 3.0 提供的用于 XML 处理的新特性;关注添加的 EXSLT 扩展如何在浏览器中使用 XSLT。
对于目前存在的成百上千个 XML 处理工具,Web 浏览器仍然是操作的中心 — XML 开发人员十分幸运,操作似乎永远不会减慢。过去的几年里,我撰写了关于开发人员喜爱的 Firefox 浏览器中与 XML 相关的特性的一系列文章(请参阅 参考资料);我已经从 Firefox 1.5 介绍到了 2.0。最近,Firefox 升级到 3.0 版,其中有很多全面改进以及大量用于 XML 处理的新的出色开发。许多改进都来自核心 Web 处理引擎 Gecko 从 1.8.1 到 1.9 的升级。
3.0 版中的 XML 基本原理
XML 领域包含大量技术,但是全部内容的起点都是解析器;Firefox 3 引入了对基本 XML 解析的一项巨大改进。在过去的 Mozilla 浏览器中,解析 XML 文档是同步的,将阻止对文档执行的所有操作,直至完全载入该文档。对比 XML 解析与 HTML 解析,后者一直都是异步的,这样文档的各个部分在解析后就可以使用了。对于用户,这意味着他将开始看到在浏览器处理完页面之前 Web 页面是如何形成的;另一方面,使用 XML 文档,在完成解析前,用户根本看不到任何内容。这是一个可用性问题,不利于处理大型的 XML 文档。
在 Firefox 3.0 中,XML 内容模型是逐步构造的,类似于 HTML。这将使 XML 在 Web 上的实际应用产生重大变化。有一些例外 — 最明显的是 XSLT 不是逐步处理的。理论上,您可以使用 XPath 的有限子集,逐步应用 XSLT 的子集,但是这样做本身就需要花费巨大精力,并且超出 Firefox 3.0 的范围。
我曾经希望 Firefox 3.0 中的改进之一是提供 xml:id 支持。关于是否支持 xml:id 曾存在一些争议,但是如果可以出现在将来的发行版中,那么就可以使用一个补丁。一般而言,Firefox JavaScript 为在 XML 文档中使用
getElementById 而提供的惟一方法是内部 DTD 子集(无外部子集,并且没有 xml:id)。如果确实需要 xml:id,请从 JavaScript 中使用 XPath 查询 XML 名称空间和 “id” 本地名称中的属性。
未能实现的另一个期望的核心改进是允许用户请求浏览器装入外部 DTD 子集。看上去好像补丁程序已就绪,但是没有足够可用的开发人员资源完成 Q&A 过程,因此未能在 Firefox 3.0 发行版中实现。
XML 领域包含大量技术,但是全部内容的起点都是解析器;Firefox 3 引入了对基本 XML 解析的一项巨大改进。在过去的 Mozilla 浏览器中,解析 XML 文档是同步的,将阻止对文档执行的所有操作,直至完全载入该文档。对比 XML 解析与 HTML 解析,后者一直都是异步的,这样文档的各个部分在解析后就可以使用了。对于用户,这意味着他将开始看到在浏览器处理完页面之前 Web 页面是如何形成的;另一方面,使用 XML 文档,在完成解析前,用户根本看不到任何内容。这是一个可用性问题,不利于处理大型的 XML 文档。
在 Firefox 3.0 中,XML 内容模型是逐步构造的,类似于 HTML。这将使 XML 在 Web 上的实际应用产生重大变化。有一些例外 — 最明显的是 XSLT 不是逐步处理的。理论上,您可以使用 XPath 的有限子集,逐步应用 XSLT 的子集,但是这样做本身就需要花费巨大精力,并且超出 Firefox 3.0 的范围。
我曾经希望 Firefox 3.0 中的改进之一是提供 xml:id 支持。关于是否支持 xml:id 曾存在一些争议,但是如果可以出现在将来的发行版中,那么就可以使用一个补丁。一般而言,Firefox JavaScript 为在 XML 文档中使用
XSLT 的重大改进
希望在 Firefox 中使用 XSLT 的人们获得的最大胜利是支持 EXSLT,这是一组由 XSLT 社区开发和支持的 XSLT 扩展,并且受到许多其他 XSLT 处理器的支持。Firefox 3.0 添加了对于大型 EXSLT 子集的支持,首先从 node-set 函数开始,这是解决 XSLT 1.0 中最严重的限制的重要方法。EXSLT 被组织到各个模块中,每个模块都定义若干个扩展函数和元素。Firefox 3.0 在一组模块中实现了一组扩展,如下所示:
Common:Firefox 3.0 实现了通用函数的基本集合:
exsl:node-set 允许您将结果树片段转换为节点集,这样便可以对其应用 XPath。
exsl:object-type 是一个内省工具,用于报告对象类型,例如字符串、节点集、数字或者布尔值。
Sets:Firefox 3.0 实现了一些使用节点集的有用扩展:
set:difference 将计算两个集合之间的差异,返回一个节点集,该节点集的节点位于其中一个实参,而不在其他实参中。
set:distinct 将检验节点集以查找拥有相同字符串值的节点,并且只保留每个节点的一个实例,而将其余都删除。
如果返回的节点集中的节点在两个集合中都有,则 set:intersection 将计算交集。
set:has-same-node 将确定两个节点集是否有任何公共节点(例如,它们是不是共享实际的同一个节点,而不只是具有相同字符串值的不同节点,与 XPath = 运算符一样)。
set:leading 将返回一个节点集,其中的节点按照文档顺序要比另一个节点集中的第一个节点早出现。
set:trailing 将返回一个节点集,其中的节点按照文档顺序要比另一个节点集中的第一个节点晚出现。
Strings:Firefox 3.0 实现了一些使用字符串的有用扩展:
str:concat 将返回一个字符串,它见集合中每个节点的字符串值连接在一起(与内置的 concat 函数相比,该扩展将连接固定顺序的表达式)。
str:split 将使用一种模式把一个字符串分隔为一连串子字符串(使用在运行时构造的节点集表示)。
str:tokenize 将使用一组单字符标记把一个字符串分隔为一连串子字符串(使用在运行时构造的节点集表示)。
Math:Firefox 3.0 实现了一些函数,这些函数可以让您更轻松地从节点集内容中获取最小数值量和最大数值量:
math:max 将返回给定节点集中的内容的最高数值。
math:min 将返回给定节点集中的内容的最低数值。
math:highest 将返回内容中拥有最高数值的节点集。
math:lowest 将返回内容中拥有最低数值的节点集。
正则表达式:Firefox 3.0 将正则表达式的功能引入到 XSLT 中:
regexp:match 将在运行时构造节点集时,针对字符串匹配正则表达式模式并返回匹配的子字符串。
regexp:test 将检查字符串是否与正则表达式模式完全匹配。
regexp:replace 将替代匹配正则表达式模式的子字符串。
通过示例了解 EXSLT
为了帮助您开始在转换过程中使用 EXSLT,我构造了一个示例,并在其中实践了大量在 Firefox 3.0 中实现的函数。我发现在浏览器中使用 XSLT 的最佳实践之一是交付针对半结构化数据的报告。您将用户引导向包含处理指令的 XML 文件,应用 XSLT 转换。在这种情况下,通常可以规定所需的浏览器版本,因此无需过于担心跨浏览器兼容性问题。此外,还将服务器的大量工作分担到每个用户的计算机中。清单 1(employees.xml)是一个员工信息文件,我将针对该文件在 Firefox 3.0 中呈现一个报告。
清单 1. 员工信息文件 employees.xml
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xml" href="employees.xsl"?>
<employees>
<department id="res">
<title>Research</title>
<info>http://example.com/ar-and-dee for more info</info>
<employee id="111">
<title>Coordinator</title>
<name>
<given>Rene</given>
<family>Descartes</family>
</name>
<location building="PAR1">France</location>
</employee>
<employee id="112">
<title>Project Manager</title>
<name>
<given>Abu Ja'far</given>
<family>Al Kwarizmi</family>
</name>
<location building="BAG2">Iraq</location>
</employee>
</department>
<department id="exec">
<title>Executive</title>
<info>Home of the head honchos</info>
<employee id="101">
<title>Chief Executive Officer</title>
<name>
<given>Genghis</given>
<family>Khan</family>
<honorific>The Great</honorific>
</name>
<location building="MON1">China</location>
</employee>
</department>
<department id="hr">
<title>Human Resources</title>
<info>We're happy to serve you at http://example.com/hr</info>
<employee id="102">
<title>Manager of Wellness</title>
<name>
<given>Ching-Yuen</given>
<family>Li</family>
</name>
<location building="SZE1">China</location>
</employee>
</department>
</employees>
注意,位于顶部的 xml-stylesheet 处理指令将指示浏览器使用 XSLT。清单 2(employees.xsl)是从 清单 1 生成报告的转换。
清单 2. 通过员工信息文件(employees.xsl)生成报告的转换
<?xml version="1.0" encoding="utf-8"?>
<!-- A -->
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:math="http://exslt.org/math"
xmlns:regex="http://exslt.org/regular-expressions"
xmlns:set="http://exslt.org/sets"
xmlns:str="http://exslt.org/strings"
xmlns="http://www.w3.org/1999/xhtml"
exclude-result-prefixes="set math regex str">
<!-- Notice the namespace declarations for EXSLT.
Notice also exclude-result-prefixes, since you don't want those
namespace declarations in the result XHTML
-->
<!-- Use XML mode to approximate XHTML output
(notice the doc type declaration info) -->
<xsl:output method="xml" encoding="utf-8"
doctype-public="-//W3C//DTD XHTML 1.1//EN"
doctype-system="http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"/>
<xsl:template match="employees">
<!-- Put the presentation style into a separate file,
specified using a processing instruction in the output -->
<xsl:processing-instruction name="xml-stylesheet">
<xsl:text>type="text/css" href="employees.css"</xsl:text>
</xsl:processing-instruction>
<html xml:lang="en">
<head>
<title>Employee report</title>
</head>
<body>
<h1>Employee report</h1>
<table>
<xsl:apply-templates/>
</table>
<hr/>
<xsl:call-template name="stats"/>
</body>
</html>
</xsl:template>
<xsl:template name="stats">
<xsl:variable name="execs"
select="department[title='Executive']/employee"/>
<xsl:variable name="employees-in-china"
select="department/employee[location='China']"/>
<!-- Use set:has-same-node to check whether the two separate
XPath queries have any node sets in common -->
<xsl:if test="set:has-same-node($execs, $employees-in-china)">
<p>Note: At least one executive presently works in China</p>
</xsl:if>
<dl>
<dt>Countries where employees presently work</dt>
<dd>
<!-- Use set:distinct to eliminate duplicate country names
from the query result -->
<xsl:for-each select="set:distinct(department/employee/location)">
<xsl:value-of select="."/>
<xsl:if test="not(position()=last())">
<xsl:text>, </xsl:text>
</xsl:if>
</xsl:for-each>
</dd>
<dt>Newest employee</dt>
<!-- Use math:highest to determine the highest numerical value of employee ID -->
<dd><xsl:value-of select="math:highest(department/employee/@id)"/></dd>
</dl>
</xsl:template>
<xsl:template match="department">
<tr>
<td colspan="4">
<!-- Use regular expressions to sniff out URLs from unstructured content -->