使用 IBM XML 语法分析器 (XML4J)查找和替换 XML 文档中的元素

使用 IBM XML 语法分析器 (XML4J)
查找和替换 XML 文档中的元素

LindaMay Patterson
IBM 软件工程师
2000 年 7 月

内容:
基础
XML 语法分析器
语法分析
结束语
参考资料
关于作者

XML4J 语法分析器一种以结构化形式再现 XML 文档以便操纵每个元素的工具。本文将介绍一个样本 XML 文档及其文档类型定义 (DTD),并通过使用 XML4J 语法分析器来描述如何访问 XML 文档所包含的信息。

Extensible Markup Language(可扩展标记语言),简称 XML,开启了以各种方法共享商业信息的大门。一些使用 XML 的主要原因包括:

  • 弥补 HTML 的不足(XML 提供自描述数据)
  • 在商家到商家事务方面,类似于现今的 EDI 用法
  • 作为不同种应用程序之间的公共通信工具

HTML 在为 Web 提供信息方面非常成功,然而,HTML 有一些缺陷。因为 XML 文档是自描述的,所以 XML 能提供额外的灵活性。对 Web 内容使用 XML 的一个有趣特性是在搜索领域方面:搜索引擎可以使用标记和数据作为搜索条件的一部分,这样就可以更精确地匹配 Web 内容。可以使用可扩展样式表语言 (XSL) 样式表来格式化文档,从而可以在支持 XML 的浏览器中查看 XML 文档。

在商家到商家事务中使用 XML,以及将 XML 用作不同种应用程序之间的公共通信工具都要求有一种机制可以读取 XML 文档并将其解释成便于计算机处理的形式。应用程序需要一种访问每一个 XML 文档所包含的个别信息(元素)的方式。这是使用 XML 语法分析器以结构化形式(分层树状结构)再现文档来实现的,从而允许访问和操纵文档中的每一个元素。

基础
每个 XML 文档都由特定于该文档的元素组成。图 1 显示了 XML 元素的结构。带有内容的元素具有开始和结束标记,并在标记之间包含内容。不带内容的元素通常用于组织文档结构,它可以有一个在大于号 (>) 之前具有斜杠 (/) 的开始标记,表示没有内容。可以将元素组织成十分类似于现今的文件这样的结构;其嵌套层次可由开始和结束标记的位置反映出来。

图 1:XML 元素的结构
图 1:XML 元素的结构

任何元素都可以拥有属性,这些属性进一步定义元素。如图 2 所示,在元素的开始标记中定义属性。

图 2:具有属性的 XML 元素
图 2:具有属性的 XML 元素

每个属性都由名称和值组成。必须用双引号 (") 将值括起。每个元素可以有任意多的属性,这取决于设计者的需要。每个设计者都必须确定要将相关信息表示成子元素还是属性 -- 这方面没有严格规定。

图 3 显示了本文中使用的文档。该文档包含商品目录数据。已经将该文档缩排以显示文档的层次结构。

图 3 - 表示电子目录的 XML 文档
图 3 - 表示电子目录的 XML 文档

此 XML 文档包含两种羊毛衫信息,男式和女式。每种商品(羊毛衫)的信息都位于一对标记中。请注意 shippingcost 标记没有结束标记,而是在开始标记中的 shippingcost 之前有一个斜杠 (/)。这就是没有内容的空标记。它可以充当占位符留作以后使用。

XML 文档应该有文档类型定义 (DTD) 以验证 XML 正确与否。DTD 包含 XML 文档的结构、所有关于元素间关系的规则和特定于某一元素的规则。DTD 表示结构中元素的层次结构和嵌套。

图 4 显示了定义 catalog 文档结构的 DTD。这个 DTD 包含各种嵌套的、以 catalog 作为根项的嵌套结构。在解释该 DTD 之前,需要理解以下基本原则:

  • 元素是用这种形式定义的。
  • 元素可以包含其它子元素,这些子元素在圆括号 () 中括起并列在元素名之后。
  • 每一个字元素都按照相关的、由元素名称后面的特殊字符表示的出现规则在结构中出现。这些规则是:
    • 如果没有特殊字符,则子元素必须出现一次且只能出现一次。
    • 如果有星号 (*),则子元素出现零次和多次。
    • 如果有问号 (?),则子元素可以出现零次或一次。
    • 如果有加号 (+),则子元素必须至少出现一次或多次。

图 4 中定义的 catalog DTD 演示了某些出现规则的用法。例如,name 只出现一次,因为它后面没有特殊字符,而 item 则可以出现几次或根本不出现。

图 4. 文档类型定义

<!DOCTYPE catalog [ <!ELEMENT catalog (name,item*) > <!ELEMENT name (#PCDATA) > <!ATTLIST catalog season (winter|spring|summer|fall) #REQUIRED> <!ELEMENT item (itemname,type*) > <!ELEMENT itemname (#PCDATA) > <!ELEMENT type (typename,cost,description,number, weight,shippingcost) > <!ELEMENT typename (#PCDATA) > <!ELEMENT cost (#PCDATA) > <!ELEMENT description (#PCDATA) > <!ELEMENT number (#PCDATA) > <!ELEMENT weight (#PCDATA) > <!ATTLIST weight unit (pound|kilogram|gram|ton) #REQUIRED> <!ELEMENT shippingcost (#PCDATA) > ]> 

某些元素包含在属性列表语句中定义的属性。正如我们前面所讲的,属性是用来向特定标记添加含义的,并且必须在 DTD 中定义。例如,catalog 有一个 season 属性,该属性的值可以是 winterspringsummerfall。本例使用了 #REQUIRED 关键字,所以必须提供一个值。需要为文档中的每个元素分别定义其数据类型。#PCDATA,即 Parsed Character Data(经过语法分析的字符数据),表示字符(文本)数据。目前,XML 文档只包含字符数据。

本文仅介绍了这篇文档将用到的基本规则和信息。还有很多书籍包括了用于创建 DTD 的所有规则和注意事项。

使用 XML 语法分析器
IBM 在包括 AS/400 Toolbox for Java 在内的各种产品中、以及在 IBM alphaWorks 网站上都提供了 XMLJ4 语法分析器(请参阅
参考资料)。该语法分析器用 Java 编程语言编写,因此可移植到其它具有 Java 虚拟机 (JVM) 的操作系统上。该语法分析器使用 DTD 和 XML 文档来创建分层显示文档的文档对象模型 (DOM) 树。DOM 提供了一组允许访问树中元素的 API。使用这些 DOM API,就可以访问、更改、删除或添加 XML 文档中的元素。

XML 语法分析器使用 DTD 来验证文档,即确保 XML 文档符合在 DTD 中指定的所有规则。例如,DTD 规则可以指定有效的标记集、有效的元素嵌套规则以及与特定元素相关的属性。XML 语法分析器还使用 DTD 来帮助对特定 XML 文档创建 DOM。DOM 是应用程序在运行期间用来查询和更新 XML 文档中信息的文档表示。

图 5 包含本文档的一个 DOM 树示例。它没有在树结构中表示内容。必须将此树看成是代表内容的树丛或森林,而不是一个单一结构。图 5 中的每个矩形都表示树中的一个节点,每个椭圆都表示一个属性。为使图尽可能简单,只包括了元素和属性名。

图 5 - 文档对象模型
图 5 - 文档对象模型

DOM 提供了访问树中元素的方法。通过遵循树状(分层)结构,这些方法允许对父代和子代调用方法来遍历树。

语法分析示例
此语法分析示例完成以下任务:

  1. 为 XML 文档创建 DOM 树。
  2. 打印整个树,包括属性。
  3. 查找和替换作为参数传递的特定元素。
  4. 打印整个(更新的)树。

本例使用 FindReplaceDOMParse 类、IBM XML4J 语法分析器(请参阅参考资料)和 DOM API 来访问和操纵 XML 文档。图 6 中演示了 import 语句。

图 6. Import 语句

 import com.ibm.xml.parsers.DOMParser; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.w3c.dom.Element;import org.w3c.dom.NamedNodeMap; 

图 6 中所列出的 import 语句是 XML4J 语法分析器附带的 DOM API 支持的一部分。可以在 XML4J 语法分析器附带的的文档中找到详细信息。

后面的图中显示了 main() 方法的关键部分。main() 方法需要四个参数:

  1. srcfile,XML 文档名
  2. targetElement,在查找和替换过程中所用到的元素(名称)
  3. valueToFind,希望替换的值
  4. valueToReplace,新值

这些参数中的每一个都被转换成 main() 中的 String。执行该程序的运行时命令示例是:

图 7. FindReplaceDOMParse 运行时命令

java .FindReplaceDOMParse catalog.xml cost $50.00 $95.00 

main() 方法按顺序获得在该命令中指定的参数。

处理流程中的下一步是为 catalog.xml 文档创建 DOM 树,这通过分析指定的源文件 (srcFile) 的语法来完成。以确认方式进行语法分析,这将确保文档是有效的。图 8 包含对用于分析 XML 文档的方法的调用语句。

图 8. 调用 parseV 方法

Document document = parseV(srcFile);

图 9 中显示的 parseV() 方法执行以下步骤:

  • 创建一个语法分析器实例。
  • 分析 XML 文档 (srcfile)。
  • 获取 DOM(文档对象)并将其返回给调用者。

图 9. 分析 XML 文档以创建 DOM 树

public static Document parseV(String srcFile) throws Exception{ try { // Get a new parser and attach an error handler DOMParser myParser = new DOMParser(); // Parse the file myParser.parse(srcFile); // Return the document return myParser.getDocument(); } catch (Exception ex) { System.err.println("[Operation Terminated] " + ex); } return null; }

图 10 包含了 main() 方法中用于处理文档的方法调用。其目的是查找元素(在第二个参数中传递)和元素值(在第三个参数中传递),并将该值改成新值(在第四个参数中传递)。在找到和替换元素之前将打印 DOM 树,在更改之后将再次打印树。

图 10. 用于打印的 main() 方法调用

 if (document != null) { // Print the initial state of the document starting at the root element System.out.println("******************BEFORE*******************"); printElement(document.getDocumentElement()); // Perform a find replace on the document document = findReplace(document, targetElement, valueToFind, valueToReplace); // Print the resulting state of the document starting at the root element System.out.println("*******************AFTER*******************"); printElement(document.getDocumentElement()); } else{ System.out.println(" in main document null"); }}

下面是在 main() 方法中被调用以进行实际处理的方法:

  • printElement(document.getDocumentElement()),由 getDocumentElement() 方法传递到文档(DOM 树)的根部。
  • document = findReplace(document, targetElement, valueToFind, valueToReplace) 查找要更改的元素并完成更改。
  • printElement(document.getDocumentElement()) 打印更新的 XML 文档。

第一个打印的版本类似于图 2 中显示的文档。发出在图 7 中定义的命令之后,男式羊毛衫的价格将从 50.00 美元改成 95.00 美元。

图 11 中显示的 printElement() 方法处理包括元素名、元素值和属性在内的整个文档(DOM 树)。

图 11. printElement() 方法

private static void printElement(Element element) { int k; NamedNodeMap attributes; NodeList children = element.getChildNodes(); //***** Start this element System.out.print("<" + element.getNodeName()); //***** Get any attibutes and print them inside the element start tag attributes = element.getAttributes(); if (attributes != null) { for (k = 0; k < attributes.getLength(); k++) { System.out.print(" " + attributes.item(k).getNodeName()); System.out.print("=" + attributes.item(k).getNodeValue()); } } if (element.hasChildNodes()) { //***** If this element has a value or sub-elements System.out.print(">"); //** For each child, if the child is an element call print element to print that //** portion of the tree, if it is a text node, print the text to stdout. //** All other node types are ignored for the sake of simplicity. for (k = 0; k< children.getLength(); k++) { if (children.item(k).getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) { printElement((Element) children.item(k)); } else if (children.item(k).getNodeType() == org.w3c.dom.Node.TEXT_NODE) { System.out.print(children.item(k).getNodeValue()); } } // end for loop //***** Add a closing tag System.out.print(""); }// end else }// end method 

printElement() 是递归方法,它将持续调用它自己,直到处理了树中的所有节点为止。DOM 接口中要处理的主要数据类型是 Node 接口。

对传递到 printElement() 的元素进行如下处理:

  1. 发出 getChildNodes(),它返回包含该节点所有子代的 NodeList。
  2. 使用 println 来打印 "<" 和用 getNodeName() 方法得到的元素节点名。
  3. 使用 getAttributes() 方法来返回包含节点属性的 NamedNodeMap。
  4. 通过循环遍历元素的所有属性来处理属性(如果属性存在的话)。
    • 使用 getNodeName() 打印属性名。
    • 使用 getNodeValue() 打印等号 (=) 和属性值。
  5. NodeList children 包含此元素的子代,这在第 1 步中确定。如果元素由子代(通过调用 hasChildNodes() 方法来确定),则在循环中处理每个子代。该列表中的节点是 ELEMENT_NODE 和 TEXT_NODE。
    • 通过调用 printElement() 方法来处理元素节点,以确保该元素及其子代都被处理。
    • 对文本节点只作打印处理,因为它是节点值,而不是新的节点。
  6. 每一个带有元素信息和属性的节点都有相关的结束标记。
  7. 对于那些没有元素值的元素,将元素名以空元素的形式打印并加以说明。

请看一下第 5 步,以确定如何处理元素。代码将检查节点类型是 ELEMENT_NODE 还是 TEXT_NODE,以便进行正确的处理。特殊的节点可以拥有子元素和文本,如图 12 所示,该图表示的是电话号码。电话号码描述了具有两个节点的元素:一个是元素,另一个是元素文本(电话号码)。图 12 有助于使图 5 中可能进行的处理更加形象。

图 12. DOM 树片断示例
图 11 - DOM 树片断示例

一旦打印了初始文档,将调用 findReplace() 方法来修改指定的元素值。除了 main() 方法向此方法传递参数之外,此方法必须执行与 printElement() 方法类似的功能。文档(DOM 树)作为参数来传递。图 13 包含了处理查找与替换活动的代码。新方法以粗体显示,演示了正在讨论的新功能。

因为这段代码的大部分与 printElement() 方法中的代码类似,所以我们只讨论 findReplace() 方法中突出显示的代码。我们以升序方式讨论突出显示的代码:

  • 从根元素开始,元素方法 getElementsByTagName() 方法用来检索元素(将结果放在 NodeList 中)。
  • 一旦确定 NodeList 中的节点是 TEXT_NODE,就将该节点与传入的值进行比较,以确定是否要更改该元素。通过对 NodeList 中的该节点使用 equals() 方法来实现这点。
  • 使用 setNodeValue() 方法来将该值设置成新值

图 13. findReplace() 方法

static private Document findReplace(Document document, String elementName, String valueToFind, String valueToReplace) { int i; int k; NodeList children; Element docRoot = document.getDocumentElement(); //*** Get the root element NodeList elements = docRoot.getElementsByTagName(elementName); if (elements != null) { //** For each element matching the search element for (i = 0; i<elements.getLength(); i++) { if (elements.item(i).hasChildNodes()) { children = elements.item(i).getChildNodes(); for (k = 0; k

类中唯一没有讨论的方法是 usage() 方法,在处理过程中发生错误时将调用该方法。该方法包括一个 println,它将打印在调用该类时传递的值。

结束语
本文提供了一个使用 XML 语法分析器 (XML4J) 的示例,以及由该语法分析器和 DOM API 所提供的各种访问与更新 XML 文档的功能。下载该语法分析器时,您将发现其中的文档包括了有关 XML4J 和与之相关的类和方法的详细信息。

参考资料

关于作者
LindaMay Patterson 是 IBM Rochester AS/400 部门的 PartnerWorld for Developers 顾问程序员。她已经在 IBM 工作了 24 年,从事过各种商业应用环境。目前,她是 PartnerWorld for Developers 的成员之一,这是个使用 Enterprise JavaBean 技术的 AS/400 Java 技术小组。在这之前,她一直从事 SanFrancisco 产品方面的工作,负责开发教育软件包并在德国从事帮助定义产品内容的工作。她的应用开发背景主要在分发和后勤系统方面。可以通过 lindamay@us.ibm.com 与 LindaMay 联系。

posted on 2005-01-17 10:32  笨笨  阅读(2003)  评论(0编辑  收藏  举报

导航