浅出Java 的Xml编程

作者:何双江(yarshray)

对于Xml,我的认识是,一个用来存放数据的标准格式.怎么看我都觉得XML和Html,完全不一样,也许仅仅只是都使用了标记来作为文档解释的原因吧.所以人们才喜欢把XML和HTML对比来看.对于我个人而言,XML就是一个简单方便的数据文件.因为.它不同与一般的关系型数据库,把数据看出二维表.通过基本的关系运算,获取二维表中的数据.XML仅仅把数据当成一个文档,然后正对文档解析获取数据.所以我的看法是,要操作XML文档,只要一个能够解释XML的文档解释器就可以了.然后把解释的内容.转变成程序所需要的数据就可以了.当操作完毕也可以把数据写回去.因此,这里主要介绍两中XML文档解释器,和java相关的Api.它们分别是DOM和SAX.及JDOM.



为了方便起见,我们需要一个简单的XML作为本文的示例参考:这个XML的主要目的是为了存放我的书籍信息.包括书名,书的作者,书的价格,书的编号.几个基本信息,

XML示例如下:

<?xml version="1.0" encoding="gb2312"?>
<Books>
<Book id="1">
<bookName>
编程珠玑
</bookName>
<bookAuthor>
Jon Bentley
</bookAuthor>
<bookISBN>
7-5083-1914-1
</bookISBN>
<bookPrice>
28.0
</bookPrice> 
</Book>
 
 
<Book id="2">
<bookName>
Java编程思想(第2版)
</bookName>
<bookAuthor>
Bruce Eckel
</bookAuthor>
<bookISBN>
7-111-10441-2
</bookISBN>
<bookPrice>
99.0
</bookPrice> 
</Book>
 
 
<Book id="3">
<bookName>
Inside VCL(深入核心VCL架构剖析)
</bookName>
<bookAuthor>
李维
</bookAuthor>
<bookISBN>
7-5053-9489-4
</bookISBN>
<bookPrice>
80.0
</bookPrice> 
</Book>
</Books>



该XML记录了三本书和其相关的信息.

大体上操作XML文档可以分为三个步骤.

1.创建XML解释器

2.通过XML解释器和XML文件建立关联

3.通过XML解释器解释XML标记



XML解释器的类型.基本上可以分为:


? 验证和非验证解析器

? 支持一种或多种 XML Schema 语言的解析器

? 支持 Document Object Model (DOM) 的解析器

? 支持 Simple API for XML (SAX) 的解析器



这里主要介绍后两种.解释器.




Document Object Model(DOM)解释器:

Dom解释器是由W3C官方制定的标准解释器Api.只要符合该标准的编程接口都可以用来操作XML.目前该解释器主要有三个级别.Level1 Level2 Level3 这里只讨论到Level2.Dom模型实际上是把XML文件中的数据转变成了内存中的一颗树.该树大体有Doument Note NodeList Element这些对象构成.而Dom则是负责分析这颗树的结构,然后通过解释树从而起到了解释XML文档的作用.这里值得注意的是由于操作的树型结构.所以对于有些情况该解释器是无法分清楚的.例如属性值:

Id="1"
Id=
"1"
Id='1'



这对于Dom而言是完全相同的.因为.对于文档的格式Dom并不做任何解释.它仅是把文档中的数据转变成一颗树而已,原有的文档格式并不DOM中出现.


 



Simple API for XML (SAX) 的解析器

SAX解释器,可以说是一个把具体操作留给编程人员而把解释工作留给自己的一个编程模型.它并没有向DOM那样把整个XML文档加载到内存而是逐行解释然后通过事件通知给程序,由具体的程序使用这些通知,然后加以处理,这里就好像编写事件驱动的代码一样.因而就内存的占用率和解释效率而言SAX是有其适用性的.对于具体环境DOM和SAX都有其具体的应用面.

SAX的事件主要包括

文档的开始startDocument事件

对于元素而言的startElement和endElement

对于处理字符文本的characters 事件

以及文档的结束endDocment事件



JDOM API

DOM和SAX虽然提供了非常丰富的功能.可是对于开发人员而言这同时也带来巨大的负担.因为太过复杂,因此开源社区推出了的Jason Hunter 和 Brett McLaughlin 两位JAVA专家推出了JDOM作为一个比较简单的API.JDOM在背后会提供适配器选择具体的XML解释器.同样它也提供了树型结构来处理XML,而JDOM的树更简介.因此它兼顾了DOM和SAX的有点.当然也降低了灵活性.因此如果单纯的使用JDOM也是一个不错的选择.



三种API的对本




DOM 模型:

需要对整个文档结构有整体了解的情况

需要修改文档结构的情况

需要多次引用该文档的情况



SAX模型:

内存占有要求有限的情况

只需要读取XML部分元素的情况

只需要引用XML文档一次的情况




XML 文档对象模型(DOM)简介:

DOM由W3C推出的目的是为了提供一个良好的分析结构,用于操作XML中的标记.这个模型和类似于HTML的标记分析模型.它提供了一组操作接口,可以被各个具体平台所实现.因此可以选择任何满足DOM的解释器所提供的编程接口所实现的应用程序.将会很方便的移植到其他实现了DOM编程接口的平台工作.

对于标准DOM而言.它提供了的接口是一组可以描述XML文档树型结构的对象,以及通过这些对象可以对XML文档操作的方法.而应用程序只要调用这些对象以及对象操作.就等于在操作XML文件.这样就简化了XML文件的操作.使得编程接口足够的抽象.那么也就是说要熟悉DOM编程模型我们首要的就是熟悉DOM所提供的对象和方法.



下面我们就逐个熟悉DOM所提供的对象和方法:


Document:

该对象所描述的是整个XML文档.也就是整个树.对于树而言它可以表示为包含了很多节点的整体对象.而这些节点统称为Node.




Node:

node是一个相对抽象的概念,因此在java的定义中node被描述为接口.该接口扩展了几个子接口.

Element: 表示文档中的元素

Attribute:表示XML文件中的属性

Text:表示节点中的文字

其他的节点类型包括:Comment 表示 XML 文件中的注释、ProcessingInstruction 表示处理指令、CDATASection 表示 CDATA 节。对于这些有兴趣的读者可以参考相关文档.这里就不一一详解.



处理 DOM 经常要用到以下方法:



Document.getDocumentElement():返回 DOM 树的根。(该函数是 Document 接口的一个方法,没有定义其他的 Node 子类型。)

Node.getFirstChild() 和 Node.getLastChild():返回给定 Node 的第一个和最后一个孩子。

Node.getNextSibling() 和 Node.getPreviousSibling():返回给定 Node 的下一个和上一个兄弟。

Element.getAttribute(String attrName):对于给定的 Element,返回名为 attrName 的属性的值。如果需要 "id" 属性的值,可以使用 Element.getAttribute("id")。如果该属性不存在,该方法返回一个空字符串 ("")。



下面我们来做基本的DOM的示例,该示例很简单只是简单的显示上面的Book.xml文档中的内容.这也是DOM所做的基本事情.


代码如下:
 
 
import javax.xml.parsers.*;
import org.w3c.dom.*;
 
 
public class DomXml{
public static void main(String[] args)throws Exception{
DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
DocumentBuilder bulider=factory.newDocumentBuilder();
Document doc=bulider.parse("BookXml.xml");
NodeList nl=doc.getElementsByTagName("Book");
for(int i=0;i<nl.getLength();i++){
Element node=(Element)nl.item(i);
System.out.println("BookName is :" +
node.getElementsByTagName("bookName").item(0).getFirstChild().getNodeValue().trim());
System.out.println("BookAuthor is :" +
node.getElementsByTagName("bookAuthor").item(0).getFirstChild().getNodeValue().trim());
System.out.println("BookISBN is :" +
node.getElementsByTagName("bookISBN").item(0).getFirstChild().getNodeValue().trim());
System.out.println("BookPrice is :" +
node.getElementsByTagName("bookPrice").item(0).getLastChild().getNodeValue().trim()+"/n/n");
}
}
}
 
 
代码很简单.只是把结果打印出来
 
 
 
 
在这段代码中我们可以看到的是
DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
DocumentBuilder bulider=factory.newDocumentBuilder();
Document doc=bulider.parse("BookXml.xml");


这样三段代码.DOM模型提供了一个工厂,由这个工厂负责加载XML解释器.这样使得程序员脱离了具体的编程环境.无需知道有关解释器的相关信息.而DocumentBuilder提供了创建XML文档的方法.通过这个对象,也使得程序无需和底层I/O系统打叫道.并且该对象会通过解释器生成正确的XML文档树.这样XML就被装载到了内存并生成了正确的树格式.

那么第二步就是要读取正确的文档内容了.读取正确的文档内容就如同对树的操作.首先遍历所需要的节点.然后读取数据就可以了.

因此理一下整个通过DOM读取XML文档的内容就可以得出一下步骤:

1.通过DocumentBulderFoctory创建XML解释器.

2.通过解释器创建一个可以加载并生成XML的DocumentBuilder

3.通过DocumentBuilder加载并生成一颗XML树.Document对象的实例

4.通过Document可以遍历这颗树.并读取相应节点中的内容.



关于解释器的加载,以及生成器的加载都是放在import javax.xml.parsers包中的.该包中处了DOM以外还包括SAX解释器和生成器.那么下面我们将讨论关于SAX的话题.



通过DOM模型创建XML文档

通过DOM编程接口除了可以读取XML文档中的数据,还可以创建XML文档。因为DOM提供的对象可以生成一颗描述XML文档的数结构,所以通用,可以把这颗生成在内存中的树结构写入到任意的输出流。这就需要通过XmlDocument的write方法。该方法用以写出XML文档到输出流。

事例代码:

import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.apache.crimson.tree.XmlDocument;
import java.io.*;

public class DomXml{
public static void main(String[] args)throws Exception{
DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
DocumentBuilder builder=factory.newDocumentBuilder();
Document doc=builder.newDocument();
Element books=doc.createElement("Books");

Element book=doc.createElement("Book");
book.setAttribute("id","001");
book.appendChild(doc.createTextNode("J2EE Book"));

books.appendChild(book);
doc.appendChild(books);
((XmlDocument)doc).write(new FileOutputStream(new File("dom.xml")));
((XmlDocument)doc).write(System.out);
}


该程序的目的是创建一个XML文档而后,创建该文档的结构,然后输出所创建的结构,这里使用了两个流,一个是针对屏幕的输出流,还有一个是针对文件的输出流。

通过DOM修改XML文档

在JAXP中提出了,把DOM所生产的XML看成一个源,可以通输出流把所修改的源重新写回到XML文档中这样。从XML文件中解释出的DOM树就可以起到同步更新的作用。这个过程首先依赖雨XML文件的解释器。解释器首先分析XML文档然后生产树结构,应用程序可以操作这颗树,然后把操作结果作为源写回到XML文件中去。这里就要用到。DOMSource对象把Document所描述的树转变成源对象,然后可以通过一个Transformer把源写回到XML文件中去。下面给出一个程序事例文件。

文档如下,所操作的XML文件还是我们所熟悉的BookXml.xml。

事例代码如下:

import javax.xml.parsers.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.*;
import org.w3c.dom.*;

public class ModifyXml{
public static void main(String[] args)throws Exception{
DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
DocumentBuilder builder=factory.newDocumentBuilder();
Document doc=builder.parse("BookXml.xml");

Element book;
Element bookName;
Element bookAuthor;
Element bookISBN;
Element bookPrice;

//insert
book=doc.createElement("Book");
book.setAttribute("id","4");

bookName=doc.createElement("bookName");
bookName.appendChild(doc.createTextNode("J2EE Programme begin Book"));
book.appendChild(bookName);

bookAuthor=doc.createElement("bookAuthor");
bookAuthor.appendChild(doc.createTextNode("Hesj"));
book.appendChild(bookAuthor);

bookISBN=doc.createElement("bookISBN");
bookISBN.appendChild(doc.createTextNode("7-145-10241-3"));
book.appendChild(bookISBN);

bookPrice=doc.createElement("bookPrice");
bookPrice.appendChild(doc.createTextNode("77.8"));
book.appendChild(bookPrice);

Node books=doc.getElementsByTagName("Books").item(0);
books.appendChild(book);

//delete
// books.removeChild(book);

//modify
NodeList allBook=doc.getElementsByTagName("Book");
Element opBook=null;
for(int i=0;i<allBook.getLength();i++){
opBook=(Element)allBook.item(i);
String id=opBook.getAttribute("id");
if(id.equals("4")){ 
break;
}
}
opBook.getElementsByTagName("bookAuthor").item(0).getFirstChild().setNodeValue("Bluce");

TransformerFactory tf=TransformerFactory.newInstance();
Transformer tr=tf.newTransformer();
DOMSource ds=new DOMSource(doc);
StreamResult sr=new StreamResult(System.out);
tr.transform(ds,sr);
}
}

这里,首先创建一个Book节点。然后添加到Document中,紧接着可以删除和修改该节点。



简单XML 编程接口( SAX)

为了解决DOM解释器需要占有大量内存的问题,因此提出了一种不加载整个XML文档.生成树.而是逐段读取XML文档然后抛出事件的解释方案.因此提出了SAX编程接口的策略.这样SAX既无需生成内存中的树结构,又无需去创建任何对象去描述XML所以节约了内存开销.只是单纯的去解释XML文件而已.

进而了解SAX所发生的事件成了对使用SAX编程是必须的.对于SAX而言事件的抛出是由解释器完成的,而处理则是交给应用程序完成的,所以实现事件接口和填写事件代码就是我们的事情.对于事件可以处理也可以不处理,不过由于SAX是不创建任何对象的.所以事件的状态也是不保持的.如果想要在多个事件中维护状态,也同样是应用程序的事情.



SAX事件说明:

对于SAX而言事件就是其编程的核心.常用的事件主要有5种.

startDocument()

该事件表示解释器开始解释文档.它没有传递任何参数,因此这里无需处理XML文档数据.

endDocument()

该事件表明解释器准备结束对XML文档的解释.因此以上两个事件都没有做太多实际工作

startElement(...)

告诉您解析器发现了一个起始标签。该事件告诉您元素的名称、该元素所有属性的名称和值,还会告诉您一些名称空间的信息。

characters(...)

告诉您解析器发现了一些文本。您得到一个字符数组、该数组的偏移量和一个长度变量,有这三个变量您就可以访问解析器所发现的文本。

endElement(...)

告诉您解析器发现了一个结束标签。该事件告诉您元素的名称,以及相关的名称空间信息。

startElement()、characters() 和 endElement() 是最重要的事件,后面将集中介绍这三个事件。所有这些事件都属于 ContentHandler 接口,定义的其他 SAX 接口处理错误、实体及其他很少使用的内容。


 



startElement() 事件

startElement() 事件告诉您 SAX 解析器发现了一个元素的起始标签。该事件有四个参数:



String uri

名称空间 URI。由于我的 XML 文档没有使用名称空间,因此这里不讨论其意义,所以可以不理会它.关于名称空间可以参考其他XML相关资料.

String localName

一个不包括名称空间的元素名称

String qualifiedName

元素的限定名,即名称空间前缀和元素本地名称的组合。

org.xml.sax.Attributes attributes

一个包含该元素所有属性的集合.该对象提供了几种方法获取属性的名称和值,以及该元素的属性个数。

如果您的 XML 应用程序查找某个元素的内容,startElement() 事件可以告诉您该元素何时开始。


 

 



characters() 事件

characters() 事件包含解析器在源文件中发现的字符。本着最小化内存占用的精神,这个事件包含一个字符数组,这与 Java String 对象相比要轻型得多。以下是 characters() 事件的参数:



char[] characters

解析器所发现的字符数组。

int start

属于该事件的 characters 数组中的一个字符的索引号。

int length

该事件中字符的的个数。

如果您的 XML 应用程序需要存储特定元素的内容,可以把存储那些内容的代码放在 characters() 事件处理程序中。




endElement() 事件

endElement() 事件告诉您解析器发现了某个元素的结束标签。它有三个参数:

String uri String localName String qualifiedName

这三个参数和startElement一样.

对该事件的典型响应是改变 XML 应用程序中的状态信息。如,在我的程序中将是把读取的XML文档打印到屏幕中去.



那么下面我们将编写一个关于SAX的应用程序.同样它和刚才DOM那个程序相同用来打印所解释的XML文档信息.

代码如下:

import java.io.File;
import javax.xml.parsers.*;
import org.xml.sax.helpers.*;
import org.xml.sax.*;
 
 
public class SaxXml extends DefaultHandler{
private String ElementName;
private int id;
private String bookName;
private String bookAuthor;
private String bookISBN;
private String bookPrice;
public SaxXml(){
this.ElementName="";
this.id=0;
this.bookName="";
this.bookAuthor="";
this.bookISBN="";
this.bookPrice="";
}
public void startDocument(){
System.out.println("Document Begin");
}
public void startElement(String uri,String localName,String qName,Attributes attributes){
this.ElementName=qName;
if(qName.equals("Book"))
System.out.println(attributes.getValue(0));
}
public void characters(char[] ch,int start,int length){
String str=new String(ch,start,length);
if(this.ElementName.equals("bookName") && !str.trim().equals("")){
this.bookName=str;
}
if(this.ElementName.equals("bookAuthor") && !str.trim().equals("")){
this.bookAuthor=str;
}
if(this.ElementName.equals("bookISBN") && !str.trim().equals("")){
this.bookISBN=str;
}
if(this.ElementName.equals("bookPrice") && !str.trim().equals("")){
this.bookPrice=str;
} 
}
public void endElement(String uri,String localName,String qName){
if(qName.equals("bookName")){
System.out.println(this.bookName);
}
if(qName.equals("bookAuthor")){
System.out.println(this.bookAuthor);
}
if(qName.equals("bookISBN")){
System.out.println(this.bookISBN);
}
if(qName.equals("bookPrice")){
System.out.println(this.bookPrice);
}
if(qName.equals("Book")){
System.out.println();
} 
this.ElementName="";
}
public void endDocument(){
System.out.println("Document End");
System.exit(0);
}
public static void main(String[] args)throws Exception{
SAXParserFactory factory=SAXParserFactory.newInstance();
SAXParser parser=factory.newSAXParser();
parser.parse(new File("BookXml.xml"),new SaxXml());
}
}



该代码通过事件实现了读取XML文档的作用.这里稍微要注意的是空白区域的处理,我这里是通过String的trim方法解决的.当然你也可以使用其他方法.这个程序中.在startElement中用来设置ElementName(元素的名字)这个状态,而在characters通过该状态作出相应的动作.这里主要是把读取的信息放到我们的对象的属性中去.在通过endElement事件来读取该属性.



SAX的开发步骤如下:

1. 实现ContentHandler 接口并填写事件代码(这里我所用的是继承DefaultHandler类,该类实现了ContentHandler 接口)

2. 创建SAX解释器工厂

3. 通过工厂创建SAX解释器

4. 使用SAX解释器加载XML文档,在把已经实现了ContentHandler 接口的类实例对象装入解释器

5. 解释器通过回调你的事件过程