级别: 中级 Micah Dubinko (micah@brainattic.info), 首席顾问, Brain Attic, L.L.C.
2004 年 9 月 01 日 对混合名称空间文档进行验证更像是一门艺术,而不像是一门科学。XForms 1.0 在各种各样的宿主语言中是作为一种组件使用的,它引入了一些关于验证程序应该如何处理这类文档的新问题。本文将讨论作者在编写在线 XForms 验证程序工具时所遇到的挑战,以及克服这些问题的技术。
我撰写的 XForms 书籍(请参阅
Resources)于 2003 年秋同时在书架上和网络上出现。在很短的时间内,我收到了大量电子邮件,都是关于 XForms 问题的,通常还附有一两页漏洞百出的 XML 源文件。一般情况下,我是乐于答复电子邮件的,但是一页一页地翻看别人的 XML 寻找常见的打印错误并不是件令人愉快的事,而且速度太慢。我需要一种更好的办法。
我是“建设性偷懒”的忠诚信奉者,于是,我决定编写一个在线工具,接受 XForms 文档作为输入,然后报告其中错误或者可能错误的所有标记结构。我从电子邮件文档中合理抽取了人们所犯错误的样本。二者结合起来,便成了一个强大的工具,它可以让表单的作者自己发现错误。
XForms 岛
XForms 1.0 规范(请参阅
Resources)被定义为一些元素、属性和内容模型。但是有一点它没有定义,就是根元素,这留给了宿主语言去解决。最常见的两种宿主语言是 XHTML 和 SVG,但在原则上,几乎任何 XML 词汇表都可以使用。因此,XForms 验证程序的第一项任务就是从文档中分离出 XForms 部分。为此,我杜撰了
XForms 岛这个词。
因为 XForms 将用途和表示分离开来,除了最小的表单文档之外,所有文档都至少有两个 XForms 岛,一个用于 XForms Model(定义表单做什么),一个用于 XForms User Interface(定义表单的外观)。
清单 1 所示的是一个简单的 XForms+XHTML 文档——可能过于简单了,但是也包含了一个常见的错误。
清单 1. 一个普通的、带有错误的 XForms+XHTML 文档
<?xml version="1.0"?>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:x="http://www.w3.org/2002/xforms"
xmlns:ev="http://www.w3.org/2001/xml-events">
<head>
<title>Basic mixed document</title>
<x:model>
<x:instance>
<!-- @@@ forgot xmlns="" on <root> @@@ -->
<root>
<child/>
</root>
</x:instance>
<x:bind nodeset="child" required="1"/>
</x:model>
</head>
<body>
<x:input ref="child">
<x:label>Label</x:label>
</x:input>
</body>
</html>
|
清单 1 显然有两个 XForms 岛:
x:model 和
x:input 。问题是代码如何定位这两个部分。事实上这并不是很难,因为可以标识为 XForms 岛的元素必须符合两个简单的条件:
- 必须在 XForms 名称空间中。
- 在 XForms 名称空间中不能有任何祖先。
虽然这种测试可以使用 XPath 完成,但我更愿意探索一种不同的 Python 用处理 XML 的方法。于是我编写了一个过滤器函数,判断给定的节点是否启动了一个 XML 岛,清单 2 显示了该代码。
清单 2. 选出 XForms 岛
# extract islands
def island_filter(node, usedns):
"""
An 'island' element has
1) the XForms NS
2) no ancestors with the XForms NS
usedns is a string containing the XForms Namespace URI
"""
if node.type != "element": return False # skip non-elements
rc = True
if (get_element_ns(node) != usedns): rc = False
node = node.parent
while node is not None:
if (node.type=="element" and get_element_ns(node)==usedns):
rc = False
break
node = node.parent
return rc
def get_element_ns(elem):
ns = None
try:
ns = elem.ns().content
except libxml2.treeError, e:
pass
return ns
|
XPath 检查
验证程序存储检查出的 XForms 岛,以备以后进行处理。清单 1 中的错误(如注释中所述)在于不小心将
root 元素留在默认的名称空间 XHTML 中了。这种错误以及其他几种类似的问题,可以使用清单 3 中所示的 XPath 检查探测到,清单 3 将返回可疑的元素节点。
清单 3. 用 XPath 检查名称空间的漏洞
//xf:instance//*[namespace-uri(.)=namespace-uri(/*) or
namespace-uri(.)=$usedns]
|
注意,因为
清单 3 没有假定宿主语言的结构,所以大量使用了 XPath 的
// 缩写形式,这种形式通过
descendant-or-self 轴搜索整个处理的文档。还要注意的是,XPath 中的名称空间前缀映射(
xf: )不一定与目标文档使用的相同(
x: )。该测试检查
x:instance 元素的后代,看它是在 XForms 名称空间中,还是在根节点的名称空间中。无疑名称中的限定部分是一个很好的线索,因为完全合法的 XForms 文档可能满足这个条件。另一方面,这也是一个发现编辑错误的好机会,如
清单 1那样,因此,验证程序发出一条警告信息。
连接 ID 和 IDREF
另外一种常见的错误是匹配
ID 和
IDREF 。这种问题部分是由于历史原因造成的,因为定义
ID 机制依赖于 DTD。一些工具(在很大程度上依赖于作者对 XML Schema 和 Infoset 的看法)也允许通过 XML Schema 数据类型定义
ID 。但是实际上,您很少会发现这样的定义,遇到的常常是恰好命名为
id 的属性。
这种情况不怎么好,但实用的验证程序工具必须能处理这种情况。验证程序查找 XForms 中包含
IDREF 的所有属性。首先尝试使用内置的
id() 函数;如果没有找到匹配的元素,则转而求助于 XPath 测试,寻找命名为
id 或者
xml:id 的属性(根据未完成的 W3C 草案,请参阅
Resources)。代码如下:
//*[@id='idstr' or @xml:id='idstr']
|
验证 XForms 岛
最后一步,验证程序要分析每个 XForms 岛,并使用 RELAX NG 验证它。这项工作比看起来要复杂,因为 XForms 的每个部分(如
label )都可能包含来自宿主语言的标记,更不用说允许到处存在的其他属性了。
为此,验证程序使用了高度模块化的 RELAX NG 模式,将其集成到非常宽容的宿主语言中。所谓“高度模块化”是指每个元素定义、元素的属性集、元素的内容模型都被分配了惟一的名称,可以单独扩展。清单 4 说明了处理单个元素定义的过程,使用的是 RELAX NG 紧凑语法。
清单 4. RELAX NG 模块化的元素定义
Common.Attributes = empty
Single.Node.Binding.Attributes = attribute bind { xsd:NCName } |
(attribute model { xsd:NCName }?, attribute ref { xsd:string })
UI.Common.Attributes &=
#host language to add accesskey, navindex, etc. here
attribute appearance { xsd:QName { pattern = "[^:]+:[^:]+" } |
"minimal" | "compact" | "full" }?
Select = element select { Select.Attributes, Select.Content }
Select.Attributes &=
Common.Attributes,
Single.Node.Binding.Attributes,
UI.Common.Attributes,
attribute selection { "open" | "closed" }?,
attribute incremental { xsd:boolean }?
Select.Content = Label, List.UI.Common.Content, UI.Common.Content
|
注意,即使包含有标为
xsd:NCName 的
IDREF 属性,也只对属性进行词法验证。如前所述,实际的
ID -
IDREF 连接检查是在不同层次上进行的。分别定义不同成分的主要好处是容易扩展,比如为所有的表单控件添加
class 属性。
事实上,这正是宿主语言模式要做的事。验证程序的这一部分仍在开发之中,但清单 5 说明了如何定义宿主语言。
清单 5. XForms + 宿主语言定义
Common.Attributes &=
attribute id { xsd:NCName }?,
attribute xml:id { xsd:NCName }?,
attribute class { xsd:NMTOKENS }?
|
如果包含在 XForms 的主模式中,
清单 5 中所示代码就可以扩展 XForms 模式,使得日常所用结构(如
class 和
id 属性)能够通过验证。因为包含的宿主语言片段基本上没有任何限制,这一部分还需要根据具体的用法进一步调整,如上述代码中的通配符所示。
验证程序工作的过程中将运行结果记录到内存中的一个 XML 文件中。最后使用 XSL 转换将结果转化成 HTML,并通过网络发送出去。
相关标准
名称空间一直是一个微妙的话题,对于作者而言更是如此。标准可以使事情更容易,针对这一问题,有两种不同的正在开发之中的标准。
一组标准称为 Document Schema Definition Languages(文档模式定义语言或 DSDL,请参阅
Resources),目前它们都朝着成为 ISO Final
Draft International Standard 的方向发展,进展各不相同。当前这组标准被分成十个部分,DSDL 意味着默认了整个验证问题的复杂性。其中包括 RELAX NG 的定义(第 2 部分)、Schematron(第 3 部分)和一种从大型文档中选择验证部分的类似 XForms 岛的机制(第 4 部分)。DSDL 的其他部分涉及到各种不同的领域,像字符指令表验证、结合不同模式语言的方法等。
另外一种相关标准和工具集是 OASIS 的 CAM,或者“Content Assembly Mechanism(内容组装机制)”。这项技术可以使用业务规则定义、验证和组合文档,从而把模式片段组织起来,定义更大的复合文档。
总而言之,混合名称空间验证是 XML 开发中一个值得挖掘的领域。这个 XForms 验证程序仍在开发之中,也是一个很好的学习机会。
参考资料
关于作者
对本文的评价
|