用Xerces-J的Dom方式解析XML文件

简介与背景知识

DOM(document objet model)是由W3C提出的一套把XML,HTML等文本转换成一系列对象的一套标准,根据这个标准,我们可以把一个XML文件转换成一系列在其他语言,例如java,javascript,C++,中可以访问的对象树。文档中的层次关系关系被转换成一棵树状的对象集合。我们可以从这个树中读取信息,可以向这个树添加信息,还可以从这个树中剔除信息,当然还可以把这棵树转换成原来的XML文件。由于DOM具有这些优点,尤其是可以动态修改的优点,我们将用大篇幅介绍。

下面是W3C提出的文档对象模型的继承关系,这里使用Java语言的界面来描述,所有的文件都包含在包org.w3c.dom中。

从这里我们可以看出,在整个对象的集成关系中,有四个最基本的界面,分别为 DOMImplementation,NamedNodeMap,Node和NodeList。其中Node界面是最重要的一个界面,用来描述文档的其他所有的界面都是从Node界面衍生出来的。Node界面及其实现类是DOM中最基本的组成元素,文档中的任何内容都可以被称作是一个节点。另外几个底层界面还包括DOMImplementation,这个界面用来进行一些与文档的实例无关的操作。

NamedNodeMap

NamedNodeMap用来描述一些没有顺序还可以用名字来引用的对象,典型的应用就是描述下面的XML代码表现的一系列的属性。

<testNamedNodeMap attr1=”test1” attr2=”test2” attr3=”test3”/>

这里面attr1,attr2,attr3等就可以用NamedNodeMap来描述。这个接口包含的方法如表1。

表1、NamedNodeMap中定义的方法

方法

使用说明

int getLength()

返回在Map里面的节点的个数。

Node getNamedItem(java.lang.String name)

通过节点的名字从Map里面获取节点。

Node getNamedItemNS(java.lang.String namespaceURI, java.lang.String localName)

通过节点的名空间和节点的名字从Map里面获取节点。

Node item(int index)

这个方法Map中的第index个节点,这个方法应该同getLength()方法协同使用。index从0开始计,如果index的值大于或等于用getLength得到的值,这个方法返回null。

Node removeNamedItem(java.lang.String name)

通过引用节点的名字,从Map中删除一个节点。返回值为被删除的节点。
Node removeNamedItemNS( java.lang.String namespaceURI, java.lang.String localName) 通过引用节点的名空间和节点的名字,从Map中删除一个节点。返回值为被删除的节点。
Node setNamedItem(Node arg) 向Map中增加一个节点。返回值为增加的节点。使用这个节点的nodeName属性。
Node setNamedItemNS(Node arg) 向Map中增加一个节点。返回值为增加的节点。使用这个节点的namespaceURI属性和localName属性。
NodeList

NodeList用来描述一系列有前后顺序的节点,典型的应用就是描述一个节点的子节点。NodeList界面只有两个方法,一个是int getLength(),另一个是Node item(int index )。这两个方法的使用同NamedNodeMap中的同名方法相同。

Node

Node是DOM中最重要的一个界面,是所有DOM对象的基础,要掌握DOM就必须熟悉理解Node,因此Node是我们介绍的重点。

Node(节点)可以用来描述XML文档中的任何级别的元素,一个文档可以被称作一个Node(节点),一个<…/>表示的部分可以被称作一个Node,一个用<…>…</…>表示的部分可以被称作一个Node,一个属性可以被称作Node,一段注释可以被称作Node,一条处理指令可以北称作Node,一段文字也是Node,总而言之,一个XML文档用DOM表示出来后就是由Node(节点)组成的一棵树。

下面我们介绍Node界面中定义的数据和方法。表2列出了Node中定义的数据,表3列出了Node中定义的方法。

表2:Node界面中定义的静态数据

数据 意义
short ATTRIBUTE_NODE 表示这个节点是一个Attr。
short CDATA_SECTION_NODE 表示这个节点是一个CDATASection。
short COMMENT_NODE 表示这个节点是一个Comment。
short DOCUMENT_FRAGMENT_NODE 表示这个节点是一个DocumentFragment。
short DOCUMENT_NODE 表示这个节点是一个Document。
short DOCUMENT_TYPE_NODE 表示这个节点是一个DocumentType。
short ELEMENT_NODE 表示这个节点是一个Element。
short ENTITY_NODE 表示这个节点是一个Entity。
short ENTITY_REFERENCE_NODE 表示这个节点是一个EntityReference。
short NOTATION_NODE 表示这个节点是一个Notation。
short PROCESSING_INSTRUCTION_NODE 表示这个节点是一个ProcessingInstruction。
short TEXT_NODE 表示这个节点是一个Text。
这些数据可以同Node界面中的getNodeType()方法协同使用,区分Node的类型。下面的一段程序演示了使用这些数据区分不同节点类型的基本方法。

switch( oNode.getNodeType() )

{

case Node.ATTRIBUTE_NODE: System.out.println(“ This is a Attr);

break;

case Node.CDATA_SECTION_NODE: System.out.println

(“This is a CDATASection”);

break;

default: System.out.println(“Other Node”);

}

表3:Node界面中声明的方法

方法 使用说明
Node appendChild(Node newChild) 在这个节点中增加一个子节点。这个新增加的子节点将被添加在子节点队列的尾部。如果参数实际上是一个DocumentFragment,其全部内容会被加入到子节点队列中。返回值为被添加的节点。
Node cloneNode(boolean deep) 这个方法复制这个节点,参数控制是否复制子节点,但是如果存在属性会复制所有的属性,包括由DTD定义产生的缺省属性。需要注意的是这个方法返回的节点不包括父节点属性,即getParentNode()返回null。
NamedNodeMap getAttributes() 返回一个包含所有的属性(Attributes)的NamedNodeMap。如果没有,则返回null。
NodeList getChildNodes() 返回一个包含所有的子节点的NodeList。或返回一个不包含任何节点的NodeList。
Node getFirstChild() 返回第一个子节点。若没有则返回null。
Node getLastChild() 返回最后一个子节点。若没有则返回null。
String getLocalName() 返回不包括名空间的局域名,若该节点不支持名空间,则返回null。
String getNamespaceURI() 返回节点的名空间URI,若没有,则返回null,而不是空的字符串。
Node getNextSibling() 返回这个节点后面的兄弟节点,如果没有,则返回null。Sibling为兄弟的意思。
String getNodeName() 返回节点的名字。
short getNodeType() 返回节点的类型,其类型如表2。
String getNodeValue() 返回节点的值。
Document getOwnerDocument() 返回包含此节点的Document。
Node getParentNode() 返回父节点。
String getPrefix() 返回名空间的前置部分。
Node getPreviousSibling() 返回前一个兄弟节点。若没有,则返回null。
boolean hasAttributes() 返回是否有属性。
boolean hasChildNodes() 返回是否有子节点。
Node insertBefore(Node newChild,

Node refChild)
在制定的节点处插入一个节点,插入的节点作为第一个参数,插入位置作为第二个参数。
boolean isSupported( String feature,

String version)
测试解析器是否支持某些特性和版本号。
void normalize() 调用此方法可以合并相邻的Text节点,消除空的Text节点。
Node removeChild(Node oldChild) 从子节点队列中去除一个节点,然后返回该节点。
Node replaceChild(Node newChild,

Node oldChild)
用第一个参数制定的节点取代用第二个参数制定的子节点。
void setNodeValue(String nodeValue) 设置节点的值。
void setPrefix(java.lang.String prefix) 设置名空间的前置值。
Element

Node界面派生的界面我们首先要介绍的是Element界面,这个界面用来描述一个用</>或者<…>…</…>表示的部分。在一个Element的实例里面可以包含一个或者多个的Attr(属性的缩写),如XML语句<testElement attr1=”1” attr2=”2”/>用DOM来描述就是一个tag叫做testElement的Element,包含两个Attr,一个名字为attr1,值为1,另一个名字为attr2,值为2。Element界面继承了Node的所有方法和数据,其特有的方法内容如表4。

表4:Element界面的特有方法

方法 使用说明
String getAttribute(String name) 调用此方法可以获取属性的值,参数为属性名,返回值为属性值。
Attr getAttributeNode(String name) 调用此方法可以获取属性节点的引用,参数为属性名,返回值为属性节点的引用。
Attr getAttributeNodeNS(String

namespaceURI, String localName)
调用此方法可以获取属性节点的引用,参数为属性的名空间和属性的局域名,返回值为属性节点的引用。
String getAttributeNS(String

namespaceURI, localName)
调用此方法可以获取属性的值,参数为属性的名空间和属性的局域名,返回值为属性值。
NodeList getElementsByTagName(String name) 调用此方法可以获取所有具有参数指定的tag的子Element,并且在一个NodeList中返回。
NodeList getElementsByTagNameNS(String

namespaceURI, String localName)
同上一方法,但是tag的名字用名空间表示。
String getTagName() 返回这个Element的tag。例如XML语句<test>…</test>将会返回test。
boolean hasAttribute(String name) 这个方法检验是否有指定属性名的属性。
boolean hasAttributeNS(String

namespaceURI,String localName)
同上方法,但是属性名用名空间表示。
void removeAttribute(String name) 删除一个属性,参数为属性名。
Attr removeAttributeNode(Attr oldAttr) 删除一个属性,参数为属性节点。
void removeAttributeNS(String

namespaceURI,String localName)
删除一个属性,参数为名空间URI和局域名。
void setAttribute(String name, String value) 设置一个属性的值,第一个参数是属性名,第二个参数是属性值。如果该属性在设置之前不存在,则增加一个新的属性。
Attr setAttributeNode(Attr newAttr) 设置一个属性节点,参数为一个Attr对象。
Attr setAttributeNodeNS(Attr newAttr) 设置一个属性节点,参数为一个带名空间和局域名的Attr对象。
void setAttributeNS(String namespaceURI, String qualifiedName,String value) 设置一个属性的值,第一个参数是名空间,第二个参数是属性名,第三个参数是属性值。如果该属性在设置之前不存在,则增加一个新的属性。
Document

Document界面是DOM中另外一个重要的界面,Document界面用来表示一个完整的XML或者HTML文档,Document界面的实例是一个文档对象树的根,同时提供一系列的动态生成DOM元素的方法。Document继承Node的所有方法,其特有的方法如表5。

表5:Document界面特有的方法

方法 使用说明
Attr createAttribute(java.lang.String name) 用指定的名字动态创建一个Attr。
Attr createAttributeNS(java.lang.String namespaceURI,java.lang.String qualifiedName) 用指定的名空间和名字动态创建一个Attr。第一个参数为名空间的URI,第二个参数为Attr的名字。
CDATASection createCDATASection(java.lang.String data)

动态创建一个CDATASection,参数为CDATASection的数据。
Comment createComment(java.lang.String data) 动态创建一个Comment(注释),参数为注释的内容。
DocumentFragment createDocumentFragment() 动态创建一个空的DocumentFragment。
Element createElement(java.lang.String tagName) 动态创建一个Element,这个方法是我们在动态修改DOM树内容,即动态创建XML的时候最常用的方法之一,参数为Element的tag。这个方法一般会和createAttribute和createTextNode方法合用。
Element createElementNS(java.lang.String namespaceURI,java.lang.String qualifiedName) 动态创建一个支持名空间的Element,第一个参数为名空间的URI第二个参数为Element的tag名。
EntityReference createEntityReference(java.lang.String name)

动态创建一个EntityReference元素,如果这个EntityReference已知,则创建其子节点。
ProcessingInstruction createProcessingInstruction(java.lang.String target, java.lang.String data) 动态创建一个ProcessingInstruction,例如

<?xml-stylesheet type="text/xml" href="14-2.xsl"?>其中xml-stylesheet为第一个参数即target的内容,type="text/xml" href="14-2.xsl"为第二个参数即data中的内容。
Text createTextNode(java.lang.String data) 动态创建一个Text节点,这可是一个重要的方法,一个XML文件中的“实质内容” 可都是用这个方法创建的,因此一定要牢牢记住。参数为节点的内容。
DocumentType getDoctype() 这个方法返回DocumentType(文件类型定义)。
Element getDocumentElement() 返回文档的根元素。同整个文档不同,这个Element不会包含DocumentType。如果解析的是一个HTML文件,那么他的根元素是<html>这个标签。
Element getElementById(java.lang.String elementId) 根据元素的ID返回一个Element。这是最有用的方法之一,可以迅速地找到一个元素。元素的ID是由在DTD中声明的ATTLIST决定的,如以下代码<!ATTLIST test1 id ID #REQUIRED>声明了test1这个Element有一个标记为id的属性作为ID。参数是ID。如果在XML文档中有不只一个采用这个ID的元素,那么解析器的行为不可预测。
NodeList getElementsByTagName(java.lang.String tagname) 通过tag取得所有使用这个tag的元素的引用。这也是最有用的方法之一,可以迅速地找到一系列使用同一个tag的元素。参数是tag的名字,返回值在一个NodeList里面返回。
NodeList getElementsByTagNameNS(java.lang.String namespaceURI,

java.lang.String localName)
同上,但是使用名空间访问。
DOMImplementation getImplementation() 返回正在使用的DOMImplementation对象。
Node importNode( Node importedNode, boolean deep) 从其他文档对象引入一个节点。第一个参数是节点的引用,第二个参数决定是否引入子节点。原节点不会被改变或者被删除。在引入的同时不会改变名空间信息。但是Element的缺省属性不会被拷贝。
Attr

Attr界面用来描述一个Element对象里面的属性,Attr界面继承了Node界面的所有的数据和方法。其特有的方法如表6。

表6:Attr界面中的方法

方法 使用说明
String getName() 返回属性的名字,就是等号前面的部分。
Element getOwnerElement() 返回拥有这个属性的父节点。
boolean getSpecified() 说明这个属性是在XML文档中声明的还是由DTD自动生成的缺省属性。如果是前者则返回true,如果是后者则返回false。
java.lang.String getValue() 返回属性的值,就是等号后面的部分。
void setValue(java.lang.String value) 设置属性的值。
CharacterData

这个界面专门用来表示字符串数据,是Text等几个非常有用的界面的父界面。提供了许多专门用于字符串数据处理的方法,如表7。

表7:CharacterData界面中的方法

方法 使用说明
void appendData(java.lang.String arg) 在字符串的尾部增添数据。参数为要增加的字符串。
void deleteData(int offset,int count) 删除字符串中的一部分数据,第一个参数为开始位置,第二个为字符的个数。个人认为更有用的是删除全部数据。
java.lang.String getData() 返回字符串数据。但是这个方法不保证返回这个对象内的数据,甚至可能返回整个XML文档。
int getLength() 返回数据长度。
void insertData(int offset,

java.lang.String arg)
这个方法用来在字符串中插入另一个一个字符串,第一个参数为开始位置,第二个参数是要插入的字符串。
void replaceData(int offset,

int count,

java.lang.String arg)
这个方法用来在字符串中置换一部分数据,第一个参数为开始位置,第二个为长度,如果第一个参数和第二个参数的和超过了数据的长度,那么将会删除开始位置后面的所有的数据,然后把新的数据追加在后面。
void setData(java.lang.String data) 这个方法置换所有的字符串数据。
java.lang.String substringData(int offset,

int count)
这个方法从数据中获取一部分字符串,第一个参数为开始位置,第二个参数为长度。如果第一个参数和第二个参数的和超过了数据的长度,那么忽略的二个参数,返回实际长度的字符串。
 

Comment以及Text

Comment表示了XML文档中的一段注释,这个界面仅仅简单地继承了CharacterData界面,没有任何属于自己的方法。

Text界面表示XML文档中Element的两个tag中间的部分,用一个熟悉开放源代码开发的朋友易于接受的方法描述就是可以在Mozilla浏览器里面直接显示出来的部分。Text界面也继承了CharacterData界面,增加了一个方法,叫做splitText(int offset)这个方法可以把一个Text对象分割为两个Text对象,参数为分割的位置。

除了我们上面介绍的界面,DOM还包含其他的一些界面,这些界面在程序设计的过程中并不常用,这里就不再介绍了。

Xerces对DOM的实现

Xerces针对每一个DOM的界面都提供了一个实现的类。这些类都在org.apache.xerces.dom包中,类的名字基本上就是在W3C的DOM包中的每一个界面的名字后面加上Impl(Implement,英文“实现”的缩写)。每一个W3C的DOM中的界面的实例都可以被cast为Xerces中的相应的Impl类的实例。但是NodeList没有叫做NodeListImpl实现,而是在NodeImpl类里面一并完成了。不过需要注意的是NodeList的实例并不能被转化为NodeImpl类的实例。

使用Xerces-J的DOM方式来解析XML文件

在使用Xerces-J的DOM方式来进行XML文件的解析的时候,我们需要用到一个叫做DOMParser的类。和我们在上面曾经介绍过的SAXParser一样,这个类在org.apache.xerces.parsers包中。这个类中重要的方法如表8。

表8:DOMParser中的重要方法

方法 使用说明
   
   
   
   
   
下面我们用一个例子说明如何使用Xerces-J的DOM方式来进行XML文件的解析。这个例子同时还演示了如何进行序列化和如何遍历整个文档树以及如何动态创建并添加一个节点。在Windows98,JDK1.2,Jview环境下调试通过。

import java.io.*;

import org.w3c.dom.*;

import org.apache.xerces.dom.*;

import org.apache.xerces.parsers.DOMParser;

import org.xml.sax.InputSource;

import org.apache.xml.serialize.XMLSerializer;

import org.apache.xml.serialize.OutputFormat;

public class TestXerces1{

DocumentImpl oDoc = null;

int tabNum = 0 ;

public static void main( String[] x)

{

new TestXerces1();

}

public TestXerces1()

{

try

{

DOMParser parser = new DOMParser();

parser.setFeature( "http://apache.org/xml/features/dom/include-ignorable-whitespace" , false );

parser.parse(new InputSource( new FileReader( new File( "testXerces.xml" ))));

oDoc = ( DocumentImpl ) parser.getDocument();

put( oDoc);

add();

put( oDoc);

XMLSerializer oXSerializer = new XMLSerializer( System.out , new OutputFormat( oDoc , "GB2312" , true ) );

oXSerializer.serialize( oDoc );

Element oOne = oDoc.getElementById("111");

if( null == oOne)

{

System.out.println(" test1 was not found!");

}

else

{

oXSerializer.serialize( oOne );

}

}

catch( Exception e )

{

e.printStackTrace();

}

}

void put( NodeImpl __oPut )

{

switch( __oPut.getNodeType() )

{

case Node.DOCUMENT_NODE:

{

put( (ElementImpl)((Document)__oPut).getDocumentElement() );

break;

}

case Node.ELEMENT_NODE:

{

tabNum++;

outTagName( __oPut.getNodeName() );

NamedNodeMapImpl oAttrs = ( NamedNodeMapImpl ) __oPut.getAttributes();

for( int i = 0 ; i < oAttrs.getLength() ; i ++)

{

put( ( NodeImpl )oAttrs.item( i ) );

}

NodeImpl oChild = (NodeImpl) __oPut.getChildNodes();

for( int i = 0 ; i < oChild.getLength() ; i ++ )

{

put( ( NodeImpl )oChild.item( i ) );

}

break;

}

case Node.TEXT_NODE:

{

tabNum++;

outText( __oPut.getNodeValue() );

break;

}

case Node.ATTRIBUTE_NODE:

{

outAttr( __oPut.getNodeName(), __oPut.getNodeValue() );

tabNum++;

}

}

tabNum--;

}

void add()

{

Element oAdd = oDoc.createElement("test3");

Text oText = oDoc.createTextNode("hello");

oAdd.appendChild( oText );

NodeImpl oNode = (NodeImpl)oDoc.getElementsByTagName("test2").item(0);

oNode.appendChild( oAdd);

}

void outTagName( String __strTagName)

{

outString( "TagName:"+__strTagName);

}

void outString( String __strString )

{

String strTab = new String();

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

{

strTab +=" ";

}

System.out.println( "out:"+strTab+__strString );

}

void outText( String __strText )

{

outString( " TEXT:"+__strText );

}

void outAttr( String __strName, String __strValue)

{

outString( " ATTR:"+__strName+"="+__strValue );

}

}

运行结果如图1。

图1:TestXerces1的运行结果

 

一个使用dom方式的留言板的例子。