WEB开发网
开发学院WEB开发Jsp Java 中的 XML:Java 文档模型的用法 阅读

Java 中的 XML:Java 文档模型的用法

 2008-01-05 19:27:57 来源:WEB开发网   
核心提示:简要探讨 java 中不同 xml 文档模型的工作原理 Dennis M. Sosnoski(dms@sosnoski.com)总裁,Sosnoski Software Solutions, Inc.2002 年 2 月 在本系列的第一篇文章中,Java 中的 XML:Java 文档模型的用法,我研究了一些用 Java

简要探讨 java 中不同 xml 文档模型的工作原理

Dennis M. Sosnoski(dms@sosnoski.com)
总裁,Sosnoski Software Solutions, Inc.
2002 年 2 月

 

在本系列的第一篇文章中,我研究了一些用 Java 编写的主要的 XML 文档模型的性能。但是,在开始选择这种类型的技术时,性能只是问题的一部分。使用方便至少是同样重要的,并且它已是一个主要理由,来支持使用 Java 特定的模型,而不是与语言无关的 DOM 。

为切实了解哪个模型真正的作用,您需要知道它们在可用性程度上是如何排名的。本文中,我将尝试进行这个工作,从样本代码开始,来演示如何在每个模型中编码公共类型的操作。并对结果进行总结来结束本文,而且提出了促使一种表示比另一种更轻易使用的一些其它因素。

请参阅以前的文章(请参阅参考资料或本文“内容”下的便捷链接)来获取这个对比中使用的各个模型的背景资料,包含实际的版本号。还可以参阅“参考资料”一节中关于源代码下载、到模型主页的链接以及其它相关信息。

代码对比
在对不同文档表示中用法技术的这些对比中,我将显示如何在每种模型中实现三种基本操作:

  • 根据输入流构建文档
  • 遍历元素和内容,并做一些更改:
    • 从文本内容中除去前导和尾随的空白。
    • 假如结果文本内容为空,就删除它。
    • 否则,将它包装到父元素的名称空间中一个名为“text”的新元素中。
  • 将已修改的文档写入输出流

这些示例的代码是以我在上篇文章中使用的基准程序为基础的,并进行了一些简化。基准程序的焦点是为了显示每个模型的最佳性能;对于本文,我将尝试显示在每种模型中实现操作的最简便方法。

我已经将每个模型的示例结构化为两个独立的代码段。第一段是读取文档、调用修改代码和编写已修改文档的代码。第二段是真正遍历文档表示和执行修改的递归方法。为避免分散注重力,我已在代码中忽略了异常处理。

您可以从本页底部参考资料一节链接到下载页,以获取所有样本的完整代码。样本的下载版本包括一个测试驱动程序,还有一些添加的代码用于通过计算元素、删除和添加的个数来检查不同模型的操作。

即使您不想使用 DOM 实现,但还是值得浏览下面对 DOM 用法的描述。因为 DOM 示例是第一个示例,所以与后面的模型相比,我用它来探究有关该示例的一些问题和结构的更具体信息。浏览这些内容可以补充您想知道的一些细节,假如直接阅读其它模型之一,那么将错过这些细节。

DOM
DOM 规范涵盖了文档表示的所有类型的操作,但是它没有涉及例如对文档的语法分析和生成文本输出这样的问题。包括在性能测试中的两种 DOM 实现,Xerces 和 Crimson,对这些操作使用不同的技术。清单 1 显示了 Xerces 的顶级代码的一种形式。

清单 1. Xerces DOM 顶级代码

 1  // parse the document from input stream ("in")
 2  DOMParser parser = new DOMParser();
 3  parser.setFeature("http://xml.org/sax/features/namespaces", true);
 4  parser.parse(new InputSource(in));
 5  Document doc = parser.getDocument();

 6  // recursively walk and modify document
 7  modifyElement(doc.getDocumentElement());

 8  // write the document to output stream ("out")
 9  OutputFormat format = new OutputFormat(doc);
10  XMLSerializer serializer = new XMLSerializer(out, format);
11  serializer.serialize(doc.getDocumentElement());

正如我在注释中指出的,清单 1 中的第一块代码(第 1-5 行)处理对输入流的语法分析,以构建文档表示。Xerces 定义了 DOMParser 类,以便从 Xerces 语法分析器的输出构建文档。InputSource 类是 SAX 规范的一部分,它能适应供 SAX 分析器使用的几种输入形式的任何之一。通过单一调用进行实际的语法分析和文档构造,假如成功完成了这一操作,那么应用程序就可以检索并使用已构造的 Document

第二个代码块(第 6-7 行)只是将文档的根元素传递给我马上要谈到的递归修改方法。这些代码与本文中所有文档模型的代码在本质上是相同的,所以在剩余的示例中我将跳过它,不再做任何讨论。

第三个代码块(第 8-11 行)处理将文档作为文本写入输出流。这里,OutputFormat 类包装文档,并为格式化生成的文本提供了多种选项。XMLSerializer 类处理输出文本的实际生成。

Xerces 的 modify 方法只使用标准 DOM 接口,所以它还与任何其它 DOM 实现兼容。清单 2 显示了代码。

清单 2. DOM Modify 方法

 1  PRotected void modifyElement(Element element) {

 2    // loop through child nodes
 3    Node child;
 4    Node next = (Node)element.getFirstChild();
 5    while ((child = next) != null) {

 6      // set next before we change anything
 7      next = child.getNextSibling();

 8      // handle child by node type
 9      if (child.getNodeType() == Node.TEXT_NODE) {

10        // trim whitespace from content text
11        String trimmed = child.getNodeValue().trim();
12        if (trimmed.length() == 0) {

13          // delete child if nothing but whitespace
14          element.removeChild(child);

15        } else {

16          // create a "text" element matching parent namespace
17          Document doc = element.getOwnerDocument();
18          String prefix = element.getPrefix();
19          String name = (prefix == null) ? "text" : (prefix + ":text");
20          Element text = 
21            doc.createElementNS(element.getNamespaceURI(), name);

22          // wrap the trimmed content with new element
23          text.appendChild(doc.createTextNode(trimmed));
24          element.replaceChild(text, child);

25        }
26      } else if (child.getNodeType() == Node.ELEMENT_NODE) {

27        // handle child elements with recursive call
28        modifyElement((Element)child);

29      }
30    }
31  }

清单 2 中显示的方法所使用的基本方法与所有文档表示的方法相同。 通过一个元素调用它,它就依次遍历那个元素的子元素。假如找到文本内容子元素,要么删除文本(假如它只是由空格组成的),要么通过与包含元素相同的名称空间中名为“text”的新元素来包装文本(假如有非空格的字符)。假如找到一个子元素,那么这个方法就使用这个子元素,递归地调用它本身。

对于 DOM 实现,我使用一对引用:childnext 来跟踪子元素排序列表中我所处的位置。在对当前子节点进行任何其它处理之前,先装入下个子节点的引用(第 7 行)。这样做使得我能够删除或替代当前的子节点,而不丢失我在列表中的踪迹。

当我创建一个新元素来包装非空白的文本内容(第 16-24 行)时,DOM 接口开始有点杂乱。用来创建元素的方法与文档关联并成为一个整体,所以我需要在所有者文档中检索当前我正在处理的元素(第 17 行)。我想将这个新元素放置在与现有的父元素相同的名称空间中,并且在 DOM 中,这意味着我需要构造元素的限定名称。根据是否有名称空间的前缀,这个操作会有所不同(第 18-19 行)。利用新元素的限定名称,以及现有元素中的名称空间 URI,我就能创建新元素(第 20-21 行)。

一旦创建了新元素,我只要创建和添加文本节点来包装内容 String,然后用新创建的元素来替代原始文本节点(第 22-24 行)。

清单 3. Crimson DOM 顶级代码

 1  // parse the document from input stream
 2  System.setProperty("javax.xml.parsers.DocumentBuilderFactory",
 3      "org.apache.crimson.jaXP.DocumentBuilderFactoryImpl");
 4  DocumentBuilderFactory dbf = DocumentBuilderFactoryImpl.newInstance();
 5  dbf.setNamespaceAware(true);
 6  DocumentBuilder builder = dbf.newDocumentBuilder();
 7  Document doc = builder.parse(in);

 8  // recursively walk and modify document
 9  modifyElement(doc.getDocumentElement());

10  // write the document to output stream
11  ((XmlDocument)doc).write(out);

清单 3 中的 Crimson DOM 示例代码使用了用于语法分析的 JAXP 接口。JAXP 为语法分析和转换 XML 文档提供了一个标准化的接口。本示例中的语法分析代码还可以用于 Xerces(对文档构建器类名称的特性设置有适当的更改)来替代较早给定的 Xerces 特定的示例代码。

在本示例中,我首先在第 2 行到第 3 行中设置系统特性来选择要构造的 DOM 表示的构建器工厂类(JAXP 仅直接支持构建 DOM 表示,不支持构建本文中讨论的任何其它表示)。仅当想选择一个要由 JAXP 使用的特定 DOM 时,才需要这一步;否则,它使用缺省实现。出于完整性起见,我在代码中包含了设置这个特性,但是更普遍的是将它设置成一个 JVM 命令行参数。

接着我在第 4 行到第 6 行中创建构建器工厂的实例,对使用那个工厂实例构造的构建器启用名称空间支持,并从构建器工厂创建文档构建器。最后(第 7 行),我使用文档构建器来对输入流进行语法分析并构造文档表示。

为了写出文档,我使用 Crimson 中内部定义的基本方法。不保证在 Crimson 未来版本中支持这个方法,但是使用 JAXP 转换代码来将文档作为文本输出的替代方法需要诸如 Xalan 那样的 XSL 处理器的。那超出了本文的范围,但是要获取具体信息,可以查阅 Sun 中的 JAXP 教程。

JDOM

Tags:Java XML Java

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接