来源: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 树。当树被构建完后,该代码将树的内容输出到标准输出。
... <address> <name> <title>Mrs.</title> <first-name>Mary</first-name> <last-name>McGoon</last-name> </name> <street>1401 Main Street</street> <city>Anytown</city> <state>NC</state> <zip>34829</zip> </address> <address> <name> ...
添加 Node 到我们的 Document
现在我们有自己的 Document 对象了,我们开始创建 Node。我们第一个要创建的 Node 是一个 <sonnet> 元素。我们将创建所有的 Node,然后将每个节点添加到其对应的父母。
注意我们使用 setAttribute 方法来对<sonnet>元素设置其 type 属性的值。 Element root = doc. createElement("sonnet"); root.setAttribute("type", h"Shakespearean"); 构建您的文档结构
当我们开始构建我们的 DOM 树时,我们将需要构建我们文档的结构。要完成它,我们将需要恰当地使用 appendChild 方法。我们将创建 <author> 元素,然后创建在其下的其它元素,接着使用 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 树
一旦我们对 <sonnet> 元素添加了所有内容,我们需要将它添加到 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("<?xml version=\"1.0\"?> <a>AlphaBravo..."); InputSource iSrc = new InputSource(sr); ps.parseAndPrint(iSrc);
parseString.java (参附录2)
这段代码展示了如何解析一个包含 XML 文档的字符串。
排列在一棵 DOM 树中的 Node
为了介绍您如何改变一棵 DOM 树的结构,我们将修改我们的 DOM 示例来排列十四行诗的 <line> 元素。有多种 DOM 方法可以用来在 DOM 树中搬移节点。
要查看代码的结果,运行 java domSorter sonnet.xml。它并不会改进诗的韵律,但它真确地排列了 <line> 元素。
要开始排列工作,我们将使用 getElementsByTagName 方法来提取在文档中的所有 <line> 元素。该方法节约了我们编写代码来遍历整个树的开销。
if (doc != null) { sortLines(doc); printDOMTree(doc); } ... public void sortLines(Document doc) { NodeList theLines = doc.getDocumentElement(). getElementsByTagName("line"); ...
domSorter.java (参附录2)
这段代码在 XML 文档中查找所有的 <line> 元素,然后排序。它展示了如何操作一个 DOM 树。
提取我们的 <line> 文本
为了简化代码,我们创建一个 helper 功能,getTextFromLine,用来提取包含在一对 <line> 元素中的文本。它简单地找到 <line> 元素的第一个子女,如果其是一个 Text 节点就返回其文本。
该方法返回一个 Java String 因此我们的排序过程可以使用 String.compareTo 方法来决定排序的次序。
该段代码实际上应该检查 <line> 所有的子女,因为它们可能包含实体(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); }
文本排序
现在我们有能力从一个给定的 <line> 元素获取文本,我们可以开始排列数据了。由于我们只有 14 个元素,我们将使用冒泡排序。
冒泡排序算法是比较两个相邻数据值,然后如果它们次序不对就交换它们。要完成交换,我们使用 getParentNode 和 insertBefore 方法。
getParentNode 返回任意 Node 的父母;我们使用这个方法来获得当前 <line> 的父母 (文档的一个 <lines> 元素使用 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 中国站点!
|