IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope:Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  XML  >

XML 问题: XOM Java XML API

严格无误的面向树的 XML 模型

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

David Mertz, Ph.D. (mertz@gnosis.cx), 转换专家, Gnosis Software, Inc.

2003 年 12 月 01 日

在本期,David 考察了 Elliotte Rusty Harold 的 XOM。简而言之,这不过是另一种 面向对象的 XML API,有点类似 DOM 的风格,但是有一些特性使得 XOM 与众不同,Harold 认为这些特性是重要的设计元素。其中最主要的一点是严格保持内存中对象的不变性,从而使 XOM 实例 总是 能序列化为正确的 XML。此外,与其他 Java XML API 相比,XOM 追求更简单和更正规。您可以在讨论论坛上与作者和其他读者分享您关于本文的想法。

开发人员一般对它们所开发的 XML API 有不同的观点。面向流的 API,如 SAX、 libxml SSAX expat ,强调的是“省”—— 更快、使用更少的内存、更少地考虑整个文档结构。此外,面向流的 API 可以使用非常过程化的风格处理,对于 C 编程而言这非常好,因为对象在 C 中还不是很普遍。

相反,面向树或者面向对象的 API 通常在内存中建立 XML 文档的完整映象,转换成某种面向对象的(或者至少是层次化的,比如 XOM )解释。遍历、筛选和转换 XML 代理对象,在一定程度上可以利用编程语言的原生语法。XSLT 也可以被看作是某种 API,它的函数性/声明性模型与上述两者都不相同,但在本文中我不准备讨论 XSLT。

在面向树的 API 中,涌现出了一些存在分歧的设计目标。像 gnosis.xml.objectify ElementTree REXML SXML XML::Grove JDOM 这样的库,非常注重把 XML 塑造成外表尽可能与各自的编程语言相似的原生对象。每一个的目标都是避免让您想到这样一个事实,即您的数据来自 XML,对您来说它只是另一个对象。

在天平的另一端,DOM 基本上完全回避涉及到任何特定的编程语言,在这些语言中可能调用 DOM 方法。尽管 DOM 的设计者可能有 Java 的技术背景,在其他语言中使用 DOM 和在 Java 中使用相比,没有感到什么不自然的地方。但另一方面,由委员会设计的重负也严重损害了 DOM:它的方法有太多不一致的命名,很少有正交性。更糟的是,DON 对象不能完全 保证 序列化为 XML,有时候您可以在内存中建立非结构良好的 DOM 对象(因此序列化需要额外的检查和操作)。

XOM 最相近的是 DOM,在某种程度上还有 JDOM 。但是, XOM 的目标是,通过全新设计的、重视正交性的、由一位专家(前述的 Harold 先生)集中控制的 API 以纠正 DOM 中的问题。 XOM 主要用于 Java,尽管也有一种 Python 实现(但与其他的 Python 技术相比似乎没有多少优势)。但是, XOM 的主要目标不是 针对 Java 技术,而是 针对 XML。Harold 的目标是用最小的相关对象方法集合捕捉和实施 XML 精确信息集。

XOM 对 XML 的关注体现在两个方面。一方面是不能创建违反 XML 规则的节点 —— 比如不允许的标签名,或者 0 字节的内容(多数 API 都没有检查这种约束)。另一方面, XOM 仅提供在和 XML 本身同一概念层上操作的方法 —— 序列化仅仅作为 XML 序列化,CDATA 部分不作为单独的节点保留,忽略 XML 属性的顺序。

第一个应用程序

为了领略 XOM ,我决定编写一个小应用程序,和 上一篇文章中用 SSAX 编写的相同。 outline 工具程序接受输入的 XML 文档,以大纲的形式生成文档摘要,作为上下文显示长文本段的开头部分。此外,它还从标签名中去掉了名称空间,只显示名称的本地部分。这个工具不是很复杂,也不是多么引人注意,但 确实 涉及到了遍历子树和属性的基本技术。

我用于测试的 XML 文档取自最近的一篇 developerWorks文章,经过了高度删减:


清单 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>

通过这个工具,我希望得到如下的结果:


清单 2. 显示的 example.xml 大纲
$ java Outline example.xml
<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 ...

这确实非常简单,而且和我提供的 SSAX 工具完全相同。





回页首


编写 outline 工具

Outline.java 的主要工作由类 nu.xom.Builder 完成,它根据一个 XML 源创建内存中的 XOM 对象。奇怪的是,我发现只要规定类初始化器为 true XOM 就会坚持进行验证,即使对没有指定 DTD 的 XML 文档也是如此。换句话说,所有这种文档都将抛出 ValidityException 异常(但这也可能取决于安装的 Java XML 解析器)。最好的方法也许是忽略初始化标记,让 XOM 选择最佳的解析器。


清单 3. Outline.java 工具程序
import nu.xom.*;
import java.io.IOException;
public class Outline {
  public static void main(String[] args) {
    try {
      // Use 'Builder(true)' to require validation
      Builder parser = new Builder();
      Document doc = parser.build(args[0]);
      showElement(doc.getRootElement(), 0);
    }
    catch (ValidityException ex) {
      System.err.println(args[0]+" is invalid.");
    }
    catch (ParseException ex) {
      System.err.println(args[0]+" is not well-formed.");
    }
    catch (java.io.IOException ex) {
      System.err.println(args[0]+" cannot be read");
    }
  }
  private static void showElement(Element element, int level) {
    // Show the tag, along with its attributes
    indent(level, "<"+element.getLocalName());
    for (int i=0; i < element.getAttributeCount(); i++) {
      Attribute attr = element.getAttribute(i);
      System.out.print(" "+attr.getLocalName()+"='"+attr.getValue()+"'");
    }
    System.out.println(">");
    // Now loop through child nodes
    for (int i=0; i < element.getChildCount(); i++) {
      Node node = element.getChild(i);
      if (node instanceof Text) {
        String text = node.getValue();
        if (text.length() > 30) {
          indent(level+1, "|"+text.substring(0,30)+"...\n");
        }
      } else if (node instanceof Element) {
        showElement((Element)node, level+1);
      }
    }
  }
  private static void indent(int level, String string) {
    for (int i=0; i < level; i++) { System.out.print("  "); }
    System.out.print(string);
  }
}

其中的结构非常简单。 .showElement() 方法显示每个元素的名称和属性,然后递归到它的孩子,每次递归中都增加一级缩进。

在设计这个工具时,我曾见犯过一个有代表性的错误。 Element 类有一个 .getChildElements() 方法,返回横向的元素列表 —— 枚举的对象排除了其他 Node 。表面上看,使用这个枚举似乎很简单,事实上这个方法也被广泛地使用,因为您可以用给定的名称限制枚举的子元素。由于 Element 还有一个 .getValue() 方法检索 PCDATA,看起来您似乎可以通过每个这样的子元素抓取其中的内容字符串。

不幸的是, .getValue() 的语义完全和我的想法不同: .getValue() 检索给定标签内的 所有 文本 —— 不仅包括它引导的那一部分,还有后面的子标签。比如在上面的例子中, <bio> 元素中的广告词同时也在 <author> 元素的包围之中,于是 author.getValue() 检索到了并不需要的内容。因此,我只能遍历所有的子节点,根据它属于哪种 Node 子类决定怎么处理它。具体而言,对于这个工具程序,我只关心 TextElement ,而不关心 CommentProcessingInstructionDocType 等等。





回页首


创建新的 XML 文档

尽管照我看来,XML API 的主要用处是解析和遍历已有的 XML 文档,有时候您也可能希望在某个程序中创建新的 XML 文档 —— 或者至少修改已有的文档。对于最简单的任务,基本的字符串操作实际上就足够了。但是很容出现程序错误,忘记关闭标签或者转义特殊字符。使用 XOM 创建文档可以保证避免这类错误。

下面是一个简单的例子,大部分取自 XOM 教程:


清单 4. HelloWorld.java
import nu.xom.*;
public class HelloWorld {
  public static void main(String[] args) {
    Element root = new Element("root");
    root.appendChild("Hello World!");
    Attribute foo = new Attribute("foo","bar");
    root.addAttribute(foo);
    Document doc = new Document(root);
    String result = doc.toXML();
    System.out.println(result);
  }
}

输出结果如下:


清单 5. HelloWorld 输出
$ java HelloWorld
<?xml version="1.0"?>
<root foo="bar">Hello World!</root>

除了基本的 .appendChild().addAttribute() 方法, .copy().detach() 方法以及 .remove*() 方法集合,对于重新安排 XOM 树非常有用。每个树和树中的每个节点都有一个 .toXML() 方法,这是 XOM 对象唯一的序列化格式。





回页首


比较

在编写这个小小的 outline 工具时,我对 XOM 与其他 API 相比到底方便在哪里产生了兴趣。因为这个工具曾经在上一篇文章中用 SSAX 编写过,显然可以比较一下。结果,Scheme 版本和 Java 语言版本 —— 分别使用 SSAX XOM —— 基本上使用了差不多的代码行,尽管 Scheme 版本使用了宏和动态类型化。当然,代码风格完全不同,Scheme 实际上使用的字符更少一些(如果忽略 SSAX 版本中大量注释的话)。

但是本专栏的热心读者知道,我曾经鼓吹过 Python —— 特别是我自己的 Gnosis Utilities API。我决定使用 gnosis.xml.objectify 的最新开发版本快速完成一个同样的工具:


清单 6. outline.py 工具程序
        from sys 
        import stdin, stdout, stderr
        from gnosis.xml.objectify 
        import XML_Objectify, \
            make_instance, tagname, content, attributes
XML_Objectify.expat_kwargs[
        'nspace_sep'] = None
        def showNode(node, level=
        0):
    stdout.write(
        "  "*level+
        "<"+tagname(node))
    
        for key,val 
        in attributes(node).items():
        stdout.write(
        " %s='%s'" % (key,val))
    stdout.write(
        ">\n")
    
        for child 
        in content(node):
        
        if isinstance(child, unicode):
            
        if len(child) > 
        30:
                stdout.write(
        "  "*(level+
        1)+
        "|"+child[:
        30]+
        "...\n")
        
        else:
            showNode(child, level+
        1)
showNode(make_instance(stdin))
      

有趣的是,我发现使用 XOM 的 Java 语言版本的长度仍然是大约 2.5 倍(当使用 Shakespeare Hamlet 的大型 XML 版本测试时,速度非常接近,Python 较短的启动时间对测试结果影响很小)。

Java 语言中的多数额外代码都和 Outline.main() 方法中各种异常检查有关。在 Python 中,我可以让内置的异常栈完成这些工作;当然,如果我准备对异常做更有意义的处理,而不仅仅是报告异常,那么 Python 将变得和 Java 语言非常相似。

不过显而易见的是,不论由于何种原因希望使用 Java 技术的程序员,如果明白使用 Python 或 Scheme 中的库能够得到更紧凑的代码,都是有益的。而且 Java language 确实有一些强大之处能够平衡这种额外的冗余。





回页首


结束语

DOM 的真正问题在于对于许多目的而言它 太完美了,有太多的方法,许多方法的目的互相交叉而又没有统一进行命名。这是由于负责规范的委员会和遗留问题造成的。尽管如此, 每个人 手里都拥有了一个 DOM 库 —— 不仅仅是 Java 程序员,还包括许多其他语言的程序员。选择 DOM 太容易了,因为它非常普及,唾手可得。

尽管如果我能够选择编写 Python (或者 Ruby 甚至 Perl)的语言的话,我一般不会选择 Java 语言,但 XOM 在每件事上确实比 DOM 做得好。 XOM 更加准确、更容易学习而且更一致。它的大部分功能这里都没有介绍,不过其他特性保证了它能够与一些有用的 XML 技术结合:XPath、XSLT、 XInclude、连接 SAX 和 DOM 的能力,等等。

如果您正使用 Java 语言进行 XML 开发,而且能够在您的应用程序中包括定制的 LGPL 库,我强烈建议您认真看看 XOM



参考资料



关于作者

David Mertz 希望一切都变得美好。可以通过 mertz@gnosis.cx 与 David 联系;在 http://gnosis.cx/publish/ 上详细介绍了他的生活。欢迎对过去的、这一篇和以后的专栏文章提出意见和建议。请您关注 David 的新书 Text Processing in Python




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

建议?




回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款