级别: 初级 David Mertz,博士 (mertz@gnosis.cx), 简化工程师, Gnosis Software, Inc.
2002 年 2 月 01 日 XML 是一种十分简单的格式。它使用普通 Unicode 文本而不是二进制编码,并且其所有结构都是由看起来可预见的标记声明的。尽管如此,XML 语法中的规则还是很多,以致于需要一个仔细调试过的解析器来处理 XML 文档 ― 而且每个解析器都有其独特的编程风格。一个替代方法是使 XML 更为简单。开放源码 PYX 格式是一种表示 XML 文档的纯面向行的格式,它使得能够以普通文本工具(诸如 grep、sed、awk、wc 和 UNIX 通用的工具集)更容易地处理 XML 文档。
经常光顾本专栏的读者几乎肯定会注意到我对那些最受欢迎的处理 XML 文档的技术有所不满。讨论我的 Python xml_objectify 修改的那些文章大多针对 DOM 的复杂性。我对 Haskell HaXml 库的介绍则主要针对我认为有些“笨拙”的 XSLT。类似地,这次我发现 SAX 比 SAX 解决的问题所需要的功能要“累赘”得多。
与 DOM 或 XSLT 相比较,SAX API 是更加轻量级的 ― 不论是在计算机资源方面还是在更重要的编程人员所花费的工夫和学习曲线方面。尽管如此,即便是 SAX 也需要 XML 程序员利用解析器库并符合回调 API。XML 文档内的数据还不是太复杂,不需要这样的功能需求。我认为应该有一种更简单的方法来处理 XML 文档;特别是当人们处理 XML 时,他应该能够更自由地使用各种常见的工具和技术。
PYX 格式
PYX 格式是 XML 文档的一种面向行的表示,它源于 SGML ESIS 格式。PYX 实际上不是 XML,但它却能够使用常用的文本处理工具,以一种更简单的方式来表示 XML 文档内的所有信息。而且,需要时还可以将 PYX 文档转换回 XML。PYX 文档的大小大致同对应的 XML 版本相同(有时大一点儿,有时小一点儿),因此 XML 和 PYX 之间的存储和传输考虑事项没有多少差异。
描述和理解 PYX 格式极其简单。每行的第一个字符标识该行的内容类型。虽然后续行可能含有相同的内容类型,但是内容并不直接跨多行。至于标记属性,只使用空格而不是使用额外的引号来分隔属性名和它的值。前缀字符是:
( 开始标记
) 结束标记
A 属性
- 字符数据(内容)
? 处理指令
|
PYX 格式中的几个字符是转义字符。出现在字符数据中的换行字符总是用
\n 转义码在一个单独的内容行中表示。制表符使用
\t 序列转义码表示,不管原始 XML 文档中出现对应制表符的位置,它都可以出现在常规(非新行)内容行中。用于转义字符的反斜杠本身转义成
\\ 。注意,空格不被转义,而且通常一些内容行整个都是空格(在文本查看器里,从视觉上可能看不出来)。行由特定于平台的换行分隔符终止。
面向行的文本处理工具和技术的广泛使用、便利性及熟悉度是 PYX 的动力。例如,GNU 文本实用程序(textutils)包括诸如 wc、tail、head 和 uniq 等工具;其它为人熟悉的文本处理工具有 grep、sed、awk 以及更复杂的 perl 和其它脚本编制语言。这类工具通常需要换行-分隔符记录并依靠正则表达式模式来标识文本的各个部分。碰巧,XML 并不能很好地符合这两个要求。
使用 PYX
让我们看一看实际使用的 PYX。PYX 库可用于多种编程语言,但是大多数时候它只在使用命令行工具 xmln 和 xmlv 时最有用。前者是一个非验证转换工具,第二个则添加了 DTD 的验证。在这一过程中,expat 和 rxp 解析器被编译进了这些工具,但用户不必为这些解析器的 API 操心。
清单 1. 文档的 XML 和 PYX 版本
[PYX]# cat test.xml
<?xml version="1.0"?>
<!DOCTYPE Spam SYSTEM "spam.dtd" >
<!-- Document Comment -->
<?xml-stylesheet href="test.css" type="text/css"?>
<Spam flavor="pork" size="8oz">
<Eggs>Some text about eggs.</Eggs>
<MoreSpam>Ode to Spam (spam="smoked-pork")</MoreSpam>
</Spam>
[PYX]# ./xmln test.xml
?xml-stylesheet href="test.css" type="text/css"
(Spam
Aflavor pork
Asize 8oz
-\n
-
(Eggs
-Some text about eggs.
)Eggs
-\n
-
(MoreSpam
-Ode to Spam (spam="smoked-pork")
)MoreSpam
-\n
)Spam |
您应该注意到该转换中没有原来 XML 文档中的 DOCTYPE 声明和注释。出于许多目的,这一点并不重要(解析器也经常废弃这些信息)。PYX 格式同 XML 格式比起来允许人们轻易地对文档提出特别的问题。例如:样本文档中的所有属性值是什么?使用 PYX,我们可以简单地询问:
清单 2. 使用 PYX 格式的特别查询(属性)
[PYX]# ./xmln test.xml | grep "^A" | awk '{print $2}'
pork
8oz |
从原来的 XML 中获得这个答案是一个巨大的难题:您要么必须创建一个完整的程序来调用解析器并查找标记属性字典,要么给出一个复杂的正则表达式,用它来查找您感兴趣的信息。麻烦的是
<MoreSpam> 元素的内容,它含有看起来十分象是标记属性的内容,但实际却不是。
这里还有一项任务,PYX 可以使之简化:让我们试着转储 XML 文档中的非空内容行。可以使用 SAX 实现这一点,但是这么做需要编写一个带有
characters() 处理程序的小应用程序以及其它几个处理程序的空框架。我们可能喜欢类似应用于 HTML 文件的
lynx -dump 这样的实现 ― 换句话就是 one liner。一种可能性是:
清单 3. 使用 PYX 格式的特别查询(内容)
[PYX]# ./xmln test.xml | grep '^-[^\n ]' | sed s/^-//
Some text about eggs.
Ode to Spam (spam="smoked-pork") |
Sean McGrath 的文章(参阅
参考资料)有另外的类似示例。
转回 XML
PYX 格式十分简单以致于任何有能力的程序员都可以在不到一个小时内编写出一个 PYX2XML 工具。无论每一行是标记、PI 还是内容,它都会确却地告诉您需要输出什么。
PYX2XML 转换略微有些状态。尤其是在碰到开始标记时,随后的(无限多)行可能含有该标记的属性。输出了这些属性(如果有的话)后,需要一个结束尖括号。当碰到开标记时,转换实用程序还不知道有多少属性(如果有的话)。因此,需要将“查找属性(looking-for-attributes)”状态设置成 true 或 false。
遗憾的是,尽管 PYX2XML 转换十分简单,pyx2xml.py 工具(可以在 Sourceforge 的 Pyxie Web 站点获得它 ― 参阅
参考资料)还存在许多令人担忧的缺陷。它会在 XML 中形成空格,这看起来不等,但却是格式良好的。更糟的是,其中还有实际编程错误,这会破坏短的脚本。这里让我为读者提供一个工作实现:
清单 4. PYX-至-XML 转换的 Python 脚本
import sys, os, xreadlines
unescape = lambda s: s.replace(r'\t','\t').replace(r'\\','\\')
write = sys.stdout.write
get_attrs = 0
for line in xreadlines.xreadlines(sys.stdin):
if get_attrs and line[0] <> 'A':
get_attrs = 0 # End of tag attribues
write('>')
if line[0] == '?': # Proc Instr
write('<?%s?>\n' % line[1:-1])
elif line[0] == '(': # Open tag
write('<%s' % line[1:-1])
get_attrs = 1
elif line[0] == 'A': # Tag attrib
name,val = line[1:].split(None, 1)
write(' %s="%s"' % (name, unescape(val)[:-1]))
elif line[:3] == r'-\n': # Newline
write(os.linesep)
elif line[0] == '-': # Misc content
write(unescape(line[1:-1]))
elif line[0] == ')': # Close tag
write('</%s>' % line[1:-1])
|
其它考虑事项
Pyxie 项目页面含有一个称为 pyxie 的 Python 模块,该模块含有一些类,这些类同用 PYX 编码的基于树或基于事件样式的文档一起工作。如果您对于许多使用采纳 PYX 格式(并且还使用 Python),那么可能值得使用其中的一些类。但是在某种程度上,我觉得这些类有些不得要领。PYX 格式的优点在于其简洁性以及可以使用面向行的工具进行处理。
如果您想要采用 XML 文档的内存中树状表示,那么可以用 DOM 来实现。如果想要使用比 DOM API 更简单的 API,那么您可以使用诸如 Python 的 xml_objectify、Perl 的 XML::Parser、Ruby 的 REXML 以及 Java 的 JDOM 这样的模块来获取树状结构。pyxie 具有类似的用途,但是没有实际的优势。同样,如果您想对 XML 文档进行十分普通的面向事件的处理,您也可以使用 SAX 或 expat;pyxie 在这里没有什么特别的优势。
也有这种时候:您想以一种对数据的层次结构有点敏感的方式处理 PYX 文档。从某种意义来说,这会再次带来与使用 SAX 或 DOM 一样的复杂性,失去了 PYX 的优点。但是在最初级别的复杂性上,为了以层次方式处理 PYX 所需要唯一的数据结构是一个标记堆栈。这是一个十分简单的数据结构需求。
例如,在顺序地处理上面提及的测试文档中,您将执行下列堆栈操作:
1. Push "Spam"
2. Push "Eggs"
3. Pop ("Eggs")
4. Push "MoreSpam"
5. Pop ("MoreSpam")
6. Pop ("Spam") |
在这个简单的例子中,堆栈的深度决不会超过两项,而堆栈的深度通常可以是任意的。确定何时压入何时弹出十分简单:碰到开始标记行就压入;碰到结束标记行就弹出。弹出操作甚至无需知道结束标记字符串,因为堆栈总是弹出最上面的东西。实际上,通过不使用结束标记字符串,PYX 格式也不会丢失任何信息(但是,这样就丢失了自我标识结束标记无
需堆栈记数的便利;例如,编写 PYX2XML 会更难)。
在一行一行地处理 PYX 文件过程中的任一点上,单个堆栈就包含了应该了解的关于当前行层次化环境的所有信息。甚至可以通过简单地集中到堆栈来构造一个 XPath 样式的限定符;对内容或属性的潜在的某种操作可能依赖于这一环境。这种处理略微超出了通常基本文本实用程序的处理能力,但是尽管如此,它仍然比 XML 解析器/接口极度膨胀的 API 要更特别、灵活及简单。
结束语
遗憾的是,XML API 的设计者们基本上忘记了 KISS 原则(“保持简单、愚蠢”("Keep It Simple, Stupid"))。的确,在一些应用程序中,DOM、SAX 或 XSLT 全面的功能和复杂性可以保证它们的正常运行,有时,这种全面的功能和复杂性甚至是必需的。但是对于许多 XML 应用程序来说,这些受欢迎的 API 却给经常使用它们的日常程序员设置了不必要的障碍。幸运的是,有些方法可以避免额外的复杂性,本专栏已经试图并且将继续试图向读者提供使简单的事情更简单的方法。
参考资料 学习
获得产品和技术
-
IBM 试用版软件:使用 IBM 试用版软件构建您的下
一个开发项目,这些试用版软件可直接从 developerWorks 下载获得。
讨论
关于作者  | 
|  | David Mertz 相信大多数 XML 作者仅仅只是解释了 API;他的目的是改变这一点(或至少避开这一点)。可以通过 mertz@gnosis.cx 与 David 联系;在
http://gnosis.cx/publish/上详细介绍了他的生活。欢迎对现在、以前或将来的专栏文章提出意见和建议。
|
对本文的评价
|