首页 > 代码库 > XML学习笔记(二):使用 DOM和SAX 解析XML :

XML学习笔记(二):使用 DOM和SAX 解析XML :

一、XML解析的简介

1、一般来说xml解析的方式有两种,Dom与SAX,且各有优缺点。pull是第三种方法见:pull解析XML

1)DOM:dom对象树

①、整个XML放入内存,转换为Document对象;

②、每个元素,转换成Element对象

③、文本,转换成Text对象

④、属性,转换成Attribute对象

优缺点:元素的CRUD比较简单,但是内存消耗大,大的xml文档不适合。


补充:内存不足时,可以修改参数,调整JVM的内存大小

 1、java.lang.OutOfMemoryError: Java heap space 堆内存不足
 2、单个main方法运行时,修改VM Arguments参数为  -Xmx80m  。80为随意修改。

 3、运行程序时,可以修改eclipse的安装目录中 eclipse.ini文件的参数值: -Xmx768m 。


2)SAX:

对于整个XML文档,从上往下读,读取一行,解析一行。

优缺点:不影响内存,但是不适合对文档作CRUD。


2、Java提供两块API与xml解析有关:

1)一为Jaxp:提供了dom与sax解析xml的方法(完全可以被Jdom或是Dom4J代替);

2)二是jaxb:主要是使用注解处理xml与JavaBean的映射,在WebService中被广泛应用。


3、我们在实际开发环境中,对于XML的工作一般可以总结为几类(都会解决):

1)配置文件的解析:自己本地的文件解析;

2)数据交互:与第三方平台如淘宝、天猫、网易等的交互数据的解析;

3)模板数据:如短息或是邮件模板;


4、FreeMarker和Apache有一套不错的api可以用于XML的解析,分别为freemarker.template.Template类和org.apache.velocity.VelocityContext类。


二、Jaxp的Dom和Sax

1、XML文件存放在名叫resources的系统source filder文件夹内:命名为Request.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<REQUEST>
	<appnt>
		<name>SAM-SHO</name>
		<age>28</age>
	</appnt>

	<insured age="24" name="SAMMY" />  <!-- 正确 -->

	<product>
		<!-- 输出不一样 -->
		<name>牙膏</name>
		<price>100</price>
		<disc>刷牙用的</disc>
	</product>

	<Service>
		<RequestHandle>
			<Handle name="log" type="com.xml.base.LogHandle1">
				<init-param>
					<param-name>file</param-name>
					<param-value>d:/log.txt</param-value>
				</init-param>
				<init-param>
					<param-name>filegg4</param-name>
					<param-value>d:/log.txtgg4</param-value>
				</init-param>
			</Handle>
			<Handle name="log2" type="com.xml.base.LogHandle1.2">
				<init-param>
					<param-name>file2</param-name>
					<param-value>d:/log.txt2</param-value>
				</init-param>
			</Handle>
		</RequestHandle>
		<RequestHandle>
			<Handle name="log1" type="com.xml.base.LogHandle1.1">
				<init-param>
					<param-name>file1</param-name>
					<param-value>d:/log.txt1</param-value>
				</init-param>
			</Handle>
		</RequestHandle>
	</Service>

	<Service name="EasyQueryUI" type="com.xml.base.LogHandle2">
		<RequestHandle>
			<Handle name="Sql" type="com.xml.base.LogHandle3">
			</Handle>
		</RequestHandle>
	</Service>

</REQUEST>

2、使用Dom和Sax解析Xml,其其开始的步骤基本都是统一的:

1)都是创建工厂、获取解析器、获取文件、解析文件。

2)然后不同之处就开始了。Dom是直接取转换的对象,而Sax是在自定义的Handler中逐个处理获取Element。

3)解析的代码都比较简单,直接上实例代码。

(一)DOM

1、获取XML文档的元素、属性、文本信息、创建新的元素并写入文档。方法如下:

1)获取工厂、解析器等为固定步骤,获取xml文档的方法有多种,这边使用classloader方式。

2)整个XML文档被解析成一个Document对象。

3)获取元素:document.getElementsByTagName("appnt"), 可以获取名为appnt的元素。这边注意,Node为一切元素的父类。

4)getNodeName():获元素的名称;getTextContent():获取元素的文本值;getAttribute():获取元素的属性值

5)添加元素的用法是固定的,

①、先创建元素,并添加属性与文本内容,方法为createElement("元素名称");setAttribute();setTextContent()

②、规定放入的位置,如放入根节点:root.appendChild("创建的元素"),root为根节点。

③、固定使用Transformer类把创建的新元素,写入文件。

	/**
	 * 通过DOM获取xml文档
	 */
	public void getXMLFromDom() {

		try {

			// 1-: 获得dom解析器工厂(工作的作用是用于创建具体的解析器)
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

			// 2-:获得具体的dom解析器
			DocumentBuilder db = dbf.newDocumentBuilder();

			// 3-: 解析一个xml文档,获得Document对象
			// 利用classloader加载xml文件,文件的位置很重要
			InputStream input = this.getClass().getClassLoader().getResourceAsStream("Request.xml");
			Document document = db.parse(input);

			
			// 4-通过元素名,得到某个元素

			// 4-1 得到<appnt>元素
			NodeList appntElement = document.getElementsByTagName("appnt");
			Node appnt = appntElement.item(0);// 得到第一个
			String name = appnt.getNodeName();// 得到元素的名称
			logger.debug("<appnt>元素的名称 :  " + name);// appnt

			// 4-2 得到<appnt>元素所有子元素
			NodeList appntChildren = appnt.getChildNodes();
			
			// 4-3 得到<appnt>元素下<name>元素
			Node appnt_name = appntChildren.item(1);
			logger.debug("<appnt>元素下<name>子元素的名称 :   " + appnt_name.getNodeName());
			logger.debug("<appnt>元素下<name>子元素的value :   " + appnt_name.getTextContent());

			// 4-4 得到<appnt>元素下<age>元素
			// 中间有text类的内容也会被解析,所以这边1跳转到了3
			// item(2)的话,会输出#Text,是指在<appnt>#Text<age>中间的文本信息
			Node appnt_age = appntChildren.item(3);
			logger.debug("<appnt>元素下<age>子元素的名称 :   " + appnt_age.getNodeName());
			logger.debug("<appnt>元素下<age>子元素的value :   " + appnt_age.getTextContent());
			
			
			// 5-获取属性值
			Element insuredElement = (Element) document.getElementsByTagName("insured").item(0);
			String insuredName = insuredElement.getAttribute("name");
			String insuredAge = insuredElement.getAttribute("age");
			
			logger.debug("<insured>元素的name属性为: " +  insuredName +" |age属性为"  + insuredAge);
			
			
			// 6-添加节点元素
			
			// 6-1 新建元素
			Element element = document.createElement("新建标签");
			element.setAttribute("属性", "10块");
			element.setTextContent("我是动态新建的元素");
			
			// 6-2 放入根节点元素
			Element root = document.getDocumentElement();//获取根节点
			root.appendChild(element);//新建的元素放入根节点元素
			
			// 6-3 写入文件,需要使用 TransformerFactory 固定写法
			TransformerFactory tff = TransformerFactory.newInstance();
			Transformer transformer = tff.newTransformer();
			
			// 写出去
			DOMSource domSource = new DOMSource(document);			
			Result result = new StreamResult(new FileOutputStream("resources/Request.xml"));
			transformer.transform(domSource, result);

		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (TransformerConfigurationException e) {
			e.printStackTrace();
		} catch (TransformerException e) {
			e.printStackTrace();
		}

	}


2、使用DOM解析整个XML文档,会使用递归方法。代码如下:

1)获取解析器等步骤都是一致的,这边使用文件流获取XML文件。

2)获取根节点后,使用递归获取根节点下的所有元素。方法为listChildrenNodes("根元素")

3)由于DOM解析的时候,会把空的文本信息Text也解析,我们可以预判断,处理掉。getChildNodes()可以得到子元素。

	/**
	 * Dom解析整个XML文档
	 * 
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 */
	public void domXML() throws ParserConfigurationException, SAXException, IOException {
		// 1-: 获得dom解析器工厂(工作的作用是用于创建具体的解析器)
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

		// 2-:获得具体的dom解析器
		DocumentBuilder db = dbf.newDocumentBuilder();

		// 3-: 解析一个xml文档,获得Document对象InputStream
		Document document = db.parse(new File("resources/Request.xml"));

		// 4-得到根节点
		Element requestElement = document.getDocumentElement();
		logger.debug("得到根节点:  " + requestElement.getNodeName());

		// 需要使用递归
		listChildrenNodes(requestElement);
	}

	/**
	 * 递归方法 打印的时候会有每个Text对象,会处理掉
	 * 
	 * @param root
	 */
	private void listChildrenNodes(Node root) {

		// 先判断一步, 把Text对象过滤掉
		if (root instanceof Element) {
			logger.debug("节点的名字为:    " + root.getNodeName());
		}
		
		NodeList childrenLists = root.getChildNodes();
		for (int i = 0; i < childrenLists.getLength(); i++) {
			Node child = childrenLists.item(i);
			// 递归调用
			listChildrenNodes(child);
		}

	}

3、以上两个方法分别简单处理了jaxp的Dom解析XML的实例,在main方法中调用即可运行,全部代码为:

/**
 * 一、XML的解析方式有2中方式:(现在还有poi解析)
 * 1、DOM解析:dom对象树
 *   1)整个XML放入内存,转换为Document对象;
 *   2)每个元素,转换成Element对象
 *   3)文本,转换成Text对象
 *   4)属性,转换成Attribute对象
 *  
 *  优缺点:元素的CRUD比较简单,但是内存消耗大,大的xml文档不适合。
 *  
 * 
 * 2、SAX解析:从上往下读,读取一行,解析一行
 * 
 *  优缺点:不影响内存,但是不适合对文档作CRUD。
 * 
 * 二、XML解析开发包
 * 1、jaxp
 * 2、jDom
 * 3、Dom4j
 * 
 * 
 * 三、调整JVM的内存大小
 * 1、java.lang.OutOfMemoryError: Java heap space 堆内存不足
 * 2、 固定为64M
 * 3、  -Xmx80m 修改VM Arguments
 * 
 */

package xml.code.base;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * 
 * DomJaxpXML.java
 * 
 * @title Dom解析XML
 * @description
 * @author SAM-SHO
 * @Date 2014-10-12
 */
public class DomJaxpXML {

	private static Logger logger = Logger.getLogger(DomJaxpXML.class);

	public static void main(String[] args) throws Exception {

		// ************************调整JVM 内存大小
		// java.lang.OutOfMemoryError: Java heap space 堆内存不足
		// 固定为64M
		// -Xmx80m 修改VM Arguments
		// byte[] b = new byte[1024 * 1024 * 70];

		DomJaxpXML JavapXML = new DomJaxpXML();

		// 通过Dom获取XML
		JavapXML.getXMLFromDom();
		
		JavapXML.domXML();
	}

	/**
	 * 通过DOM获取xml文档
	 */
	public void getXMLFromDom() {

		try {

			// 1-: 获得dom解析器工厂(工作的作用是用于创建具体的解析器)
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

			// 2-:获得具体的dom解析器
			DocumentBuilder db = dbf.newDocumentBuilder();

			// 3-: 解析一个xml文档,获得Document对象
			// 利用classloader加载xml文件,文件的位置很重要
			InputStream input = this.getClass().getClassLoader().getResourceAsStream("Request.xml");
			Document document = db.parse(input);

			
			// 4-通过元素名,得到某个元素

			// 4-1 得到<appnt>元素
			NodeList appntElement = document.getElementsByTagName("appnt");
			Node appnt = appntElement.item(0);// 得到第一个
			String name = appnt.getNodeName();// 得到元素的名称
			logger.debug("<appnt>元素的名称 :  " + name);// appnt

			// 4-2 得到<appnt>元素所有子元素
			NodeList appntChildren = appnt.getChildNodes();
			
			// 4-3 得到<appnt>元素下<name>元素
			Node appnt_name = appntChildren.item(1);
			logger.debug("<appnt>元素下<name>子元素的名称 :   " + appnt_name.getNodeName());
			logger.debug("<appnt>元素下<name>子元素的value :   " + appnt_name.getTextContent());

			// 4-4 得到<appnt>元素下<age>元素
			// 中间有text类的内容也会被解析,所以这边1跳转到了3
			// item(2)的话,会输出#Text,是指在<appnt>#Text<age>中间的文本信息
			Node appnt_age = appntChildren.item(3);
			logger.debug("<appnt>元素下<age>子元素的名称 :   " + appnt_age.getNodeName());
			logger.debug("<appnt>元素下<age>子元素的value :   " + appnt_age.getTextContent());
			
			
			// 5-获取属性值
			Element insuredElement = (Element) document.getElementsByTagName("insured").item(0);
			String insuredName = insuredElement.getAttribute("name");
			String insuredAge = insuredElement.getAttribute("age");
			
			logger.debug("<insured>元素的name属性为: " +  insuredName +" |age属性为"  + insuredAge);
			
			
			// 6-添加节点元素
			
			// 6-1 新建元素
			Element element = document.createElement("新建标签");
			element.setAttribute("属性", "10块");
			element.setTextContent("我是动态新建的元素");
			
			// 6-2 放入根节点元素
			Element root = document.getDocumentElement();//获取根节点
			root.appendChild(element);//新建的元素放入根节点元素
			
			// 6-3 写入文件,需要使用 TransformerFactory 固定写法
			TransformerFactory tff = TransformerFactory.newInstance();
			Transformer transformer = tff.newTransformer();
			
			// 写出去
			DOMSource domSource = new DOMSource(document);			
			Result result = new StreamResult(new FileOutputStream("resources/Request.xml"));
			transformer.transform(domSource, result);

		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (TransformerConfigurationException e) {
			e.printStackTrace();
		} catch (TransformerException e) {
			e.printStackTrace();
		}

	}

	/**
	 * Dom解析整个XML文档
	 * 
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 */
	public void domXML() throws ParserConfigurationException, SAXException, IOException {
		// 1-: 获得dom解析器工厂(工作的作用是用于创建具体的解析器)
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

		// 2-:获得具体的dom解析器
		DocumentBuilder db = dbf.newDocumentBuilder();

		// 3-: 解析一个xml文档,获得Document对象InputStream
		Document document = db.parse(new File("resources/Request.xml"));

		// 4-得到根节点
		Element requestElement = document.getDocumentElement();
		logger.debug("得到根节点:  " + requestElement.getNodeName());

		// 需要使用递归
		listChildrenNodes(requestElement);
	}

	/**
	 * 递归方法 打印的时候会有每个Text对象,会处理掉
	 * 
	 * @param root
	 */
	private void listChildrenNodes(Node root) {

		// 先判断一步, 把Text对象过滤掉
		if (root instanceof Element) {
			logger.debug("节点的名字为:    " + root.getNodeName());
		}
		
		NodeList childrenLists = root.getChildNodes();
		for (int i = 0; i < childrenLists.getLength(); i++) {
			Node child = childrenLists.item(i);
			// 递归调用
			listChildrenNodes(child);
		}

	}
	
}

(二)SAX

1、 SAX采用事件处理的方式解析XML文件,涉及两个部分:解析器和事件处理器:

1)、解析器可以使用jaxp的API创建,创建出sax解析器后,就可以指定解析器去解析某个XML文档。

2)、解析器采用SAX方式在解析某个文档时,它只要解析到XML文档的一个组成部分都会去调用事件处理器的一个方法,解析器在调用事件处理器的方法时,会把当前解析到的XML内容作为方法参数传递给事件处理器。

3)、 事件处理器是由程序员编写的,程序员通过事件处理器中方法的参数,就可以很轻松地得到SAX解析器解析到的数据,从而可以决定如何对数据进行处理。

2、简单的理解:获取工厂、解析器、xml文档的步骤是统一的,我们只需要实现一个事件处理器,而且自定义的事件处理器是要继承DefaultHandler即可,不然会要实现接口的很多方法。


3、通过SAX解析整个XML文档:

1)这边我们没有的事件处理器没有继承DefaultHandler,采用的是实现ContentHandler,会实现很多,非常麻烦,不推荐。

2)获取工厂、得到解析器、得到读取器、设置内容处理器、解析xml文档的步骤是固定的,主要在于自己写的事件处理器。

3)这边设置读取器、内容处理器、解析XML是分开执行的,也可以一步到位,解析器类SAXParser有这样的封装。

4)事件处理器中需要用到的是三个方法:

①、startElement():获取元素的开始便签和属性。

②、characters():获取元素的文本内容。

③、endElement():获取元素的结束标签。

代码如下:

	/**
	 * 通过SAX解析整个XML
	 */
	private void getSaxXML() {
		try {
			// 1-: 获得SAX解析器工厂(工作的作用是用于创建具体的解析器)
			SAXParserFactory spf = SAXParserFactory.newInstance();

			// 2-:获得具体的SAX解析器
			SAXParser tSAXParser = spf.newSAXParser();

			// 3-:得到读取器
			XMLReader xmlReader = tSAXParser.getXMLReader();

			// 4-设置内容处理器
			xmlReader.setContentHandler(new ListHandler());

			// 5-: 解析一个xml文档
			InputStream input = this.getClass().getClassLoader().getResourceAsStream("Request.xml");// 利用classloader加载xml文件
			xmlReader.parse(new InputSource(input));

		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

/**
 * 
 * SaxJaxpXML.java
 * 
 * @title 得到xml文档所有内容的Handler,继承自ContentHandler
 * @description
 * @author SAM-SHO
 * @Date 2014-10-20
 */
class ListHandler implements ContentHandler {

	private Logger logger = Logger.getLogger(ListHandler.class);

	@Override
	public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
		// 开始标签
		logger.debug("<" + qName + ">");

		// 获取属性值
		for (int i = 0; atts != null && i < atts.getLength(); i++) {
			String attrName = atts.getQName(i);
			String attrValue = http://www.mamicode.com/atts.getValue(attrName);>
4、通过SAX获取指定标签的内容

1)我们利用SAXParser封装的方法,一步到位组装读取器、设置内容处理器、解析一个xml文档。

2)事件处理器直接继承DefaultHandler,然后重写需要的三个方法。

3)一个XML文件中相同名字的的元素可能会有多个,可以动态指定获取。

代码如下:

	/**
	 * 获取指定标签
	 */
	private void getTagXML() {
		try {
			// 1-: 获得SAX解析器工厂(工作的作用是用于创建具体的解析器)
			SAXParserFactory spf = SAXParserFactory.newInstance();

			// 2-:获得具体的SAX解析器
			SAXParser tSAXParser = spf.newSAXParser();

			// 3-: 解析一个xml文档
			InputStream input = this.getClass().getClassLoader().getResourceAsStream("Request.xml");// 利用classloader加载xml文件
			tSAXParser.parse(new InputSource(input), new TagValueHandler());// 直接一步到位

		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

/**
 * 
 * SaxJaxpXML.java
 * 
 * @title 获取指定标签的值,继承DefaultHandler
 * @description
 * @author SAM-SHO
 * @Date 2014-10-20
 */
class TagValueHandler extends DefaultHandler {
	private Logger logger = Logger.getLogger(TagValueHandler.class);

	private String currentTag; // 记住当前解析到的是什么标签
	private int needNumber = 2; // 记住想获取第几个作者便签的值,这边先定义为2
	private int currentNumber = 0; // 当前解析到的是第几个

	@Override
	public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
		currentTag = qName;
		if (currentTag.equals("name")) {
			currentNumber++;
		}
	}

	@Override
	public void characters(char[] ch, int start, int length) throws SAXException {
		// 当前解析到的便签为第二个<name>元素就输出
		if ("name".equals(currentTag) && currentNumber == needNumber) {
			String content = new String(ch, start, length);
			logger.debug(content);
		}
	}

	@Override
	public void endElement(String uri, String localName, String qName) throws SAXException {
		currentTag = null;// 置空

	}

}



三、JDom和Dom4j

(一)JDom

1、创建XML文件:

1)Document类即代表整个XML文档,把生成的 Document 利用 XMLOutputter 类输出即可。

2)映射关系:元素:Element;属性:Attribute;注解:Comment;文本信息:Text;

3)注意:addContent()是追加,setContent()会覆盖。

	/**
	 * 创建XML
	 * 
	 * @throws IOException
	 */
	private void createXML() {
		ArrayList<Element> eleList = new ArrayList<Element>();
		Document doc = null;

		try {
			// 新建一个Document
			doc = new Document();
			// 新建一个根元素 <root>
			Element root = new Element("root");
			// 给根元素添加文本内容
			root.setText("this is a root el");
			// 修改根元素 的名字
			root.setName("rootElement");
			// 给根元素添加属性
			root.setAttribute("name", "属性");

			// 新建一个元素<APPNT>
			Element appntEle = new Element("APPNT");
			// 定义一个属性对象
			Attribute appntAttr = new Attribute("name", "SAM-SHO");
			// 给<APPNT>元素添加属性
			appntEle.setAttribute(appntAttr);
			appntEle.setAttribute("age", "28");
			// 给<APPNT>元素添加文本内容
			appntEle.setText("我是投保人");
			// 给<APPNT>元素添加注解
			Comment comment = new Comment("我是注解");
			appntEle.addContent(comment);

			// 新建一个元素<INSURED>
			Element insuredEle = new Element("INSURED");
			// 定义一个属性对象
			Attribute insuredAttr = new Attribute("name", "SAMMY");
			// 给<INSURED>元素添加属性
			insuredEle.setAttribute(insuredAttr);
			insuredEle.setAttribute("age", "49");
			// 给<INSURED>元素添加文本内容

			Text text = new Text("我是被保人,可以这样加文本");
			// insuredEle.setText("我是被保人");
			insuredEle.addContent(text);
			insuredEle.addContent("   ||我是追加");

			// addContent()是追加,setContent()会覆盖
			// insuredEle.setContent(new Text("....我会覆盖"));

			// 把新建的元素放入集合
			eleList.add(appntEle);
			eleList.add(insuredEle);

			// 把新建的元素添加到 根元素 <root>
			root.setContent(eleList);
			// 也可以使用addContent()一个一个加
			// root.addContent(appntEle);
			// root.addContent(insuredEle);

			// 把根元素添加到 Document
			doc.setRootElement(root);
			// doc = new
			// Document(root);//可以直接这样新建xml文档,同doc.setRootElement(root)

			String xmlStr = out.outputString(doc);
			System.out.println(xmlStr);// 打印到控制台

			out.output(doc, System.out);// 输出到控制台
			out.output(doc, new FileOutputStream("resources/root.xml"));// 输出到文件

		} catch (IOException e) {
			e.printStackTrace();
		}
	}

2、解析整个XML文档:

1)只要有一点Dom解析的基础,JDom的使用就会变得比较简单。

2)得到解析器、解析XML文件的步骤都是固定的。

3)我们获取到元素与属性后,可以直接输出,也可以set到预先定义好的JavaBean中。

	/**
	 * 解析XML
	 * 
	 * @throws IOException
	 */
	private void getXML() {

		try {

			// 1-获取解析器
			SAXBuilder dBuilder = new SAXBuilder();

			// 2-获取xml文件的流
			InputStream is = JDomXML.class.getClassLoader().getResourceAsStream("Request.xml");

			// 3- 解析XML文件
			Document doc = dBuilder.build(is);

			// 4-获取元素
			
			// 4-1 获取根元素<request>
			Element root = doc.getRootElement();// 拿到request

			// 4-2获取子元素<appnt>
			Element appntEle = root.getChild("appnt");

			// 4-2-1 获取<appnt>的<name>元素
			Element appntName = appntEle.getChild("name");
			logger.debug("<name>元素名称: " + appntName.getName());
			logger.debug("<name>元素文本信息: " + appntName.getText());

			// 4-2-2 获取<appnt>的<age>元素
			Element ageName = appntEle.getChild("age");
			logger.debug("ageName: " + ageName.getName());
			logger.debug("ageText: " + ageName.getText());

			// 4-3 获取子元素<insured>
			Element insuredEle = root.getChild("insured");
			
			// 4-3-1 获取<insured>的name属性
			Attribute nameAttr = insuredEle.getAttribute("name");
			logger.debug("nameAttr: " + nameAttr.getValue());

			// 4-3-2 获取<insured>的age属性
			Attribute ageAttr = insuredEle.getAttribute("age");
			logger.debug("ageAttr: " + ageAttr.getValue());
			logger.debug("直接拿ageAttr: " + insuredEle.getAttributeValue("age"));

			// 4-4 获取子元素<product>
			Element productEle = root.getChild("product");

			// 4-4 获取子元素<Service>
			List<Element> serviceLists = root.getChildren("Service");// 拿到<Service>元素
			for (Element servlce : serviceLists) {

				// 获取<Service>的两个属性: name和type
				logger.debug(servlce.getAttributeValue("name"));
				logger.debug(servlce.getAttributeValue("type"));

				// 获取<Service>的子元素 <RequestHandle>,可以有多个
				List<Element> requestLists = servlce.getChildren("RequestHandle");

				for (Element Request : requestLists) {

					// 获取 <RequestHandle>的子元素 <Handle>,可以多个
					List<Element> handleLists = Request.getChildren("Handle");

					for (Element handle : handleLists) {

						// 获取<Handle>的属性 : name 和 type
						logger.debug(handle.getAttributeValue("name"));
						logger.debug(handle.getAttributeValue("type"));

						// 获取<Handle> 的子元素<init-param> 可以有多个
						List<Element> paramLists = handle.getChildren("init-param");
						for (Element init_param : paramLists) {

							// 获取<init-param>的两个子元素 <param-name>和<param-value>
							Element param_name = init_param.getChild("param-name");
							logger.debug("param-name: " + param_name.getTextTrim());

							Element param_value = http://www.mamicode.com/init_param.getChild("param-value");>

(二)Dom4j

1、简介:

1)Dom4j是以上各api中效率最好的,javm内部使用的也是Dom4j。

2)Dom4j的文档写的非常好,介绍的很详细。它的文档位于下载包下:如dom4j-1.6.1\docs\guide.html 。

3)使用Dom4j解析XML文档,可以使用强大XPath语法。语法地址:http://www.zvon.org/xxl/XPathTutorial/General_chi/examples.html


2、创建XML文档以及对其CRUD

1)Dom4j创建Document和Element都需要用到 DocumentHelper 类。如

// 创建一篇文档
Document doc = DocumentHelper.createDocument();
// 创建元素			
Element ele = DocumentHelper.createElement("插入新元素");

2)Dom4j的语法设计,使得链式编程得到很大的便利。如添加一个新元素:

			// 3-在<insured>下添加新元素,注意,addText\addAttribute都是操作在元素<DESC>上
			document.getRootElement().element("insured").addElement("DESC").addText("新加的元素").addAttribute("属性", "值");

3)Dom4j的解析语法有一个很大的不足,就是作CRUD的时候必须一层一层的从根元素开始获取。(可以使用XPath语法解决)。如下面添加、删除、更新元素时,必须从根节点开始获取,然后操作:

			// 3-2 指定位置上添加新元素:如<product>元素下子元素 <price>下添加<插入新元素>
			// 这个插入很恶心,一点都不爽。
			Element product = document.getRootElement().element("product");
			List<Element> list = product.elements();
			list.add(2, DocumentHelper.createElement("插入新元素").addText("这个插入很恶心"));

			// 3-3 删除指定节点,如删除<appnt>的<age>子元素
			 Element ageEle = document.getRootElement().element("appnt").element("age");//得到<age>元素
			 // ageEle.remove(ageEle);//是否可以删除自己?? 答案是不行
			 ageEle.getParent().remove(ageEle);//只能利用父亲元素的remove()方法

			// 3-4 更新 修改<appnt>的<name>子元素
			document.getRootElement().element("appnt").element("name").addAttribute("修改属性", "修改的属性值").setText("修改文本域:SAM");

4)生成文档的输出:

①、尽量配合格式化 OutputFormat 类一起使用,这样可以避免编码错误的发生。

②、使用字符流转码,如:new OutputStreamWriter(new FileOutputStream("resources/Request.xml"), "utf-8")

③、直接使用字节流配合 OutputFormat 格式化一起使用。

// 4-输出到文档
			// 注意:这边有可能会有编码问题:其实用于读取写入sting化的xml文件(称为报文)时,都存在转码问题。
			// 以前有个项目,和天猫对接,天猫的报文编码为 GBK 的,我们的系统统一UTF-8.这样很明显需要转码。
			// 先用 GBK 读取后需要转成 UTF-8 才能使用。
			// 问题分析:
			// 1) 这边控制编码的是IO流的 FileWriter 。
			// 2) FileWriter默认会使用本地平台的码表,如你的平台编码为GB2312。而你的xml文档的编码为utf-8,这样写入中文会乱码,即写入的为GB2312 。
			// 3) XMLWriter这边只接受 Writer ,我们可以使用 OutputStreamWriter 指定写入的编码为utf-8即可。
			OutputFormat format = OutputFormat.createPrettyPrint();// 格式化编辑器
			XMLWriter writer = new XMLWriter(new FileWriter("resources/Request.xml"), format);
			// XMLWriter writer = new XMLWriter(new OutputStreamWriter(new FileOutputStream("resources/Request.xml"), "utf-8"));
			writer.write(document);
			writer.close();

			// 编码扩展:修改xml的编码为 GBK 。
			// document这边默认会转成utf-8,写出的时候需要指定格式化输出器的编码为 GBK,
			// 然后利用字节流 FileOutputStream 直接输出
			// 或者利用字节流转成字符流 OutputStreamWriter,但是需要指定转的编码
			// OutputFormat format = OutputFormat.createPrettyPrint();//格式化编辑器
			// format.setEncoding("GBK");
			// 字符流
			// XMLWriter writer = new XMLWriter(new OutputStreamWriter(new FileOutputStream("resources/Request.xml"), "GBK"),format);
			// 自节流
			// XMLWriter writer = new XMLWriter(new FileOutputStream("resources/Request.xml"), format);
			// writer.write(document);
			// writer.close();

			// 漂亮的格式输出到控制台
			OutputFormat format2 = OutputFormat.createPrettyPrint();
			// format2.setEncoding("GBK");
			writer = new XMLWriter(System.out, format2);
			writer.write(document);


5)其实Dom4j的语法,在文档中非常详细,这边就不多说了。注意输出编码问题即可。详细代码如下:

	/**
	 * 创建XML文档以及CRUD
	 * 
	 * @throws DocumentException
	 * @throws IOException
	 */
	public void createDocument() {

		/******* 在原有文档上添加 **********/

		try {
			// 1- 获取解析器
			SAXReader reader = new SAXReader();

			// 2-读取文档
			Document document = reader.read(new File("resources/Request.xml"));
			// Document document = reader.read(new InputStreamReader(new FileInputStream("resources/Request.xml"),"GBK"));

			// 3-在<insured>下添加新元素,注意,addText\addAttribute都是操作在元素<DESC>上
			document.getRootElement().element("insured").addElement("DESC").addText("新加的元素").addAttribute("属性", "值");

			// 3-2 指定位置上添加新元素:如<product>元素下子元素 <price>下添加<插入新元素>
			// 这个插入很恶心,一点都不爽。
			Element product = document.getRootElement().element("product");
			List<Element> list = product.elements();
			list.add(2, DocumentHelper.createElement("插入新元素").addText("这个插入很恶心"));

			// 3-3 删除指定节点,如删除<appnt>的<age>子元素
			 Element ageEle = document.getRootElement().element("appnt").element("age");//得到<age>元素
			 // ageEle.remove(ageEle);//是否一删除自己?? 答案是不行
			 ageEle.getParent().remove(ageEle);//只能利用父亲元素的remove()方法

			// 3-4 更新 修改<appnt>的<name>子元素
			document.getRootElement().element("appnt").element("name").addAttribute("修改属性", "修改的属性值").setText("修改文本域:SAM");

			// 4-输出到文档
			// 注意:这边有可能会有编码问题:其实用于读取写入sting化的xml文件(称为报文)时,都存在转码问题。
			// 以前有个项目,和天猫对接,天猫的报文编码为 GBK 的,我们的系统统一UTF-8.这样很明显需要转码。
			// 先用 GBK 读取后需要转成 UTF-8 才能使用。
			// 问题分析:
			// 1) 这边控制编码的是IO流的 FileWriter 。
			// 2) FileWriter默认会使用本地平台的码表,如你的平台编码为GB2312。而你的xml文档的编码为utf-8,这样写入中文会乱码,即写入的为GB2312 。
			// 3) XMLWriter这边只接受 Writer ,我们可以使用 OutputStreamWriter 指定写入的编码为utf-8即可。
			OutputFormat format = OutputFormat.createPrettyPrint();// 格式化编辑器
			XMLWriter writer = new XMLWriter(new FileWriter("resources/Request.xml"), format);
			// XMLWriter writer = new XMLWriter(new OutputStreamWriter(new FileOutputStream("resources/Request.xml"), "utf-8"));
			writer.write(document);
			writer.close();

			// 编码扩展:修改xml的编码为 GBK 。
			// document这边默认会转成utf-8,写出的时候需要指定格式化输出器的编码为 GBK,
			// 然后利用字节流 FileOutputStream 直接输出
			// 或者利用字节流转成字符流 OutputStreamWriter,但是需要指定转的编码
			// OutputFormat format = OutputFormat.createPrettyPrint();//格式化编辑器
			// format.setEncoding("GBK");
			// 字符流
			// XMLWriter writer = new XMLWriter(new OutputStreamWriter(new FileOutputStream("resources/Request.xml"), "GBK"),format);
			// 自节流
			// XMLWriter writer = new XMLWriter(new FileOutputStream("resources/Request.xml"), format);
			// writer.write(document);
			// writer.close();

			// 漂亮的格式输出到控制台
			OutputFormat format2 = OutputFormat.createPrettyPrint();
			// format2.setEncoding("GBK");
			writer = new XMLWriter(System.out, format2);
			writer.write(document);

			/******************** 创建新文档 ********************/

			// 创建一篇文档
			Document doc = DocumentHelper.createDocument();

			// 添加一个元素
			Element root = doc.addElement("catalog");
			// 为root元素添加注释
			root.addComment("An XML Catalog");
			// 添加标记
			root.addProcessingInstruction("target", "instruction");

			// 创建元素
			Element journalEl = new BaseElement("journal");
			// 添加属性
			journalEl.addAttribute("title", "XML Zone");
			journalEl.addAttribute("publisher", "IBM developerWorks");
			root.add(journalEl);

			// 添加元素
			Element articleEl = journalEl.addElement("article");
			articleEl.addAttribute("level", "Intermediate");
			articleEl.addAttribute("date", "December-2001");

			Element titleEl = articleEl.addElement("title");
			// 设置文本内容
			titleEl.setText("Java configuration with XML Schema");
			// titleEl.addText("Java configuration with XML Schema");

			Element authorEl = articleEl.addElement("author");
			authorEl.addElement("firstname").setText("Marcello");
			authorEl.addElement("lastname").addText("Vitaletti");

			// 可以使用 addDocType() 方法添加文档类型说明。
			doc.addDocType("catalog", null, "file://c:/Dtds/catalog.dtd");

			// 将xml转换成文本
			doc.asXML();

			OutputFormat format3 = OutputFormat.createPrettyPrint();
			writer = new XMLWriter(System.out, format3);
			writer.write(doc);
		} catch (DocumentException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

3、使用Dom4j的语法解析XML文档。一般来说,比较麻烦,使用xpath更加简便。


	/**
	 * 使用Dom4j解析xml文档
	 */
	public void readXml() {
		try {

			// 1- 获取解析器
			SAXReader reader = new SAXReader();

			// 2-读取文档
			InputStream in = this.getClass().getClassLoader().getResourceAsStream("Request.xml");
			Document document = reader.read(in);

			// 3-读取根节点
			Element root = document.getRootElement();

			QName qName = root.getQName();
			String name = qName.getName();
			logger.debug("根节点的名称: " + name);

			name = root.getName();
			logger.debug("根节点的名称2 : " + name);

			// 4-获取子节点
			Element appneEle = root.element("appnt");// 只有一个
			String appntName = appneEle.element("name").getTextTrim();
			String appntAge = appneEle.element("age").getTextTrim();
			logger.debug("appnt节点的name为 : " + appntName + " |age为 " + appntAge);

			// 5-获取有多个子节点
			Element serviceEle = (Element) root.elements("Service").get(1);

			// 5-1 获取属性值
			String attrName = serviceEle.attribute("name").getName();
			String attrNameValue = http://www.mamicode.com/serviceEle.attribute("name").getValue();>
	/**
	 * 递归方法
	 * 
	 * @param element
	 */
	public void treeWalk(Element element) {
		System.out.println(element.getName());

		for (int i = 0, size = element.nodeCount(); i < size; i++) {
			Node node = element.node(i);
			if (node instanceof Element) {

				treeWalk((Element) node);
			} else {
				// do something....
			}
		}
	}

4、XML与String的相互转换:

1)XML转String:Document.asXML()方法即可。

2)String转XML:需要使用DocumentHelper.parseText("")方法。

	/**
	 * XML与String的相互转换
	 */
	public void ConverXmlAndString() {
		try {
			Document document = DocumentHelper.createDocument();
			String text = document.asXML();

			String text2 = "<person> <name>James</name> </person>";
			Document document2 = DocumentHelper.parseText(text2);

		} catch (DocumentException e) {
			e.printStackTrace();
		}
	}


5、使用XPath解析XML文档。

1)必须添加 jaxen.jar 包,在下载Dom4j包中就有,添加即可。

2)XPath的语法规范有中文版本,具体地址为:http://www.zvon.org/xxl/XPathTutorial/General_chi/examples.html

3)Dom4j中与XPath有关的主要方法为

//得到单个元素,如果为多个,默认取第一个
document.selectSingleNode("");

//得到符合条件的元素集合
document.selectNodes("");


4)XPath语法最主要的为:"/":绝对路径,需要层次分明;"//":相对路径,获取多个值;"@"进行条件筛选。

5)具体语法代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<rootElement name="属性">
	<user id="a1" name="33">用户1</user>
	<user id="a2" name="33">用户2</user>
	
	<APPNT id="1" name="SAM-SHO" age="28">我是投保人<!--我是注解--></APPNT>
	<INSURED id="2" name="SAMMY" age="49"><user id="a3">用户3</user></INSURED>
</rootElement>

	/**
	 * 利用强大的XPath 语法:http://www.zvon.org/xxl/XPathTutorial/General/examples.html
	 * 需要导入jaxen.jar包
	 */
	public void xPathTest() {
		try {
			// 1- 获取解析器
			SAXReader reader = new SAXReader();

			// 2-读取文档
			Document document = reader.read(new File("resources/xpath.xml"));

			// 3-利用XPath
			// 3-1 以斜线 / 开始, 那么该路径就表示到一个元素的绝对路径
			Element rootEle = (Element) document.selectSingleNode("/rootElement");// 得到根元素<rootElement>元素
			System.out.println(rootEle.attributeValue("name"));// 属性

			// 3-2 得到<APPNT>元素: "/"绝对路径,层次必须清楚
			Node appntNode = document.selectSingleNode("/rootElement/APPNT");
			System.out.println(appntNode.getText());// 我是投保人

			// 3-3 双斜线 // 开头, 则表示选择文档中所有满足双斜线//之后规则的元素,相对路径
			// 得到所有的user元素,不论层级,"用户3"也会在范围内
			List<Node> list = document.selectNodes("//user");
			for (Iterator<Node> iterator = list.iterator(); iterator.hasNext();) {
				Node node = (Node) iterator.next();
				System.out.println(node.getText());
			}

			// 3-4 删选元素:"@",如id='a1'的<user>元素,需要有单引号''
			Node userNode = document.selectSingleNode("//user[@id='a1']");
			System.out.println(userNode.getText());// 用户1

			// @ 中可以使用 and 和or等逻辑:如 获取id='a1'或者id='a3'的<user>元素
			List<Node> listNodes = document.selectNodes("//user[@id='a1' or @id='a3']");
			for (Iterator<Node> iterator = listNodes.iterator(); iterator.hasNext();) {
				Node node = (Node) iterator.next();
				System.out.println("获取id='a1'或者id='a3'的<user>元素 : "+node.getText());
			}

			// @:获取id='a1'and name='33'的<user>元素
			userNode = document.selectSingleNode("//user[@id='a1' and @name='33']");
			System.out.println("获取id='a1'and name='33'的<user>元素: " + userNode.getText());

		} catch (DocumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

四、小结:

1、jaxp的api使用还是比较麻烦的,功能和效率都不是很好,直接可以被JDom和Dom4J代替。意见是如果不是万不得已,还是不要使用。

2、Dom4j 比 JDom 的使用更加简便,且效率更高。

2、其实Java 内部的 jaxm 用的就是 Dom4j,所以一般我们使用Dom4j即可。而在解析xml时推荐使用更加强大简便的XPath语法。


XML学习笔记(二):使用 DOM和SAX 解析XML :