本文主要介绍处理创作的 XML 文档 的一些问题。所谓创作的 XML 文档,这里指的是来源于内容创建者的数据内容,一般是由一个 DTD 或模式所引导的。许多环境会出于各种原因(比如,一致性、效率和节约成本)而采用引导创作内容。将 DTD 置于适当的位置,就有可能实现 100% 的一致性。如果作者受 DTD 的控制(和限制),那么就一定能够保证结果是可预见的,对吗?
然而,对大多数 DTD 的密切观察就会发现相当程度的灵活性 — 特别是当这个 DTD 源于普通资源或者来自一个标准或规范时。这并不是 DTD 范例的一个缺点,但是如果 XML 数据集周边的基础架构 — 昂贵的发布渠道、文档管理系统和翻译过程— 以某种方式与文档发生关联,那么问题就很难办。
通常的实践做法是应用样式控制来限制文档差异。实现样式控制的方法是手供对数据集进行交叉检查,但这代价比较大,还有一个更有效的方法是自动检查。
内容创建者拥有一个 XML 数据集,以及一系列需要完成的任务。这些任务能够自动化吗?
解决这个问题,通常也就是简单地回答一个问题:“这个任务是否是可预计、可重复和可定义的?” 更简单的检查是不需要解析文本内容而只需要关注文档结构的方法:
- 新的内容是否有一个交叉引用目标?是否其他人很有可能会链接到这个主题?
- 这个清单是否有多个项目?如果没有,它就不是一个清单。
- 是否所有重要的安全信息都在任务清单之前出现?最好预先警告用户可能出现触电。
您可以确定一个已知的文档模型,或者作为一般文档。毕竟,这是关系到可扩展性的问题:如果工具只有一个用途,而且可以通过自动化节省时间,那么采用一个简单的独立的脚本来处理业务逻辑可能就是一种合理的方法。如果是一个多用途且用户可以定制的工具,那么就需要更强大的可配置方法。这里我采用后一种方法。
XSLT 是一种万能 XML 处理语言。然而,它并非唯一的选择,因为许多 XML 是使用 DOM 技术处理的,而本文所介绍的一些方法也可以采用 DOM 技术实现。但是当您希望执行一些修改操作时,XSLT 是最理想的工具。
这里的 XSLT 例子包含了一个 HTML 来演示如何简单地将工具作为独立的应用程序部署。这个组合使用的 XSLT 版本为 1.0(见 参考资料),并且嵌入的脚本是使用 Microsoft® JScript® 脚本语言编写的。
第一步是捕获业务逻辑。根据本次练习的目的,您需要对本文的 XML 源进行检查。检查规则是基于针对这些文档作者的样式指南。
这些检查是通过检查文档结构来保证编写的内容是完整的,而不是通过分析文本内容实现的。XPath 是进行这些检查的理想候选工具。
这个方法意味着要执行整个设计过程,包括定义如何最佳地捕获文档错误,如何对错误进行分类,以及如何处理这些错误。为什么不直接将这个错误检查直接嵌入到 XSLT 中呢?这种技术的优点是在错误检查编码到一个 XML 词汇之后,这个工具就成为处理一个或多个配置的通用代码。用户可以选择不同的规则集处理不同的文档数据:
- 定义测试文档的方法。根据本实践的目的,这里的设计所选择的是 XPath。
- 定义 “通过 — 失败” 准则。使用基于 XPath 表达式的文档检查来查询一个或多个节点是否存在。
- 定义失败的严重性。每一个检查都可以按以下方式分类:
- 强制性。错误检查过程第一个实例执行时就失败。
- 建议性。没有过程错误,但是这个过程将实例标记为错误。
- 条件性。是强制性的一种变形,这个检查更复杂一些,它基于节点 XPath 表达式的返回值额外执行一次环境检查。
- 创建和导入一个映射文件。这个文件应该进行以下文档检查:
- 为文档定义一个名称空间 — 例如:
<err:document xmlns:err="http://error.com/mynamespace">
- 创建每个错误定义。
- 概括说明文档执行的每一个错误检查:
<err:element type="structure" name="dw-document" context="/dw-document" enforce="yes">
- 说明在元素上执行的错误检查:
<err:element type="element" name="ol" context="./li" pass=">=2"/>
- 为文档定义一个名称空间 — 例如:
关于完整的示例测试集,见 参考资料。
在您定义了错误检查语法之后,您就可以定义一个或多个应用到不同数据集的规则集。
XSLT 可能有两个输出流:日志消息和优化文档源(如果执行修改操作)。
这个设计是使用 XSLT 输出流来创建一个新的优化文档和一个 XSLT 扩展来将日志消息写到另一个输出流。这个单独的例子将日志消息添加到一个 HTML 日志窗格上。
这些错误检查可以被分成两种不同的类别:顶级结构性检查和元素级检查。XSLT 首先执行顶级检查;然后如果可能的话(即,之前所有检查均通过),它会使用常用 XSLT 模板来处理文档内容。
为了创建这个 XSLT,我们需要执行以下步骤:
- 在这个 XSLT 中定义一个
script元素,以定义嵌入的脚本。首先,创建一个日志环境,然后创建一个函数来存储这些消息,如 清单 1 所示。
清单 1. 在 XSLT 中定义嵌入脚本<msxsl:script language="JScript" implements-prefix="xslext"> <![CDATA[ var messages = new Array(); var msgct = 0; function addMsg( msg ){ messages[msgct++] = msg; return ""; } ]]> </msxsl:script>
- 添加一个模板来处理这些消息。清单 2 显示这些处理代码。
清单 2. 添加一个模板<xsl:template name="handlemsg"> <xsl:param name="msg"/> <xsl:param name="terminate">no</xsl:param> <xsl:param name="lvl">1</xsl:param> <xsl:variable name="logmsg"> <!-- Indent the log messages to help with readability --> <xsl:choose> <xsl:when test="$lvl=2"> • </xsl:when> <xsl:when test="$lvl=3"> • </xsl:when> <xsl:when test="$lvl=4"> • </xsl:when> <xsl:when test="$lvl=4"> • </xsl:when> </xsl:choose> <xsl:value-of select="$msg"/> </xsl:variable> <xsl:variable name="log" select="xslext:addMsg( string( $logmsg ) )"/> <xsl:if test="$terminate='yes'"> <xsl:variable name="errormsg" select="xslext:addMsg( 'ERROR: Error checking caused the process to stop' )"/> <!-- If the error msg force termination, the process must first output all existing log messages --> <xsl:variable name="output" select="xslext:outputMsgs( $logfileout )"/> <xsl:message terminate="yes"></xsl:message> </xsl:if> </xsl:template>
这个模板是从整个 XSLT 上调用的,用来处理发送到消息扩展函数的消息。
- 使用一个全局文档变量来定义进行判断的 XPath 表达式,并创建一个接收表达式的函数。清单 3 所示就是这段代码。
清单 3. 创建一个全局变量<msxsl:script language="JScript" implements-prefix="xslext"> <![CDATA[ var xpathdoc = null; function setUpXPath( ns, trialexpr ){ var xml = ns.nextNode().xml; try{ xpathdoc = new ActiveXObject( "Msxml2.DOMDocument.3.0" ); xpathdoc.loadXML( xml ); return trialexpr + ": " + xpathdoc.selectNodes( trialexpr ).length; } catch(e) { return "ERROR: " + e.description; } } ]]> </msxsl:script>
清单 3 显示了一个创建作为进一步执行 XPath 判断的环境节点的 DOM 文档。
- 在 XSLT 的主体中调用这个初始化函数,如 清单 4 所示。
清单 4. 添加一个初始化函数<xsl:call-template name="handlemsg"> <xsl:with-param name="msg">Setup ' <xsl:value-of select="xslext:setUpXPath( $root, concat( '//', name($root) ) )"/> '</xsl:with-param> </xsl:call-template>
说明这个扩展函数是如何使用名称空间前缀调用的(见本例的
xslext)。这个前缀是这个自定义函数与一些 XSLT 的标准函数的区别所在,如number()、string()和contains()。 - 处理顶级文档测试:
- 定义一个规则集文件参数:
<xsl:param name="rulesetfile"></xsl:param>
给这个参数赋值一个文件 URI。这个例子会在运行时接收用户选择。
- 创建一个处理每一个测试的模板:
xsl:template name="process-check"
这个模板是按以下方式执行的。首先,您要创建一个扩展函数,使用
xpathdoc作为环境节点,然后估计规则文件中的测试表达式集:
function evalXPath( exp ){ try{ return xpathdoc.selectNodes( exp ).length; } catch(e) { return "Exception: " + e.description; } }
如果成功,这段代码会返回一个整数:这个数应该大于等于 1。0 表示测试执行成功,但是没有发现匹配的结果;如果函数抛出一个异常或者 XPath 表达式编写错误时会出现一个错误描述信息。
- 调用这个函数,然后将返回值存储到一个变量中:
<xsl:variable name="check" select="xslext:evalXPath( string( $context ) )"/>
其中
$context是为err:element设置的表达式字符串(例如,/dw-document//meta-dcsubject)。如果
$check的值大于或等于 1,那么这个测试就设置为 Enforce,这样测试就通过了。如果
$check的值为 0,并且测试未被设置为 Enforce,那么测试是通过了,但是用户会看到一条警告消息。否则,测试就失败了,并且这个过程会停止。您可以通过一个将
terminate设置为 Yes 的xsl:message强制停止这个测试(见 清单 2)。日志消息会调用这个模板,而terminate参数会被设置为 Yes。 - 为所有需要处理的强制性测试定义一个节点集:
document($rulesetfile)//err:element[@type='structure'][@enforce='yes']
- 处理其他所有非强制性的顶级测试:
document($rulesetfile)//err:element[@type='structure'][not(@enforce='yes')]
- 定义一个规则集文件参数:
- 处理元素级测试。
这些测试是在各个模板中处理的。为了保持这个过程的通用性,这个 XSLT 具有一个简单的处理元素的模板:
xsl:template match="node()"
在这个通用的模板中,您要设置一个变量来判断这个规则集是否包含一个可适用测试:
<xsl:variable name="match" select="document($rulesetfile)//err:element[@type='element'] [@name=$name]"/>
其中
$name是定义为当前元素的名称。如果
$match为 True,那么这个测试的环境是使用另一个扩展函数运行的。这个函数类似于顶级 XPath 判断,它从 XSLT 接收当前节点,并执行表达式判断,如 清单 6 所示。
清单 6. 判断一个表达式的函数function evalXPathAgainstNode( node, exp ){ try{ return node.nextNode().selectNodes( exp ).length; } catch(e) { return "Exception: " + e.description; } }
如果这个函数返回一个整数值(既不是 0 也不是一条错误消息),那么这个整数会传递给另一个函数,来根据在
pass属性中所定义的 “通过 — 失败” 准则测试数字:
<err:element type="element" name="ol" context="./li" pass=">=2" />
- 判断
ol元素有 2 个或 2 个以上的li孩子节点,如 清单 7 所示。
清单 7. 判断 li 元素的个数function evalExpr( str, pass ){ return eval( str + pass ); } ... <xsl:variable name="eval" select="xslext:evalExpr( $check, $pass )"/>
- 这个 XSLT 会返回类似于 清单 8 的日志结果。
清单 8. XSLT 日志结果Start Setup '//dw-document: 1'... · Check (Top-level document?) '1' · Conditional check '(Document ID missing?) '1' (1==1) == true' · Conditional check '(Article missing?) '1' (1==1) == true' · Conditional check '(Meta field (document type) missing?) '1' (1==1) == true' · Conditional check '(Meta field (subject) missing?) '1' (1==1) == true' · Conditional check '(Article title missing?) '1' (1==1) == true' · Conditional check '(Document author missing?) '1' (1==1) == true' · Conditional check '(Published date missing?) '1' (1==1) == true' · Check (Missing abstract?) '1' · Conditional check '(Dates out of sync?) '0' (00) == 0' · Conditional check '(Broken internal links?) '0' (0==0) == true' · Context checking 'heading' (./a[@name]) '(1==1) == true'... · Error context checking 'heading' (./a[@name]) '(0==1) == false'... · Context checking 'heading' (./a[@name]) '(1==1) == true'... · Context checking 'ol' (./li) '(3>=2) == true'... / End
在创建了一个执行检查、确定错误和修改文档的过程之后,下一步显然就是修改元素。这个例子包含了添加到文档的基本代码。
如果规则集显示 err:element 有一个孩子元素 err:onfail,那么代码可以是以下任意一种:
<err:insertbefore></err:insertbefore><err:insertatstart></err:insertatstart><err:insertatend></err:insertatend><err:insertafter></err:insertafter>
insert 元素包含了修正文档的 XML 标签 — 例如:
<err:insertatstart>
<a name="function:generate-id()" /></err:insertatstart>
|
这个 XSLT 需要对它进行处理。
然后,您可以创建一个模板来遍历一个节点集:
<xsl:template name="copy-nodeset"> |
将 err:insertbefore、err:insertatstart、err:insertatend
和 err:insertafter 元素的内容传递到 XSLT 中这个模板的相关位置 — 例如:
<-- Add 'err:insertbefore' here -->
<xsl:element name="{name()}">
<xsl:copy-of select="@*"/>
<-- Add 'err:insertatstart' here -->
<xsl:apply-templates/>
<-- Add 'err:insertatend' here -->
</xsl:element>
<-- Add 'err:insertafter' here -->
|
这个模板会对 function:generate-id() 方法进行特殊处理。
为了保证完整性,我们需要添加文档插入内容的日志:
... · Error context checking 'heading' (./a[@name]) '(0==1) == false'... · Adding content at start of 'heading' · Error context checking 'heading' (./a[@name]) '(0==1) == false'... · Adding content at start of 'heading' ... |
本文介绍了如何使用 XSLT 来分析文档结构以确定是否满足一定业务规则的要求。这个过程可以以两种重要方式执行一个重要的函数:第一,协助内容创建者实现创作目标 — 例如,用户可以脱机工作并多次执行测试来验证他们完成了特定的任务;第二,作为正式工作流文档的一部分 — 例如,这个工具可嵌入到一个文档知识库工作流中,而 “通过 — 失败” 准则可以控制一个可管理文档在编辑、审阅和接受状态之间的转换。
将业务逻辑与 XSLT 分离,从而使工具更具灵活性。这段代码会变得更加通用,因为一个代码库就可以应用多个规则集。XSLT 比 DOM 更加强大,而且支持使用转换过程来修改文档。
| 描述 | 名字 | 大小 | 下载方法 |
|---|---|---|---|
| 示例 XSLT 代码 | xslt_source.zip | 9KB | HTTP |
学习
-
Simplified English (Wikipedia):阅读关于鼓励技术作者使用明确且保持一致的撰写风格的倡议。
-
XSL Transformations (XSLT) Version 1.0 规范(W3C,1999 年 11 月):了解用于转换 XML 文档的语言 XSLT 的语法与语义。
-
用 XSL 扩展来延伸 XSL 技术(Jared Jackson,developerWorks,2002 年 4 月):使用扩展,一种允许您扩展
XSLT 核心特性功能的技术。
-
用于数据的 XML:用 EXSLT 扩展 XSLT 的功能(Kevin Williams,developerWorks,2002 年 12 月):了解
EXSLT 标准及它是如何扩展 XSLT 1.0 的功能的。
- 计划使用 XML 名称空间,第 1 部分(David Marston,developerWorks,2004 年 4 月):了解适合您使用的最佳 XML 名称空间用法。
-
developerWorks XML 专区:在 XML 专区获取提高您的专业技能所需的资源。
-
My developerWorks:个性化您的 developerWorks 体验。
-
IBM XML 认证:了解如何才能成为一名 IBM 认证的 XML 和相关技术的开发人员。
-
XML 技术库:访问
developerWorks XML 专区,获得广泛的技术文章和技巧、教程、标准和 IBM 红皮书。此外,阅读更多的
>XML 技巧文章。
-
developerWorks 技术活动 和 网络广播:随时关注这些活动中的技术。
- developerWorks 播客:收听面向软件开发人员的有趣访谈和讨论。
- developerWorks 演示中心,观看并了解 IBM 及开源技术和产品功能。
获得产品和技术
-
Business Rules Exchange(Svante Ericsson,TPSMG,2004 年 9 月):考虑使用一个
XML 词汇对项目相关的文档创建规则进行编码。它是基于验证文档的 DTD 规定创作规则的。
-
IBM 产品评估试用版软件:下载或
IBM SOA Sandbox for People,并开始使用来自
DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。
讨论
- Yahoo! XML 群组:参与讨论。
- XML 专区讨论论坛:参与任何一个 XML 相关讨论。
-
developerWorks 中文社区。查看开发人员推动的博客、论坛、组和 wikis,并与其他 developerWorks 用户交流。