4.10 XML解析

  在介绍XML概述中提到过,XML文档的应用范围主要有存储数据、系统配置和数据交换。也就是说,作为程序员,需要编写程序读取XML文档中的数据,或将数据写入XML文档。目前最常用的XML解析技术有:DOM和SAX。JDK提供了JAXP来使用DOM和SAX,其中org.w3c.dom是W3C推荐的用于使用DOM解析XML文档的接口,org.xml.sax是使用SAX解析XML文档的接口,javax.xml.parsers是提供允许处理XML文档的类,支持DOM和SAX。本章重点介绍DOM解析,对SAX解析方法仅作简要介绍,不展开讲解。

4.10.1 DOM树

  DOM(Document Object Model)是XML文档的应用程序接口,它定义了对XML文档进行随机访问与操作的方法。DOM是一个与语言无关、与平台无关的标准接口规范。利用DOM,程序开发人员可以动态地创建XML文档,遍历文档结构,添加、修改、删除文档内容,改变文档的显示方式等。可以这样说,文档代表的是数据,而DOM则代表了如何去处理这些数据。

  DOM把一个XML文档映射成一个分层对象模型,而这个层次的结构,是一棵根据XML文档生成的节点树。DOM在对XML文档进行分析之后,不管这个文档有多简单或多复杂,其中的信息都会被转化成一棵对象节点树。在这棵节点树中,有一个根节点,其他所有的节点都是根节点的子节点。节点树生成之后,就可以通过DOM接口访问、修改、添加、删除树中的节点或内容了。

  对DOM树的操作,主要通过以下几个接口。

1.Node接口

  Node接口在整个DOM树中具有举足轻重的地位,DOM接口中有很大一部分接口是从Node接口继承过来的,例如Document(根节点)、Element(元素)、Attr(属性)、Comment(注释)、Text(元素或属性的文本内容)等接口都是从Node继承过来的。在DOM树中,Node接口代表了树中的一个节点。Node接口的常用方法如下。

  • NodeList getChildNodes()

  返回此节点的所有子节点的NodeList。

  • Node getFirstChild()

  返回此节点的第一个子节点。

  • Node getLastChild()

  返回此节点的最后一个子节点。

  • Node getNextSibling()

  返回此节点之后的节点。

  • Node getPreviousSibling()

  返回此节点之前的节点。

  • Document getOwnerDocument()

  返回与此节点相关的Document对象。

  • Node getParentNode(

  返回此节点的父节点。

  • short getNodeType()

  返回此节点的类型。

  • String getNodeName()

  根据此节点类型,返回节点名称。

  • String getNodeValue()

  根据此节点类型,返回节点值。

  前面已经提到,DOM中很多接口都是从Node接口继承的,所以Node接口拥有的方法这些接口都可以使用。但是这些从Node接口继承下来的接口又都各有特性,所以Node接口拥有的方法在各个子接口上的返回值含义不尽相同。例如,对于Element(元素接口)的getNodeType()的返回值为Node.ELEMENT_NODE常量,getNodeName()的返回值为标签名称,getNodeValue()的返回值为null。表4.1列出了nodeName、nodeValue 和 attributes 的值将根据接口类型的不同而不同,这对于XML解析的初学者而言是个难点,请大家务必结合后面的例子,深刻理解。

表4.1 Node子接口属性值

Interface nodeName nodeValue attributes
Attr 与Attr.name相同 与Attr.value相同 null
CDATASection “#cdata-section” 与CharacterData.data相同,CDATA 节的内容 null
Comment “#comment” 与CharacterData.data相同,该注释的内容 null
Document “#document” null null
DocumentFragment “#document-fragment” null null
DocumentType 与DocumentType.name相同 null null
Element 与Element.tagName相同 null NamedNodeMap
Entity entity name null null
EntityReference 引用的实体名称 null null
Notation notation name null null
ProcessingInstruction 与ProcessingInstruction.target相同 与ProcessingInstruction.data相同 null
Text “#text” 与CharacterData.data相同,该文本节点的内容 null
  • String getTextContent()

  返回此节点的文本内容。

  • void setNodeValue(String nodeValue)

  根据此节点类型,设置节点值。

  • void setTextContent(String textContent)

  设置此节点的文本内容。

  • Node appendChild(Node newChild)

  将节点newChild添加到此节点的子节点列表的末尾。

  • Node insertBefore(Node newChild,Node refChild)

  在现有子节点refChild之前插入节点newChild。

  • Node removeChild(Node oldChild)

  从子节点列表中移除oldChild所指示的子节点,并将其返回。

  • Node replaceChild(Node newChild, oldChild)

  将子节点列表中的子节点oldChild替换为newChild,并返回oldChild节点。

2.Document接口

  Document接口表示DOM树中的根节点,即对XML文档进行操作的入口节点。通过Document节点,可以访问到文档中的其他节点。Document接口的常用方法如下:

  • Element getDocumentElement()

  返回代表这个DOM树根节点的Element对象。

  • NodeList getElementsByTagName(String tagname)

  按文档顺序返回包含在文档中且具有给定标记名称的所有Element的NodeList。

3.NodeList接口

  NodeList接口提供了对节点集合的抽象定义,包含了一个或多个节点(Node)的有序集合。NodeList接口的常用方法如下。

  • int getLength()

  返回有序集合中的节点数。

  • Node item(int index)

  返回有序集合中的第index个项。

4.10.2 DOM解析XML

  使用DOM解析XML文档的步骤如下。

  (1)创建解析器工厂,即DocumentBuilderFactory对象。

  (2)通过解析器工厂获得DOM解析器,即DocumentBuilder对象。

  (3)解析指定XML文档,得到DOM节点树。

  (4)对DOM节点树进行操作,完成对XML文档的增删改查。

  下面使用DOM对之前编写的用于存放“租车系统”车辆信息的vehicles.xml文档进行解析,并输出“租车系统”中有几种类型的车,“租车系统”中有几辆卡车,并详细输出每辆卡车的id属性及详细信息,程序运行结果如图4.8所示。

4.10 XML解析 - 图1


图4.8 使用DOM解析vehicles.xml

  具体代码如下(需要大家认真阅读代码中的注释,理解含义):

  1. import java.io.*;
  2. import javax.xml.parsers.*;
  3. import org.w3c.dom.*;
  4. import org.xml.sax.SAXException;
  5. public class TestDOM {
  6. public static void main(String[] args) {
  7. try {
  8. //创建解析器工厂
  9. DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
  10. //通过解析器工厂获得DOM解析器
  11. DocumentBuilder db = dbf.newDocumentBuilder();
  12. //解析指定XML文档,得到DOM节点树
  13. Document doc = db.parse("vehicles.xml");
  14. //得到根节点下的所有子节点
  15. NodeList vehicles = doc.getChildNodes();
  16. System.out.println("“租车系统”中共有" + vehicles.getLength() + "种类型的车!");
  17. //得到所有<truck>节点列表信息
  18. NodeList truckList = doc.getElementsByTagName("truck");
  19. System.out.println("“租车系统”中共有" + truckList.getLength() + "辆卡车!");
  20. //遍历所有卡车
  21. for (int i = 0; i < truckList.getLength(); i++) {
  22. //获取索引为i的卡车
  23. Node truck = truckList.item(i);
  24. //获取卡车属性值并显示
  25. Element element = (Element) truck;
  26. String idValue = element.getAttribute("id");
  27. //以下通过属性值获得属性节点,再通过属性节点getNodeValue()获得属性值
  28. //Node attr = element.getAttributeNode("id");
  29. //String idValue = attr.getNodeValue();
  30. System.out.println("id为" + idValue + "的卡车信息为:");
  31. //获取索引为i的卡车详细信息并输出
  32. for (Node node = truck.getFirstChild(); node != null; node = node.getNextSibling()) {
  33. //根据节点类型进行判断,显示元素节点信息,例如 <oil>20</oil>
  34. if (node.getNodeType() == Node.ELEMENT_NODE){
  35. //元素节点的节点名即为标签名,例如oil
  36. String name = node.getNodeName();
  37. //元素节点<oil>20</oil>下第一个子节点为文本节点20,得到节点值20
  38. String value = node.getFirstChild().getNodeValue();
  39. System.out.println(" " + name + ":" + value + ";");
  40. }
  41. }
  42. }
  43. } catch (ParserConfigurationException e) {
  44. e.printStackTrace();
  45. } catch (SAXException e) {
  46. e.printStackTrace();
  47. } catch (IOException e) {
  48. e.printStackTrace();
  49. }
  50. }
  51. }

  在上面的代码中,用到了根节点、属性节点、元素节点和文本节点,它们的nodeName、nodeValue和attributes 的值含义各不相同,需要注意。

4.10.3 SAX解析XML

  相比于DOM,SAX(Simple API for XML)是一种速度更快、更有效的解析XML文档的方法。它不需要一次性建立一个完整的DOM树,而是读取文档时激活事件进行处理。

  DOM是W3C标准,提供的是标准的解析方式,但其解析效率一直不尽如人意。这是因为DOM解析XML文档时,把所有内容一次性装载入内存,并构建一个驻留在内存中的节点树。如果需要解析的XML文档过大,或者只对该文档中的一部分内容感兴趣,这种做法就会引起性能问题。

  SAX既是一个接口,也是一个软件包。SAX在解析XML时是事件驱动型的,它的工作原理简单地说就是对文档进行顺序扫描,当扫描到文档开始与结束、元素开始与结束等地方时通知事件处理程序,由事件处理程序做相应动作,然后继续同样的扫描,直至文档结束。SAX的缺点也很明显,要用SAX对XML文档进行解析时,就要实现多个事件处理程序用来处理可能触发的事件,对程序员而言操作起来相对复杂。