随笔-128  评论-55  文章-5  trackbacks-0

处理 XML 的 API
基于 XML 的应用程序从逻辑上分成 3 个层次,下面是 XML 文档或数据,中间是 XML 解析器,上面是 XML 应用程序。XML 解析器的作用是读入 XML 文档或数据,将 XML 建造(build)为内存中的数据结构,便于应用程序处理,或者根据 XML 中包含的信息,调用应用程序提供的回调方法。由于 XML 是基于文本的,具有平台中立性,各种语言都有相应的 XML 解析器。

处理 XML 的 API 有这几种:DOM、SAX、JAXP、JDOM 和 DOM4J,这几种是最常用的,还有其它的一些不常用的 API。其中 DOM 可以用于多种语言,SAX 可以用于 Java 与 C++,JAXP、JDOM 和 DOM4J 只能用于 Java。一个 XML 解析器至少需要实现 DOM 或 SAX 两种 API 中的一种,不过现在常用的 Java XML 解析器都同时实现了 DOM、SAX 和 JAXP 3 种 API。JDOM 和 DOM4J 是由单独的包(jar)来实现的,使用 JDOM 和 DOM4J 需要得到标准的 XML 解析器的支持。目前在众多的 Java XML 解析器中使用最广的是 Xerces 和 Crimson,它们都是开源软件组织 Apache 软件基金会资助的开源软件。其中 Xerces 最初的代码来自 IBM 的贡献,Crimson 最初的代码来自 Sun 的贡献。JDK 1.4 以后的版本中已经包括有 XML 解析器,因此不再需要另外的 XML 解析器。IBM JDK 1.4 本带的 XML 解析器是 Xerces 的早期版本,Sun JDK 1.4 本身带的 XML 解析器是 Crimson 的早期版本(孩子总是自家的亲)。不过即使使用 JDK 1.4,你仍然可以使用其它的 XML 解析器。在我们的开发中全部使用 Xerces,主要是因为 Xerces 在所有 Java XML 解析器中功能是最丰富的,根据性能测试 Xerces 的性能要比 Crimson 好的多,而且我们在 Linux 上出于性能的考虑全部使用 IBM 的 JRE/JDK。


[PS: XML4J简史。XML4J 由 Hiroshi Maruyama 在“IBM 东京研究实验室”开发,它可以分解并解释 XML 文档,以便机器和应用程序读取。在 1998 年 W3C 宣布建议的 XML 规范的同一天,XML4J 作为免费试用下载软件进入 alphaWorks 网站。从那时起,XML4J 的日渐流行导致人们对 alphaWorks 网站上的该技术要使用商业许可证选项,并将它作为一部分包括在 IBM 的 WebSphere Application Server 中。其它公司(包括 Vervet Logic、NC.Focus 和 First Union)也在其最新软件中采用该技术。1999 年,IBM 通过将源码捐赠给“Apache 软件基金会”,将源码发放给在此技术基础上构建程序的社区。需要正式 XML4J 支持的开发人员可以通过 IBM 产品 WebSphere Application Server 或 Visual Age for Java 的产品来获得支持。否则,只能通过 alphaWorks 的 XML4J 论坛获得支持。XML4J 后演变为了Xerces。]

参见:Xerces、XML4J 和 XML4C 添加了对 XML Schema 的支持 http://www.ibm.com/developerworks/cn/xml/x-parup/

[PS: 在2002.6月的时候,apache有三个java解析器:Crimson,Xerces1,Xerces2。由于历史原因,apache接收了两个不同公司的捐赠。一个是IBM的xml4j,现在变成了现在的Xerces1.另外一个是SUN捐赠的Project x,变成了现在的Apache Crimson.
而Xerces2是一个重写的解析器。它的目标是可维护,模块化,某些执行性能,这是前两版本没有达到的目标。xerces2的设计是为了满足Apache项目的长期发展。]


DOM 简介:
DOM 对于 XML 的处理方式就是把整个 XML 读到内存中形成一棵树状结构,然后用各种方法对这棵数进行遍历、插入、删除、修剪等操作。因为 DOM 是 W3C 的正式标准,所有的语言都有支持 DOM 的解析器,包括 Java、C/C++、Perl、JavaScript 等等。DOM 的优点是信息量丰富(全部都在内存中),而且可以随机访问,尤其是在处理前后相互关联的元素时非常方便。DOM 的缺点是 XML 应用程序在处理 XML 之前必须先由 XML 解析器把整个 XML 读进内存并生成树状结构,如果 XML 非常大,例如 10M,解析的过程是非常慢的。如果再加上 XSLT 转换(这是一种必须要使用 DOM 的操作)这类同样耗费资源的操作,可能会耗尽系统的内存资源。所以标准 DOM 只适合于中小型 XML 的处理。

SAX 简介:
为了更好地解决大型 XML 处理的问题,Java 开发人员发明了 SAX。SAX 采用事件驱动的方式来处理 XML,它的处理方式是:为每一个元素、属性、内容(这些都认为是事件)定义一个回调方法,这个回调方法由应用程序提供。解析器以数据流的方式读入 XML,当遇到某个元素、属性、内容时就调用相应的回调方法。SAX 的优点是处理效率高,适合处理大型 XML。缺点是 SAX 对 XML 是只读的,不能够对 XML 进行写操作,而且 SAX 处理 XML 中前后相互关联的元素时也没有 DOM 方便,因为应用程序必须自己保留以前事件的状态信息。但是 SAX 还是取得了巨大的成功,后来 SAX 还被移植到了 C++ 等语言中。SAX 更详细的优缺点可以查看《XML 高级编程》第 6 章的内容,我们这里有这本书的电子版。

JAXP 简介:
你们对 XML 熟悉了以后可能会经常听到 JAXP 这个词。JAXP 其实不是一个独立的 XML API,它的作用是为了解决不同的 XML 解析器之间的兼容性问题的。在 Sun 推出 JAXP 之前,商业公司(IBM、Oracle、Sun,etc.)、XML 专业研究者以及开源软件爱好者开发出来多种多样的 XML 解析器。这些解析器有的仅仅实现了 DOM API,有的同时实现了DOM 和 SAX。在我学习 XML 的过程中,我所知道的 Java XML 解析器就有 7、8 种。这些不同的 XML 解析器各有各的特长,它们虽然都遵从 W3C DOM 或 SAX 的规范,但是或多或少存在着一些差别,这使得为一种解析器编写的应用程序移植到另一种解析器变得有些困难(也不是非常困难,只是不太方便)。为了解决这个问题,Sun 在 DOM、SAX 的 API 之上加了一个抽象层(基本上就是加了一个抽象工厂的设计模式,如果你们对设计模式有所了解的话),这就是 JAXP。JAXP 主要采用反射的方式来工作,可以动态加载所使用的 XML 解析器。使用 JAXP 来做 XML 开发,可以完全忽略不同的 XML 解析器之间的差别,不需要修改代码就可以更换成另外的 XML 解析器。JAXP 目前已经得到大多数 Java XML 解析器的支持。除了封装 DOM、SAX 的那些工厂类外,JAXP 还包含用于 XSLT 转换的 API(也是一些工厂类),这些功能在 javax.xml.transform 这个包里。

JDOM 简介:
除了 SAX,Java 开发人员还发明了 JDOM。虽然名字里有 DOM,JDOM 其实与 DOM 毫无关系。JDOM 严格说来其实是一种对象模型,除了处理 XML,还可以用于很多其它用途。JDOM 的输入可以有多种格式,输出也可以有多种格式。这些格式包括 XML 文件或数据流、Java 属性文件、标准 DOM 树等等。JDOM 利用 SAX 或 DOM(一般是用 SAX,极少用 DOM)读入 XML 中的信息后在内存中生成自己的数据结构,就是通过组合(composition)关系嵌套在一起的一系列对象,然后用 Java 程序员最习惯的面向对象的方式来处理这些数据,处理结束后可以非常方便地输出成各种格式。和 DOM、SAX 相比,用 JDOM 来做 XML 开发更加容易(代码量更少)。JDOM 的主要的缺点是最初的设计未考虑性能问题,因此性能很差,而且全部使用具体类来实现导致了灵活性较差,但是考虑到易用性和开发效率,JDOM 仍然得到了广泛的使用。

DOM4J 简介
由于 JDOM 本身存在着一些设计缺陷,开发到一半,JDOM 的一些开发人员分了出来重新启动了一个开源项目 DOM4J。DOM4J 与 JDOM 所要达到的目标是一样的,即提供一套比 DOM、SAX 使用更加方便的处理 XML 的 API,同时避免 DOM、SAX 的主要缺点。DOM4J 重新做了设计,完全基于接口和多态,因此提高了灵活性。DOM4J 在概念上对 XML 的理解与 DOM 非常相似,只要你理解了 DOM,你就可以轻而易举地理解 DOM4J,实际上 DOM4J 可以看做是一个更好的 DOM 实现。而且 DOM4J 保持了 JDOM 的易用性,性能上也比 JDOM 有了非常大的提高。最值得一提的是 DOM4J 完全支持 XPath,看看 DOM4J 的这段代码:

代码:
List list = document.selectNodes( //foo/bar );
Node node = document.selectSingleNode(//foo/bar/author);

这与我们前台写的 JavaScript 是非常相似的,我们学会 DOM4J 是几乎不需要花什么时间。
DOM4J 与 JDOM 一样,通过 SAX 或 DOM(一般用 SAX)读入 XML 中的信息在内存中生成自己的数据结构,因此 DOM4J 至少需要一个实现了 SAX 的 XML 解析器,我们可以直接使用 Xerces。
由于 DOM4J 具有易用性、性能、灵活性、功能强大等多方面的优势,今后我们如果需要在服务器端做 XML 开发,DOM4J 将是我们主要采用的工具。

关于这几种 API 的详细内容,请参考《Java 与 XML》和《XML 高级编程》。关于 DOM4J,主要有这些资料:
http://dom4j.sourceforge.net/faq.html
http://www.csdn.net/develop/article/22/22753.shtm

听了我上面的介绍你们可能会觉得 DOM 是比 SAX 更基础的 API,因为它是 W3C 的标准,所有的语言都支持,而 SAX 的使用仅局限于少数几种语言。某种程度上你是对的,但是在 Java 的世界里,SAX 是比 DOM 更加基础的 API。由于 SAX 处理效率很高,SAX 的应用范围比 DOM 更广。例如:我们中间件的低层框架 Avalon(也是 Apache 软件基金会的产品)处理 XML 配置文件时使用的就是 SAX。另外,因为 W3C 并没有规定在内存中如何生成一棵 DOM 树(W3C 只规定了如何操作这棵 DOM 树),Xerces 采用高效率的 SAX 来读入 XML,然后生成 DOM 树。因此当你在用 Xerces 做 DOM 开发时发现经常需要捕获 SAXException 就没什么可奇怪的了。JDOM 和 DOM4J 通常也是使用 SAX 读入 XML,然后生成自己的数据结构。对于 JAXP、JDOM 和 DOM4J 来说,DOM 和 SAX 都是基础的 API。

在我们公司做开发最常接触的 XML 开发是使用 JavaScript 做 DOM 开发,因为时间有限,所以我今天只详细讲一下在前台使用 JavaScript 和 DOM API 做 XML 开发的过程。今天的目的主要是让大家对于 XML 相关的知识有一个整体的了解。

大家知道 JavaScript 是嵌入在浏览器中的,创建 DOM 树不是 JavaScript 的责任,浏览器已经创建好了,另外通常 JavaScript 也不能直接读写 XML 文件(在权限许可的情况下 JavaScript 可以读写本地文件)。JavaScript 处理的 XML 数据有两个来源,一个是来自于页面中的 XML 数据岛,另外一个是来自于从 XMLHTTP 接口接收的后台发来的 XML 数据。注意:在浏览器中的 DOM 有两种,HTML DOM 和 XML DOM。如何处理 HTML DOM 在普通的 JavaScript 教材(《JavaScript 权威指南》等等)中已经讲得很详细了,我这里只详细讲一下如何处理 XML DOM,下面所说的 DOM 都是指 XML DOM。在讲 XML DOM 之前我首先要讲一下 XPath。

什么是 XPath?简单地说,XPath 就是定位 XML 中某些节点(元素、属性、内容、处理指令、文档类型定义、注释)的方法。XPath 的目的是为 XSLT 和 XPointer 提供一个共同的、整合的语法,用来对应 XML 的各个部分,选择 XML 中的某个或某些节点。XPath 是在 DOM 树中查找节点、做 XSLT 转换、定义文档内部指针(XPointer)的基础。有时候也把一个符合 XPath 规范的表达式称做一个 xpath。我们通常把 XPath 表达式的结果称为一个节点集(node set)。节点集能够被转换、复制、忽略或执行其它的合法操作。XPath 除了包括定位语法外还包括很多函数定义,这些函数分成 4 类:节点集函数、字符串函数、布尔函数和数值函数。节点集函数,例如 position() 和 count(),通常以节点集作为输入(以 XPath 表达式的形式),然后做进一步处理。其它 3 种类型的函数,例如 substring()、not() 和 round() 提供基本的字符串处理、逻辑运算和数值运算的功能。关于 XPath 中各种函数定义的详细内容可以参考《XML 高级编程》这本书。所有这些表达式语法或函数定义都是 XPath 规范或实现的一部分。

好了,长话短说,我在这里主要讲一下 XPath 如何使用。其实我们需要知道的基本上就是《无废话 XML》中表 7.1 的内容。
XPath 的定位语法与操作系统目录结构的语法非常相似。也分成两种,相对路径与绝对路径。相对路径从当前正在处理的节点开始,绝对路径从文档的根节点开始。我来举些例子:
代码:
A               对应当前节点下所有名为 A 的子元素。
*               对应当前节点下所有子元素。
*/A               对应自当前节点开始,所有名为 A 的孙元素。
@A               对应一个附属于当前节点,名为 A 的属性。
@*               对应所有附属于当前节点的属性。
text()            对应当前节点的子元素中的所有文本节点。
.               对应当前节点
..               对应当前节点的父节点。
A[1]            对应当前节点下,第一个名为 A 的子元素。
A[position()=1]      作用同上。
A/[@B="true"]      对应当前节点下所有名为 A 的子元素,这个子元素必须含有一个名为 B 的属性,其属性值必须为 "true"。
A|B               对应当前节点下,所有名为 A 或 B 的子元素;| 代表“或”的关系。
.//A            对应当前节点下,所有名为 A 的元素;// 符号代表可跨越数级。
A//B            对应到所有名为 B 的元素,它们的上级(可跨越数级)必须有一个名为 A 的元素,而且 A 元素必须是当前节点的子元素。
/A               对应根节点下所有名为 A 的子元素。
//A               对应根节点下所有名为 A 的元素。A 元素可以在任意层次。


我们在做前台开发时,有两个方法会用到 XPath,selectSingleNode() 和 selectNodes()。这两个方法是 IE 的扩展,不属于 W3C DOM 规范,但是使用起来非常方便,所以我们在 DOM4J 中也可以看到这两个方法。
代码:
hwn.__drTags = sr1.selectSingleNode("./tag");
var cxn = xn.selectNodes("./*[not(@m:f) or @m:f!='d']");


上面是从我们的前台开发框架中 copy 出来的代码。
selectSingleNode 用于获得一个节点,如果有多个节点满足条件,返回的是第一个节点。
selectNodes 用于获得节点集,返回的结果是包含所有满足条件节点的数组。

第二条语句的参数看起来有些复杂,它的意思是找到 xn 节点下所有不包含 m:f 属性或者包含 m:f 属性,但属性值不等于 'd' 的所有子元素。


下面我来详细讲解 XML DOM 在 JavaScript 中的实现。
在浏览器中,无论 XML 的数据来源如何,最后都会由浏览器生成一棵 DOM 树。这棵 DOM 树所对应的对象类型为 XMLDocument,这棵 DOM 树上的所有节点(包括根节点)都是 XMLNodes 类型的对象。事实上 XMLDocument 也是 XMLNodes 的子类,所以你可以在任何一种 XMLNodes 派生出的对象上调用 selectSingleNode 和 selectNodes。

数据来自 XML 数据岛:
例如在页面中有这样一个 XML 数据岛,
<xml id="book">
...
</xml>

var xd = book.XMLDocument;

通过外部 XML 文件创建新的 DOM 树:
var xd = new ActiveXObject("Microsoft.XMLDOM");
xd.load(data.xml);

或者将外部文件中的数据加载到 XML 数据岛:
var xd = book.XMLDocument;
xd.load(data.xml);

注意:load() 这个方法的参数可以是任意合法的 URL,不一定限制为文件名。

将数据保存到 XML 文件用
xd.save(data.xml);

通过字符串创建新的 DOM 树:
var xd = new ActiveXObject("Microsoft.XMLDOM");
xd.loadXML("<"+hd.__hwnodes.style.rootTag+"></"+hd.__hwnodes.style.rootTag+">");


数据来自 XMLHTTP:
var xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
xmlhttp.open("GET","/hwsvr2/qe",false);
xmlhttp.send(null);
var xd = xmlhttp.responseXML;

得到 XML 文档的根节点:
var xn = xd.documentElement;


我再来说说在 Mozilla 中创建 XML DOM 树的方法,将来我们有可能会为 Mozilla 开发页面,所以对于如何保持脚本的兼容性有些了解是有好处的,这部分内容不是必须掌握的内容。
Mozilla 中并没有 IE 的 XML 数据岛这个概念,不过在 Mozilla 中模拟 XML 数据岛是非常容易的,这里有这方面的资料:
http://www.mozilla.org/xmlextras/xmldataislands/
感兴趣的可以看看。

Mozilla 中内建有对 XML DOM 的支持,并没有使用 ActiveX 之类外挂的方式(被 Mozilla 开发者讥笑为打补丁的方式)。在 Mozilla 中访问创建好的 DOM 树的方法与 IE 中基本上是一样的,所不同的是创建 DOM 的过程,Mozilla 采用的方法都是符合 W3C 标准的方法:

通过外部 XML 文件创建新的 DOM 树:
var xd= document.implementation.createDocument("","",null);
xd.load("data.xml");

第一个参数是名字空间的 URL,第二个参数是 DOM 根元素名称,第三个参数是定义 XML 格式的 DTD。在这里这 3 个参数都可以不提供。

Mozilla 中没有 loadXML() 这个简单的方法,但是可以手工为 Mozilla 添加这个方法,详细信息在这里:
http://webfx.eae.net/dhtml/xmlextras/xmlextras.html

数据来自 XMLHTTP:
var xmlhttp=new XMLHttpRequest();
xmlhttp.open("GET","/hwsvr2/qe",false);
xmlhttp.send(null);
var xd = xmlhttp.responseXML;


得到 XML 文档的根节点:
var xn = xd.documentElement;

由上面可以看到只要创建了 XML DOM 树后,访问方法 IE、Mozilla 是完全一样的,这部分的差异非常容易通过封装的方法屏蔽掉。Mozilla 没有提供 selectSingleNode() 和 selectNodes() 两个方法,以前我写过这两个方法在 Mozilla 上的实现,贴在论坛上,感兴趣的可以看看:
http://forum.hibernate.org.cn/viewtopic.php?t=965


下面我来讲讲访问 DOM 树常用的方法。

刚才我们说到过,DOM 树的访问有两个级别,文档级别和节点级别。

在文档级别,XMLDocument 对象上有这些方法:
abort():终止正在进行的异步下载
getElementByTagName():根据标记名称查找当前节点的子元素。
load():从指定位置下载 XML。
save():将 XML 保存到指定位置。

XMLDocument 对象上有这些属性:
async:指出 XML 是否可以异步下载。
doctype:返回 XML 的 DTD 节点。
documentElement:返回 XML 的根元素。
parseError:返回包含 parse error 信息的对象。

在节点级别,XMLNodes 对象上有这些方法:
createElement():在当前节点下创建一个子元素。
createTextNode():在当前节点下创建一个内容节点。

appendChild():在当前节点上添加新的子节点。
cloneNode():复制一个节点,返回值为生成的新节点。
hasChildNodes():当前节点是否为叶节点。
insertBefore():在某节点前插入新的节点。
removeChild():删除当前节点的子节点。
replaceChild():替换当前节点的子节点。

XMLNodes 对象上有这些属性:
childNodes:包含所有的子节点数组。
firstChild:当前节点的第一个子节点
lastChild:当前节点的最后一个子节点
nextSibling:当前节点的下一个兄弟节点
nodeValue:节点值
ownerDocument:当前节点对应的 XML 文档
parentNode:当前节点的父节点。
parentElement:父元素的句柄。
xml:从当前节点开始子树所生成的 XML。这个属性只有 IE 支持。

对于所有的元素节点,可以使用这两个方法:
setAttribute():设置当前元素的属性。
getAttribute():获得当前元素的属性。
clearAttributes():清除当前元素的所有属性。

另外还有 selectSingleNode() 和 selectNodes(),这两个方法其实是最常用的了。由于 XML DOM 中可以使用强大的 XPath 查找你想要查找的任意节点,比 HTML DOM 中仅能用 all()、getElementById()、getElementByTagName() 几个功能有限的方法方便得多。
只要你理解了 DOM 的概念,并且没有忘记以前学的数据结构的知识,上面的这些方法理解起来是非常简单的,我就不细讲了,关键是要经常使用,熟能生巧。



Author: orangelizq
email: orangelizq@163.com

欢迎大家访问我的个人网站 萌萌的IT人
posted on 2007-07-14 14:27 桔子汁 阅读(1366) 评论(0)  编辑  收藏 所属分类: Web Service

只有注册用户登录后才能发表评论。


网站导航: