级别: 初级 David Mertz,博士 (mertz@gnosis.cx), 转换专家, Gnosis Software, Inc.
2003 年 11 月 01 日 在上一部分中,David 考察了不同程序语言的 XML 库以及各自不同的优点和弱点。这一次,他考察了 Lisp/Scheme 语言家族,这些语言在教学中和纯化论者之间仍然受到欢迎。所讨论的有 Scheme(一个高效的纯函数性的解析器)的 SSAX 库、SXML 树库(类似于 DOM)以及相关的专门用于处理这些库的工具 SXLT 和 SXPath。David 探讨了在强函数语言中解析的好处,并比较了 SSAX 和其他语言的 XML 库。
我知道,一些纯麦芽威士忌的内行会为一种苏格兰威士忌是否比另一种好而争论不休。但是作为一个很少喝酒的人,我很难完全理解这些狂热者的思想倾向。
对于 Lisp 和 Scheme 编程,我的看法差不多。我可以告诉您这是一个充满精巧和智慧的领域,但不知何故,无论是波兰表示法(前缀表示法)和无休无止的括号,还是规避代码和数据之间语义差别的热切追求,仍然令我感到陌生。尽管如此,我还是对说明这些语言如何进行 XML 处理抱有足够的兴趣。
什么是 SXML?
在 Lisp/Scheme 的爱好者中,观察 Scheme 的
SSAX
库的出发点,是注意到 XML 在语义上基本等同于嵌套的面向列表的数据结构,而这种结构是类 Lisp 语言与生俱来的。可以用 XML 表示的任何东西都能直接用 SXML 表示—— Scheme 列表嵌套着与原始 XML 相同的数据。另外,Scheme 还拥有丰富的列表和树处理函数库,以及专注于处理这些结构的历史。也许,天生就适合处理 XML。
第一步最好是通过具体的形式来看看 SXML。树是 XML 的底层抽象——Infoset,但是抽象信息采用特殊的语义形式。下面是我最近撰写的另一篇文章经过大大简化(但仍然是结构良好)的版本:
清单 1. 包含大多数 XML 特征的 XML 文档
$ cat example.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/css"
href="http://gnosis.cx/publish/programming/dW.css" ?>
<dw-document xmlns:dw="http://www.ibm.com/developerworks/">
<title>The Twisted Matrix Framework</title>
<author name="David Mertz, Ph.D.">
<bio>David thinks it's turtles all the way down...</bio>
</author>
<docbody>
<dw:heading refname="" toc="yes">Introduction</dw:heading>
<p>
Sorting through Twisted Matrix is reminiscent of the old story
about blind men and elephants. Twisted Matrix has many
capabilities within it, and it takes a bit of a gestalt switch
to <i>get</i> a good sense of why they are all there.
</p>
</docbody>
</dw-document>
|
转化到 SXML 后,本文档看起来是这样:
清单 2. 具有大多数 XML 特征的 SXML 文档
$ ./xml2sxml example.xml
(*TOP* (*PI* xml "version=\"1.0\" encoding=\"UTF-8\"")
(*PI* xml-stylesheet
"type=\"text/css\"
href=\"http://gnosis.cx/publish/programming/dW.css\" ")
(dw-document
(title "The Twisted Matrix Framework")
(author
(@ (name "David Mertz, Ph.D."))
(bio "David thinks it's turtles all the way down..."))
(docbody
(http://www.ibm.com/developerworks/:heading
(@ (toc "yes") (refname ""))
"Introduction")
(p "
Sorting through Twisted Matrix is reminiscent of the old story
about blind men and elephants. Twisted Matrix has many
capabilities within it, and it takes a bit of a gestalt switch
to "
(i "get")
" a good sense of why they are all there.
"))))
|
您会发现 XML 和 SXML 之间一些有趣的差别,但也会发现两者在任何方面都存在明显而直接的对应关系。一些差异相对来说很小,比如圆括号代替了尖括号;而另一些则是针锋相对的。比如对于后者,
SSAX
的缔造者 Oleg Kiselyov 认为,减少关闭括号的结束标签使语法更简洁(请参阅
参考资料)。而 XML 的设计者走了另外一条路,去掉了 SGML 中的标签缩减选项。他们也许错了,但是明确的结束标签没有了,因为它们的可能性被忽视了。
但是,SXML 通过一些方式在不损失信息的情况下修正了 XML 中某些不雅观的地方。具体来讲,属性和元素内容的区别对多数 XML 开发者都感觉有随意性,被 SXML 用一种精巧的方式消除了。属性集合就是另外一个嵌套列表,只不过恰好以名称
@ 开始,这个名称在 XML 标识符中被方便地禁止了。事实上,SXML 文档非常类似于规避了属性的 XML文档,但有时候在其它元素内嵌套了
<@> 子元素。在 SXML 中引用刚好命名为
@ 的子女与筛选任何其他标签名没有区别。有意思的是,注意到我的
gnosis.xml.objectify
和 RELAX NG 样式表都试图实现类似的属性和元素的同质性。
处理指令和注释也简化了标签名,这在 XML 中是无法实现的:前者用
PI ,后者用
COMMENT 。与大多数的 Lisp 语言一样,只要一种基本数据结构就表示了一切。
SXML 格式中的命名空间也非常有意思。当像上面这样生成 SXML 时,完整的命名空间 URI 变成了标签名的一部分。不过,也可以使用可选的
NAMESPACES 标签简化命名空间的引用,基本上与 XML 一样,但是使用该标签您必须手工编写 SXML 或者改进转换工具。
使用 SSAX 库
从上面的描述看,SXML 似乎是 XML 许多简写符号(如 PYX、SOX、SLiP 和 XSLTXT)的另外一种。区别在于 SXML 不仅(毫无疑问)简化了读和编辑,而且在 Scheme 中已经有自己的代码。这种格式不需要特殊的或相关的解析器。
作为使用
SSAX
库的第一个例子,看一看上面使用的
xml2sxml
应用程序:
清单 3. xml2sxml 转换脚本
#!/sw/bin/guile -s
!#
(load "sxml-all.scm")
(pp (SSAX:XML->SXML (open-input-file (cadr (command-line))) '() ))
|
这段脚本不长,有吗? 当然,这依赖于一些您经常使用的
load 函数:
清单 4. sxml-all.scm 支持文件
(load "libs/pp.scm")
(load "libs/guile/common.scm")
(load "libs/guile/myenv.scm")
(load "libs/guile/parse-error.scm")
(load "libs/util.scm")
(load "libs/input-parse.scm")
(load "libs/look-for-str.scm")
(load "sxml/ssax.scm")
(define (port? x) (or (input-port? x) (output-port? x)))
|
我可能不需要每次都使用全部这些
load 函数,它们加载了完整的
SSAX
函数集合。最后一行有点古怪,不知道由于什么原因,
SSAX
所用的函数
port? ,在我用
fink 安装到 Mac OSX 上的 Guile 版本中不能使用。但是,我增加的定义又直接超出了 Guile 手册的范围。我猜想也许其他的 Scheme 系统没有这个问题。
函数
SSAX:XML->SXML 生成的数据结构是一种常见列表,可以使用所有常规的 Scheme 函数处理。比如:
清单 5. 导航 SXML 结构
guile> (load "sxml-all.scm")
guile> (define sxml
(SSAX:XML->SXML (open-input-file "example.xml") `()))
guile> (list-ref sxml 1)
(*PI* xml "version=\"1.0\" encoding=\"UTF-8\"")
guile> (cadr (list-ref sxml 3))
(title "The Twisted Matrix Framework")
guile> (eq? (car (list-ref sxml 3)) `dw-document)
#t
|
使用 SSAX
尽管 SXML 表示只是一个树,可以通过一般的 Scheme 技术操作和遍历,
SSAX
库还提供了一个方便的宏,称为
SSAX:make-parser ,其工作方式类似于其他程序语言中的 SAX API。这个宏对树遍历进行了一些优化,可以以线性效率——用大 O 表示法为 O(N)——处理给定的 SXML 结构,普通的方法很容易会占用更多的内存和 CPU 时间。(关于大 O 表示法请参阅
参考资料。)
与您在类似 C、C++、Python 或 Perl 中可能用过的真正的 SAX API 不同,
SSAX
是遍历树而不是扫描线性字节流——就是说,SAX 或
expat
,并只是把找到的起始标签和结束标签作为事件回调相关的处理器。如果要跟踪标签出现的相关嵌套和上下文,您必须维护自己的栈或者其他数据结构。与此相对比,在
SSAX
中,每个节点都来自一个父节点,传递并返回一个
seed 。当然,这个
seed 自身本质上是一个数据结构,您可以在每个
NEW-LEVEL-SEED 和
FINISH-ELEMENT 处理器中操作它们——但至少操作是本地的而非全局的。
为了说明
SSAX
是如何工作的,我改进了
SSAX
库的 CVS 目录中所提供的在线例子(请参阅
参考资料)。我将说明如何显示属性和(缩减的)CDATA 的内容。这很可能会促使您编写一个
sxml2xml 工具——奇怪的是
SSAX
没有包含这样的工具,甚至也没有这样的直接的函数或者宏。不过,我将忽略正确的转义、处理指令和其他少数几个方面。
清单 6. Outline 转换脚本
#!/sw/bin/guile -s
!#
(load "sxml-all.scm")
(define (format-attrs attrs)
(if (and (pair? attrs) (pair? (car attrs)))
(string-append " " (caar attrs) "='" (cdar attrs) "'"
(if (> (length attrs) 1)
(format-attrs (cdr attrs)) ""))
""))
(define (tag->string tag)
(if (symbol? tag) tag (cdr tag)))
(define (outline xml-port)
((SSAX:make-parser
NEW-LEVEL-SEED
(lambda (elem-gi attrs namespaces expected-content seed)
(display (string-append
seed ; indent the element name
"<" ; open brace
(tag->string elem-gi) ; print the name of the element
(format-attrs attrs) ; display the attributes
">\n")) ; closing brace, newline
(string-append " " seed)) ; advance the indent level
FINISH-ELEMENT
(lambda (elem-gi attributes namespaces parent-seed seed)
parent-seed) ; restore the indent level
CHAR-DATA-HANDLER
(lambda (s1 s2 seed)
(if (> (string-length s1) 30)
(display
(string-append seed "|" (substring s1 0 30) "...\n")))
seed)
) xml-port ""))
(display (call-with-input-file (cadr (command-line)) outline)))
|
基本部分非常类似于一个 SAX 类。
outline 函数由
SSAX:make-parser 宏生成。允许定义几种事件类型。主要的事件有进入和离开一个元素,取字符数据。有两个支持函数帮助进行处理。
outline 使用的
seed 非常简单,它只是一个字符串,到达树的分支的深度越深,该字符串就越长。当然,您
可以传递所遇到的标签的全部列表——比如对节点进行类似 XPath 那样的分析。CDATA 处理器只是检查是否有足够的 CDATA 值进行显示(至少 30 个字符,任选),然后在与当前元素相同的缩进位置上显示。
NEW-LEVEL-SEED 处理器说明了两个有趣的地方,主要是关于它的两个支持函数。SXML 结构中,每个标签并非都是简单的符号,特别地,命名空间限定的标签就有两个符号。函数
tag->string 检查标签的类型,只显示名称的本地部分,而不包括命名空间。您可以选择其他的方法,这里只是为了测试。
函数
format-attrs 可能更多表现了 Scheme 中一般的递归编程,而不仅仅局限于
SSAX
。此外,标签可能有 0 个、1 个或多个属性,对每种情况都需要返回一个字符串。真正的 Scheme 程序员可能会提出更简捷的方法,非常欢迎在本专栏的
讨论论坛中提出建议。
现在来考虑输出,假定是前面的 XML 文档。顺便提一下,不能处理的 PI 将会产生警告,因此我重定向了
STDERR 以忽略这些警告:
清单 7. outline 显示的 example.xml
$ ./outline example.xml 2>/dev/null
<dw-document>
<title>
<author name='David Mertz, Ph.D.'>
<bio>
|David thinks it's turtles all ...
<docbody>
<heading refname='' toc='yes'>
<p>
|
Sorting through Twisted...
<i>
| a good sense of why they are ...
|
还有什么呢?
除了与 SAX 和 DOM 的等价物(Scheme 与生俱来的嵌套列表),
SSAX
还有自己的
SXPath
和
SXSLT
组件。这里没有足够的地方展开讨论它们了,但还值得简要地提一提。
不幸的是,在 Oleg Kiselyov 的文章 "XML, XPath, XSLT implementations as SXML, SXPath
and SXSLT"(请参阅
参考资料)中所讨论的
SXPath
和
SXSLT
函数与宏,没有包含在
SSAX
发布的版本中,至少在 Guile(可以使用几种 Scheme 系统的其它版本)中没有。容易下载的那些只能用于少数 Scheme 系统,而且版本也不清楚。根据上面提到的那篇文章,
SXPath
非常类似于 XPath。比如,下面的两个表达式都展开为一个选择函数:
清单 8.
SXPath
表达式,原生原版
((sxpath '(// TAF VALID @ Trange *text)) document)
((txpath "//TAF/VALID/@TRange/text()") document)
|
经过宏展开后变为:
清单 9. 全路径的选择器函数
((node-join
(node-closure (node-typeof? 'TAF))
(select-kids (node-typeof? 'VALID))
(select-kids (node-typeof? '@))
(select-kids (node-typeof? 'TRange))
(select-kids (node-typeof? '*text*)))
document)
|
当然,用这种展开的形式允许在类 XPath 选择器中编程完成任何计算——您能够用 Scheme 编写的任何东西。
SXSLT 在概念上类似。用 Scheme 形式编写的样式表语义上类似于 XSLT。但是灵活性与
HaXml
相近,在任何具体的转换规则中都可以嵌套任意的额外代码。当然,特别是 XSLT 引擎,常常伴随着用 JavaScript、VB 或其他语言编写的实现特殊功能的外部函数
API。但是对于 SXSLT,定制函数与转换样式表元素完全用同样的 Scheme 语言编写。
关于 SSAX 的最后思考
我稍微有点喜欢
SSAX
库了,我猜想进一步熟悉 Lisp/Scheme 编程后也许会更喜欢。它具备了我在其他部分所讲述的 XML 原生库的许多优点:
gnosis.xml.objectify
、
REXML
、
XML::Grove
和
HaXml
,等等。把
XML 放到所用程序语言的
完全不同的数据对象中有很多东西值得论述。
简而言之,
SSAX
还有许多地方有待完善。很难指出能下载什么,各个部分能用于何种 Scheme 系统。这篇文章有点不协调、不完整——文章的大部分集中到理论上,更多地讨论了抽象的目标和概念而不是具体的用法和
API。作为对 Scheme 能够做什么、使用函数性技术的说明,本文很有意思,但是如果有某种很容易下载并安装和
直接使用
的东西就更好了。
参考资料
关于作者
对本文的评价
|