Java XML教程(第5章)-JSP技术-3P代码网
繁体中文
设为首页
加入收藏
当前位置:JSP技术首页 >> Java与XML >> Java XML教程(第5章)

Java XML教程(第5章)

2004-10-01 08:26:10  作者:  来源:互联网  浏览次数:43  文字大小:【】【】【
简介:来源:http://d23xapp2.cn.ibm.com/developerWorks/education/xml/xmljava/tutorial/xmljava-1-1.html 第五章 解析器高级功能 概览 我们已经讨论了使用一个 XML 解析器来处理 XML 文档的基础。在本章节,我...
关键字:教程 Java XML

来源:http://d23xapp2.cn.ibm.com/developerWorks/education/xml/xmljava/tutorial/xmljava-1-1.html

第五章 解析器高级功能

概览

我们已经讨论了使用一个 XML 解析器来处理 XML 文档的基础。在本章节,我们将探讨一些高级概念。

首先,我们将从头构建一棵 DOM 树。换而言之,我们将不需要一个 XML 源文件来创建一个 Document 对象。

然后,我们将向您显示如何使用解析器来处理包含在一个字符串的 XML 文档。

接着,我们将向您显示如何操作一棵 DOM 树。我们将对我们示例的 XML 文档操作并对其诗句排序。

最后,我们将展示如何利用如 DOM 和 SAX 标准的接口使得解析器的更改十分容易。我们将向您展示两个使用不同 XML 解析器的示例应用。而其中 DOM 和 SAX 代码没有改变。

从头构建一棵 DOM 树

有时您想要从头构建一棵 DOM 树。要完成这个任务,您创建一个 Document 对象,然后对其添加不同的 Node 对象。

您可运行 java domBuilder 来看一个从头构建一棵 DOM 树的示例应用。该应用重新创建了通过对 sonnet.xml 最初解析而构建出的 DOM 树(但不再创建空格符)。

我们首先创建一个 DocumentImpl 类的实例。此类实现 DOM 定义的 Document 接口。

Document doc = (Document)Class.

forName("com.ibm.xml.dom.DocumentImpl").

newInstance();

domBuilder.java (代码请参考附录2)

这段代码不使用一个 XML 文档来构建一个 DOM 树。当树被构建完后,该代码将树的内容输出到标准输出。

...

Mrs.

Mary

McGoon

1401 Main Street

Anytown

NC

34829

...

添加 Node 到我们的 Document

现在我们有自己的 Document 对象了,我们开始创建 Node。我们第一个要创建的 Node 是一个 元素。我们将创建所有的 Node,然后将每个节点添加到其对应的父母。

注意我们使用 setAttribute 方法来对元素设置其 type 属性的值。

Element root = doc.

createElement("sonnet");

root.setAttribute("type",

h"Shakespearean");

构建您的文档结构

当我们开始构建我们的 DOM 树时,我们将需要构建我们文档的结构。要完成它,我们将需要恰当地使用 appendChild 方法。我们将创建 元素,然后创建在其下的其它元素,接着使用 appendChild 方法将所有这些元素添加到正确的父母。

注意 createElement 是属于 Document 类的一个方法。我们的 Document 对象拥有我们在此创建的所有元素。

最终,注意到我们为所有的元素内容创建 Text 节点。Text 节点是元素的子女,而 Text 节点的父母则被添加到对应的父母下。

Element author =

doc.createElement("author");

Element lastName = doc.

createElement("last-name");

lastName.appendChild(doc.

createTextNode("Shakespeare"));

author.appendChild(lastName);

完成我们的 DOM 树

一旦我们对 元素添加了所有内容,我们需要将它添加到 Document 对象。我们最后一次调用 appendChild 方法,这次是将子元素添加到 Document 对象上。

记住一个 XML 文档只能有一个根(root)元素;如果您要向Document添加多个根元素appendChild 将抛出一个异常。

当我们构建好 DOM 树后,我们构建一个 domBuilder 对象,然后调用它的 printDOMTree 方法来打印 DOM 树。

Element line14 = doc.

createElement("line");

line14.appendChild(doc.

createTextNode("As any ..."));

text.appendChild(line14);

root.appendChild(text);

doc.appendChild(root);

domBuilder db = new domBuilder();

db.printDOMTree(doc);

使用 DOM 对象来避免解析

您可以想象一个 DOM Document 对象作为一个 XML 文档的编译形式。如果您正使用 XML 来在不同方之间传递数据,您将可以通过接受和发送 DOM 对象而不是 XML 源数据来节约时间。

这是最常见的原因您为何需要从头来构建一个 DOM 树。

最坏情况下,您需要在您发送数据前从一棵 DOM 树创建出 XML 源数据,然后在您接受 XML 数据时创建出一棵 DOM 树。直接使用 DOM 对象将节约大量时间。

一个警告:要注意一个 DOM 对象可能比 XML 源数据要大很多。如果您要在一个十分缓慢的连接线路上传递数据,发送较小的 XML 源数据而重新解析数据要比传递大数据更有效。

解析一个 XML 字符串

很有可能您需要解析一个 XML 字符串。IBM 的 XML4J 解析器支持这个功能,尽管您需要将您的字符串转换成一个 InputSource 对象。

第一步是从您的字符串中创建一个 StringReader 对象。一旦完成此步,您可以从 StringReader 创建一个 InputSource 对象。

您可运行 java parseString 来查看代码的运行结果。在示例应用中,XML 字符串是写死的(hardcoded);有许多种方法让您从一个用户或其它机器获得 XML 输入。有了这个技术,您就不再需要将 XML 文档输出到一个文件系统来解析它了。

parseString ps = new parseString();

StringReader sr =

new StringReader("

AlphaBravo...");

InputSource iSrc = new InputSource(sr);

ps.parseAndPrint(iSrc);

parseString.java (参附录2)

这段代码展示了如何解析一个包含 XML 文档的字符串。

排列在一棵 DOM 树中的 Node

为了介绍您如何改变一棵 DOM 树的结构,我们将修改我们的 DOM 示例来排列十四行诗的 元素。有多种 DOM 方法可以用来在 DOM 树中搬移节点。

要查看代码的结果,运行 java domSorter sonnet.xml。它并不会改进诗的韵律,但它真确地排列了 元素。

要开始排列工作,我们将使用 getElementsByTagName 方法来提取在文档中的所有 元素。该方法节约了我们编写代码来遍历整个树的开销。

if (doc != null)

{

sortLines(doc);

printDOMTree(doc);

}

...

public void sortLines(Document doc)

{

NodeList theLines =

doc.getDocumentElement().

getElementsByTagName("line");

...

domSorter.java (参附录2)

这段代码在 XML 文档中查找所有的 元素,然后排序。它展示了如何操作一个 DOM 树。

提取我们的 文本

为了简化代码,我们创建一个 helper 功能,getTextFromLine,用来提取包含在一对 元素中的文本。它简单地找到 元素的第一个子女,如果其是一个 Text 节点就返回其文本。

该方法返回一个 Java String 因此我们的排序过程可以使用 String.compareTo 方法来决定排序的次序。

该段代码实际上应该检查 所有的子女,因为它们可能包含实体(entity)引用 (例如实体 &miss; 可能替代了文本 "mistress")。我们将把这个改进作为读者的一个练习。

public String getTextFromLine(Node

lineElement)

{

StringBuffer returnString =

new StringBuffer();

if (lineElement.getNodeName().

equals("line"))

{

NodeList kids = lineElement.

getChildNodes();

if (kids != null)

if (kids.item(0).getNodeType() ==

Node.TEXT_NODE)

returnString.append(kids.item(0).

getNodeValue());

}

else

returnString.setLength(0);

return new String(returnString);

}

文本排序

现在我们有能力从一个给定的 元素获取文本,我们可以开始排列数据了。由于我们只有 14 个元素,我们将使用冒泡排序。

冒泡排序算法是比较两个相邻数据值,然后如果它们次序不对就交换它们。要完成交换,我们使用 getParentNode 和 insertBefore 方法。

getParentNode 返回任意 Node 的父母;我们使用这个方法来获得当前 的父母 (文档的一个 元素使用 sonnet DTD)。

insertBefore(nodeA, nodeB) 在 nodeB 前插入 nodeA 到 DOM 树中。insertBefore 最重要的特性是如果 nodeA 已经存在于 DOM 树中了,它在插入 nodeB 前将删除该节点。

public void sortLines(Document doc)

{

NodeList theLines =

doc.getDocumentElement().

getElementsByTagName("line");

if (theLines != null)

{

int len = theLines.getLength();

for (int i=0; i < len; i++)

for (int j=0; j < (len-1-i); j++)

if (getTextFromLine(

theLines.item(j)).

compareTo(getTextFromLine(

theLines.item(j+1))) > 0)

theLines.item(j).

getParentNode().insertBefore(

theLines.item(j+1),

theLines.item(j));

}

}

用来操作树的有用的 DOM 方法

除insertBefore 之外,还有其它的 DOM 方法可用来操作树。

parentNode.appendChild(newChild)

将一个节点作为给定父母节点的最后子女添加。调用 parentNode.insertBefore(newChild, null) 完成同样功能。

parentNode.replaceChild(newChild, oldChild)

将 oldChild 用 newChild 来取代。节点 oldChild 必须是 parentNode 的子女。

parentNode.removeChild(oldChild)

将 oldChild 从 parentNode 下删除。

parentNode.appendChild(newChild);

...

parentNode.insertBefore(newChild,

oldChild);

...

parentNode.replaceChild(newChild,

oldChild);

...

parentNode.removeChild(oldChild);

...

关于树操作还需注意的事项

如果您需要删除一个给定节点的所有子女,这要比看上去难多了。这两段在左边的示例代码看起来可以完成任务。但是,在第二个才能完成。第一个示例代码由于 kid 的实例数据在 removeChild(kid) 被调用后就改变了而无法完成任务。

换而言之,for 循环删除了 kid,第一个子女,然后检查 kid.getNextSibling 是否为 null 。由于 kid 刚被删除,它不再有任何同胞了,因此 kid.getNextSibling 是 null。 for 循环只会运行一次。不论 node 有一个或几千个子女,第一段示例代码只是删除第一个子女。要使用第二段示例代码来删除所有的子节点。

/** Doesn't work **/

for (Node kid = node.getFirstChild();

kid != null;

kid = kid.getNextSibling())

node.removeChild(kid);

/** Does work **/

while (node.hasChildNodes())

node.removeChild(node.getFirstChild());

使用另一个DOM 解析器

尽管我们想象不出一个您为何要改换解析器的理由,您可以使用非 XML4J 的解析器来解析您的 XML 文档。如果您查看t domTwo.java 的代码,您将看到更换到 Sun 的 XML 解析器只需要两个修改。

首先,我们必须载入(import) Sun 公司的类。这很简单。我们要修改的只是创建 Parser 对象的代码。如您所见,Sun 的解析器构建过程比较复杂,但余下的代码不用修改了。所有的 DOM 代码不需要任何的修改。

最终,在 domTwo 的不同之处是命令行格式。出于某些原因,Sun 的解析器不能用常用的方法来解析文件名。如果您运行 java domTwo file:///d:/sonnet.xml (当然根据您的系统修改 file URI),您将获得 domOne 的相同结果。

import com.sun.xml.parser.Parser;

import

com.sun.xml.tree.XmlDocumentBuilder;

...

XmlDocumentBuilder builder =

new XmlDocumentBuilder();

Parser parser =

new com.sun.xml.parser.Parser();

parser.setDocumentHandler(builder);

builder.setParser(parser);

parser.parse(uri);

doc = builder.getDocument();

domTwo.java (参见附录2)

这段代码等同于 domOne.java,但它使用 Sun 公司的 XML 解析器而不是 IBM 的。它展示了 DOM 接口的可移植性。

使用一个不同的 SAX 解析器

我们也编写了 saxTwo.java 来展示如何使用 Sun 公司的 SAX 解析器。与 domTwo 类似,我们有两个修改之处。第一个是载入(import) Sun 的 Resolver 类而不是 IBM 的 SAXParser 类。

我们需要修改创建 Parser 对象的代码,然后我们需要根据我们输入的 URI 创建一个 InputSource 对象。我们要修改的只是创建 parser 的代码需要被包含在 try 代码段中以捕获当我们创建 Parser 对象时可能产生的异常。

import com.sun.xml.parser.Resolver;

...

try

{

Parser parser =

ParserFactory.makeParser();

parser.setDocumentHandler(this);

parser.setErrorHandler(this);

parser.parse(Resolver.

createInputSource(new File(uri)));

}

saxTwo.java (见附录2)

这段代码等同于 saxOne.java,但它使用 Sun 公司的 XML 解析器而不是 IBM 的。它展示了 SAX 接口的可移植性。

总结

在本章节,我们介绍了一些使用 XML 解析器的高级编程技巧。我们展现了如何直接生成 DOM 树,如何解析字符串而不是文件,如何在一棵 XML 树中移动元素以及如何改变解析器而不用影响 DOM 和 SAX 的代码。

希望您喜欢本教程!

这就是本教程的所有内容了。 我们讨论了 XML 应用的基本架构,而且我们也介绍了您如何处理 XML 文档。以后的教程将介绍构建 XML 应用更多的细节,包括:

使用可视工具来构建 XML 应用

将一个 XML 文档从一种形式转换到另一种

为最终用户或其他进程创建接口,及对后端存储数据的接口

要获得更多信息

如果您想了解更多的 XML 知识,可访问 developerWorks 的 XML 专区。这个站点有示例代码、其它的教程、关于 XML 标准的信息以及其它内容。

最后,我们很愿意听取您的意见!我们设计 developerWorks 是成为开发人员的资源。如果您有任何评价、建议或抱怨,请让我们知道。

谢谢,---Doug Tidwell 或 developerWorks 中国站点!

相关文章