xml解析之DOM

DOM 是Document Object Model 的缩写,即文档对象模型。XML 将数据组织为一颗树,所以DOM 就是对这颗树的一个对象描叙。通俗的说,就是通过解析XML 文档,为 XML 文档在逻辑上建立一个树模型,树的节点是一个个对象。我们通过存取这些对象就能够存取XML 文档的内容。

下面我们来看一个简单的例子,看看在DOM 中,我们是如何来操作一个XML 文档的。

 

这是一个XML 文档,也是我们要操作的对象:

 

<?xml version="1.0" encoding="UTF-8"?>

<messages>

<message>Good-bye serialization, hello Java!</message>

</messages>

 

下面,我们需要把这个文档的内容解析到一个Java 对象中去供程序使用,利用JAXP ,我们只需几行代码就能做到这一点。首先,我们需要建立一个解析器工厂,以利用这个工厂来获得一个具体的解析器对象:

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

我们在这里使用DocumentBuilderFacotry 的目的是为了创建与具体解析器无关的程序,当DocumentBuilderFactory 类的静态方法newInstance() 被调用时,它根据一个系统变量来决定具体使用哪一个解析器。又因为所有的解析器都服从于JAXP 所定义的接口,所以无论具体使用哪一个解析器,代码都是一样的。所以当在不同的解析器之间进行切换时,只需要更改系统变量的值,而不用更改任何代码。这就是工厂所带来的好处。这个工厂模式的具体实现,可以参看下面的类图。

  

DocumentBuilder builder = factory.newDocumentBuilder();

当获得一个工厂对象后,使用它的静态方法newDocumentBuilder() 方法可以获得一个DocumentBuilder 对象,这个对象代表了具体的DOM 解析器。但具体是哪一种解析器,微软的或者IBM 的,对于程序而言并不重要。

 

API javax.xml.parsers.DocumentBuilderFactory 1.4

返回DocumentBuilderFactory 类的一个实例

static DocumentBuilderFactory newInstance()

返回DocumentBuilder 类的一个实例

DocumentBuilder newDocumentBuilder()

 

然后,我们就可以利用这个解析器来对XML 文档进行解析了:

Document doc = builder.parse("src/message.xml");

 

DocumentBuilder 的parse() 方法接受一个XML 文档名作为输入参数,返回一个Document 对象,这个Document 对象就代表了一个XML 文档的树模型。以后所有的对XML 文档的操作,都与解析器无关,直接在这个Document 对象上进行操作就可以了。而具体对 Document 操作的方法,就是由DOM 所定义的了。

 

API javax.xml.parsers.DocumentBuilder 1.4

解析来自给定文件、URL 或输入流的XML 文档,返回解析后的文档

  • Document parse(File f)
  • Document parse(String url)
  • Document parse(InputStream in)

JAXP 支持W3C 所推荐的DOM 2 。如果你对DOM 很熟悉,只需要按照DOM 的规范来进行方法调用就可以。DOM 是用来描叙XML 文档中的数据的模型,引入DOM 的全部原因就是为了用这个模型来操作XML 文档的中的数据。DOM 规范中定义有节点(即对象)、属性和方法,我们通过这些节点的存取来存取XML 的数据。

上面得到了Document 对象,使用它的 getElementsByTagName() 方法,我们可以得到一个NodeList 对象,一个Node 对象代表了一个XML 文档中的一个标签元素,而 NodeList 对象,观其名而知其意,所代表的是一个Node 对象的列表:

NodeList children = doc.getElementsByTagName("message");

我们通过这样一条语句所得到的是XML 文档中所有<message> 标签对应的Node 对象的一个列表。然后,我们可以使用NodeList 对象的item() 方法来得到列表中的每一个Node 对象:

Node child = children.item(0);

当一个Node 对象被建立之后,保存在XML 文档中的数据就被提取出来并封装在这个Node 中了。在这个例子中,要提取Message 标签内的内容,我们通常会使用Node 对象的getNodeValue() 方法: String message = child.getFirstChild().getNodeValue();

注意: 这里还使用了一个getFirstChild() 方法来获得message 下面的第一个子Node 对象。虽然在message 标签下面除了文本外并没有其它子标签或者属性,但是我们坚持在这里使用getFirseChild() 方法,这主要和W3C 对DOM 的定义有关。W3C 把标签内的文本部分也定义成一个Node ,所以先要得到代表文本的那个Node ,我们才能够使用getNodeValue() 来获取文本的内容。

现在,既然已经能够从XML 文件中提取出数据了,就可以把这些数据用在合适的地方,来构筑应用程序。

DOM 的基本对象有5 个:Document ,Node ,NodeList ,Element 和Attr 。如下结构图。

 

Document 对象代表了整个XML 的文档,所有其它的Node ,都以一定的顺序包含在Document 对象之内,排列成一个树形的结构,程序员可以通过遍历这颗树来得到XML 文档的所有的内容,这也是对XML 文档操作的起点。我们总是先通过解析XML 源文件而得到一个Document 对象,然后再来执行后续的操作。此外,Document 还包含了创建其它节点的方法,比如createAttribut() 用来创建一个Attr 对象。它所包含的主要的方法有:

API org.w3c.dom.Document

createAttribute(String) :用给定的属性名创建一个Attr 对象,并可在其后使用setAttributeNode 方法来放置在某一个Element 对象上面。

 

createElement(String) : 用给定的标签名创建一个Element 对象,代表XML 文档中的一个标签,然后就可以在这个Element 对象上添加属性或进行其它的操作。

 

createTextNode(String) : 用给定的字符串创建一个Text 对象,Text 对象代表了标签或者属性中所包含的纯文本字符串。如果在一个标签内没有其它的标签,那么标签内的文本所代表的Text 对象是这个Element 对象的唯一子对象。

 

getElementsByTagName(String) : 返回一个NodeList 对象,它包含了所有给定标签名字的标签。

 

getDocumentElement() : 返回一个代表这个DOM 树的根节点的Element 对象,也就是代表XML 文档根元素的那个对象。

 

Node 对象是DOM 结构中最为基本的对象,代表了文档树中的一个抽象的节点。在实际使用的时候,很少会真正的用到Node 这个对象,而是用到诸如Element 、Attr 、Text 等Node 对象的子对象来操作文档。Node 对象为这些对象提供了一个抽象的、公共的根。虽然在Node 对象中定义了对其子节点进行存取的方法,但是有一些Node 子对象,比如Text 对象,它并不存在子节点,这一点是要注意的。Node 对象所包含的主要的方法有:

API org.w3c.dom.Node

appendChild(org.w3c.dom.Node) :为这个节点添加一个子节点,并放在所有子节点的最后,如果这个子节点已经存在,则先把它删掉再添加进去。

 

getFirstChild() : 如果节点存在子节点,则返回第一个子节点,对等的,还有getLastChild() 方法返回最后一个子节点。

 

getNextSibling() : 返回在DOM 树中这个节点的下一个兄弟节点,对等的,还有getPreviousSibling() 方法返回其前一个兄弟节点。

 

getNodeName() : 根据节点的类型返回节点的名称。

 

getNodeType() : 返回节点的类型。

 

getNodeValue() : 返回节点的值。

 

hasChildNodes() : 判断是不是存在有子节点。

 

hasAttributes() : 判断这个节点是否存在有属性。

 

getOwnerDocument() : 返回节点所处的Document 对象。

 

insertBefore(org.w3c.dom.Node neworg.w3c.dom.Node ref) : 在给定的一个子对象前再插入一个子对象。

 

removeChild(org.w3c.dom.Node) : 删除给定的子节点对象。

 

replaceChild(org.w3c.dom.Node neworg.w3c.dom.Node old) : 用一个新的Node 对象代替给定的子节点对象。

 

NodeList 对象,顾名思义,就是代表了一个包含了一个或者多个Node 的列表。可以简单的把它看成一个Node 的数组,我们可以通过方法来获得列表中的元素:

 

API org.w3c.dom. NodeList

GetLength() : 返回列表的长度。

Item(int) : 返回指定位置的Node 对象。

 

Element 对象代表的是XML 文档中的标签元素,继承于Node ,亦是Node 的最主要的子对象。在标签中可以包含有属性,因而Element 对象中有存取其属性的方法,而任何Node 中定义的方法,也可以用在Element 对象上面。

API org.w3c.dom. Element

getElementsByTagName(String) : 返回一个NodeList 对象,它包含了在这个标签中其下的子孙节点中具有给定标签名字的标签。

 

getTagName() : 返回一个代表这个标签名字的字符串。

 

getAttribute(String) : 返回标签中给定属性名称的属性的值。在这儿需要主要的是,应为XML 文档中允许有实体属性出现,而这个方法对这些实体属性并不适用。这时候需要用到getAttributeNodes() 方法来得到一个Attr 对象来进行进一步的操作。

 

getAttributeNode(String) : 返回一个代表给定属性名称的Attr 对象。

 

Attr 对象代表了某个标签中的属性。Attr 继承于Node ,但是因为Attr 实际上是包含在Element 中的,它并不能被看作是 Element 的子对象,因而在DOM 中Attr 并不是DOM 树的一部分,所以Node 中的getparentNode() , getpreviousSibling() 和getnextSibling() 返回的都将是null 。也就是说,Attr 其实是被看作包含它的 Element 对象的一部分,它并不作为DOM 树中单独的一个节点出现。这一点在使用的时候要同其它的Node 子对象相区别。

 

需要说明的是,上面所说的DOM 对象在DOM 中都是用接口定义的,在定义的时候使用的是与具体语言无关的IDL 语言来定义的。因而, DOM 其实可以在任何面向对象的语言中实现,只要它实现了DOM 所定义的接口和功能就可以了。同时,有些方法在DOM 中并没有定义,是用IDL 的属性来表达的,当被映射到具体的语言时,这些属性被映射为相应的方法。

<?xml version="1.0" encoding="UTF-8"?>
<links>
	<link>
		<text>The makers of Java</text>
		<url newWindow="no">http://java.sun.com</url>
		<author>Sun Microsystems</author>
		<date>
			<day>5</day>
			<month>4</month>
			<year>2008</year>
		</date>
		<description>Sun Microsystem's website.</description>
	</link>
	<link>
		<text>Janwer's Homepage</text>
		<url>www.janwer.com</url>
		<author>张峻伟</author>
		<date>
			<day>5</day>
			<month>3</month>
			<year>2008</year>
		</date>
		<description>
			A site from Janwer Zhang, give u lots of suprise!!!
		</description>
	</link>
	<link>
		<text>张峻伟的个人主页</text>
		<url>janwer.iteye.com</url>
		<author>张峻伟</author>
		<date>
			<day>5</day>
			<month>3</month>
			<year>2008</year>
		</date>
		<description>有关J2EE</description>
	</link>
	<link>
		<text>zhang janwer's Homepage</text>
		<url>www.junwei.com</url>
		<author>janwer zhang</author>
		<date>
			<day>6</day>
			<month>3</month>
			<year>2008</year>
		</date>
		<description>A site from J2EE,C#,C++,C and so on!</description>
	</link>
</links>

 

我们希望在一个名为server.xml 文件中保存了一些URL 地址,通过一个简单的程序,我们可以通过DOM 把这些URL 读出并显示出来,也可以反过来向这个XML 文件中写入加入的URL 地址。

package cn.janwer.xml;

import javax.xml.parsers.*;
import org.w3c.dom.*;

public class XmlRead {
	public static void main(String args[]) {
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			Document doc = builder.parse("src/server.xml");
			doc.normalize(); //以去掉XML文档中作为格式化内容的空白
			/**
			 * XML文档中的空白符也会被作为对象映射在DOM树中。
			 * 因而,直接调用Node方法的 getChildNodes方法有时候会有些问题,
			 * 有时不能够返回所期望的NodeList对象。
			 * 解决的办法是使用Element的 getElementByTagName(String),
			 * 返回的NodeLise就是所期待的对象了。然后,可以用item()方法提取想要的元素。 
			 */
			NodeList links = doc.getElementsByTagName("link");
			for (int i = 0; i < links.getLength(); i++) {
				Element link = (Element) links.item(i);
				System.out.print("Content: ");
				System.out.println(link.getElementsByTagName("text").item(0)
						.getFirstChild().getNodeValue());
				System.out.print("URL: ");
				System.out.println(link.getElementsByTagName("url").item(0)
						.getFirstChild().getNodeValue());
				System.out.print("Author: ");
				System.out.println(link.getElementsByTagName("author").item(0)
						.getFirstChild().getNodeValue());
				System.out.print("Date: ");
				Element linkdate = (Element) link.getElementsByTagName("date")
						.item(0);
				String day = linkdate.getElementsByTagName("day").item(0)
						.getFirstChild().getNodeValue();
				String month = linkdate.getElementsByTagName("month").item(0)
						.getFirstChild().getNodeValue();
				String year = linkdate.getElementsByTagName("year").item(0)
						.getFirstChild().getNodeValue();
				System.out.println(day + "-" + month + "-" + year);
				System.out.print("Description: ");
				System.out.println(link.getElementsByTagName("description")
						.item(0).getFirstChild().getNodeValue().trim());
				System.out.println();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

	}
}

 

下面的内容,就是在修改了DOM 树后重新写入到XML 文档中去的问题了。这个程序名为 Xmlwrite.java 。在JAXP1.0 版本中,并没有直接的类和方法能够处理XML 文档的写入问题,需要借助其它包中的一些辅助类。而在 JAXP1.1 版本中,引入了对XSLT 的支持,所谓XSLT ,就是对XML 文档进行变换(Translation )后,得到一个新的文档结构。利用这个新加入的功能,我们就能够很方便的把新生成或者修改后的DOM 树从新写回到XML 文件中去了,下面我们来看看代码的实现,这段代码的主要功能是向 links.xml 文件中加入一个新的link 节点。

 

我们希望在上面的XML 文件中加入一个新的link 节点,因而首先还是要读入links.xml 文件,构建一个DOM 树,然后再对这个DOM 树进行修改(添加节点),最后把修改后的DOM 写回到links.xml 文件中:

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance ();

DocumentBuilder builder = factory.newDocumentBuilder();

Document doc = builder.parse("src/server.xml");

doc.normalize();

// --- 取得变量 ----

String text = "zhang janwer's Homepage";

String url = "www.junwei.com";

String author = "janwer zhang";

String discription = "A site from J2EE,C#,C++,C and so on!";

为了看清重点,简化程序,我们把要加入的内容硬编码到记忆String 对象中,而实际操作中,往往利用一个界面来提取用户输入,或者通过JDBC 从数据库中提取想要的内容。

Text textseg;

Element link=doc.createElement("link");

 

首先应该明了的是,无论什么类型的Node ,Text 型的也好,Attr 型的也好,Element 型的也好,它们的创建都是通过Document 对象中的 createXXX() 方法来创建的(XXX 代表具体要创建的类型)。

创建节点的过程可能有些千篇一律,但需要注意的地方是,对Element 中所包含的text (在DOM 中,这些text 也是代表了一个Node 的,因此也必须为它们创建相应的node ),不能直接用Element 对象的setNodeValue() 方法来设置这些text 的内容,而需要用创建的Text 对象的setNodeValue() 方法来设置文本,这样才能够把创建的Element 和其文本内容添加到DOM 树中。看看前面的代码,你会更好的理解这一点:

doc.getDocumentElement().appendChild(link);

最后,不要忘记把创建好的节点添加到DOM 树中。Document 类的getDocumentElement() 方法,返回代表文档根节点的Element 对象。在XML 文档中,根节点一定是唯一的。

TransformerFactory tFactory =TransformerFactory.newInstance();

Transformer transformer = tFactory.newTransformer();

DOMSource source = new DOMSource(doc);

StreamResult result = new StreamResult(new java.io.File("links.xml"));

transformer.transform(source, result);

 

然后就是用XSLT 把DOM 树输出了。这里的TransformerFactory 也同样应用了工厂模式,使得具体的代码同具体的变换器无关。实现的方法和DocumentBuilderFactory 相同,这儿就不赘述了。Transformer 类的transfrom 方法接受两个参数、一个数据源Source 和一个输出目标Result 。这里分别使用的是DOMSource 和StreamResult ,这样就能够把DOM 的内容输出到一个输出流中,当这个输出流是一个文件的时候,DOM 的内容就被写入到文件中去了。

package cn.janwer.xml;

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

public class Xmlwriter {
	public static void main(String args[]) {
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory
					.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			Document doc = builder.parse("src/server.xml");
			doc.normalize();
			// ---取得变量----
			String text = "zhang janwer's Homepage";
			String url = "www.junwei.com";
			String author = "janwer zhang";
			String discription = "A site from J2EE,C#,C++,C and so on!";
			// -------------
			Text textseg;
			Element link = doc.createElement("link");

			Element linktext = doc.createElement("text");
			textseg = doc.createTextNode(text);
			linktext.appendChild(textseg);
			link.appendChild(linktext);

			Element linkurl = doc.createElement("url");
			textseg = doc.createTextNode(url);
			linkurl.appendChild(textseg);
			link.appendChild(linkurl);

			Element linkauthor = doc.createElement("author");
			textseg = doc.createTextNode(author);
			linkauthor.appendChild(textseg);
			link.appendChild(linkauthor);

			java.util.Calendar rightNow = java.util.Calendar.getInstance();
			String day = Integer.toString(rightNow.get(java.util.Calendar.DAY_OF_MONTH));
			String month = Integer.toString(rightNow.get(java.util.Calendar.MONTH));
			String year = Integer.toString(rightNow.get(java.util.Calendar.YEAR));
			Element linkdate = doc.createElement("date");

			Element linkdateday = doc.createElement("day");
			textseg = doc.createTextNode(day);
			linkdateday.appendChild(textseg);

			Element linkdatemonth = doc.createElement("month");
			textseg = doc.createTextNode(month);
			linkdatemonth.appendChild(textseg);

			Element linkdateyear = doc.createElement("year");
			textseg = doc.createTextNode(year);
			linkdateyear.appendChild(textseg);

			linkdate.appendChild(linkdateday);
			linkdate.appendChild(linkdatemonth);
			linkdate.appendChild(linkdateyear);
			link.appendChild(linkdate);

			Element linkdiscription = doc.createElement("description");
			textseg = doc.createTextNode(discription);
			linkdiscription.appendChild(textseg);
			link.appendChild(linkdiscription);

			doc.getDocumentElement().appendChild(link);

			TransformerFactory tFactory = TransformerFactory.newInstance();
			Transformer transformer = tFactory.newTransformer();
			DOMSource source = new DOMSource(doc);
			StreamResult result = new StreamResult(
					new java.io.File("src/server.xml"));
			transformer.transform(source, result);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

 

编辑XML 文档总结:

   1. 更改节点数据

     Node.setNodeValue(elemValue);

 

   2. 添加节点

     String totalString = new Double(total).toString();

          Node totalNode = doc.createTextNode(totalString); 

          //Document 对象创建新的文本节点,该节点带有作为值的 totalString

          Element totalElement = doc.createElement("total");

          // 创建新元素 total

          totalElement.appendChild(totalNode);                        

          // 将节点添加到新的 total 元素。

          thisOrder.insertBefore(totalElement, thisOrder.getFirstChild());

     // 将新元素添加到 Document ,指定新的 Node ,然后指定新 Node 在 Node 之前

        

   3. 除去节点

     Node deadNode = thisOrderItem.getParentNode().removeChild(thisOrderItem);

 

   4. 替换节点

     Element backElement = doc.createElement("backordered");    

          // 创建新元素 backordered

          Node deadNode = thisOrderItem.getParentNode().replaceChild(backElement,thisOrderItem);

        

   5. 创建和设置属性

     Element backElement = doc.createElement("backordered");    

          // 创建新元素 backordered

          backElement.setAttributeNode(doc.createAttribute("itemid"));

          // 创建新属性 itemid

          String itemIdString = thisOrderItem.getAttributeNode("itemid").getNodeValue();

          // 取得 thisOrderItem 的属性 itemid 的值

          backElement.setAttribute("itemid", itemIdString);

          // 设置 backElement 的属性 item 的值 , 可以省略 createAttribute

          Node deadNode = thisOrderItem.getParentNode().replaceChild(backElement,thisOrderItem);

 

   6. 除去属性

     Element thisOrder = (Element)orders.item(orderNum);

          Element customer = (Element)thisOrder.getElementsByTagName("cusomertid").item(0);

          customer.removeAttribute("limit");

          // 去除属性 limit


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值