XML解析之使用Jaxp对XML进行DOM解析
1.XML的解析方式对比:
我们写好XML文档后,需要对XML文档进行CRUD,这就涉及如何对XML文档进行解析.
•dom:(Document Object Model, 即文档对象模型) 是 W3C 组织推荐的处理 XML 的一种方式。
•sax: (Simple API for XML) 不是官方标准,但它是 XML 社区事实上的标准,几乎所有的 XML 解析器都支持它。
DOM解析 SAX解析 优点 DOM将文档中的元素封装成对象 ,在内存中以树的结构存放,这样节点与节点之间有关系, 便于CRUD(create read update delete) 读一行解析一行,内存消耗相对较小,适合处理大型的XML文档 缺点 如果文档比较大(>1GB)那么再把文档中的元素封装成DOM对象可能导致内存溢出 不能CUD,如果想考虑DOM XML解析器
•Crimson、Xerces 、Aelfred2
XML解析开发包
•Jaxp、Jdom、dom4j
2.Jaxp对XML进行DOM解析:
JAXP 开发包是J2SE的一部分,它由javax.xml、org.w3c.dom 、org.xml.sax 包及其子包组成
在 javax.xml.parsers 包中,定义了几个工厂类,程序员调用这些工厂类,可以得到对xml文档进行解析的 DOM 或 SAX 的解析器对象。
例如:book.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE 书架 SYSTEM "book.dtd"><书架><书><书名>JavaWeb</书名><作者 姓名="王五"/><售价>50元</售价><页面作者 个人爱好="上网" 网站职务="页面作者" 联系信息=""/></书></书架><!--
1. #REQUIRED :网站职务="设计",页面作者元素中一旦写了网站职务属性,那么该属性的值只能写 "页面作者"
否则报错: Attribute "网站职务" with value "设计" must have a value of "页面作者"
2.对于属性个人爱好的属性值,你写了,其属性值可以任意更改,但是不写默认为 "上网"
-->
利用程序遍历内存中的dom树:(该思想和利用JS遍历HTML在内存的DOM树一致):
package itheima;import java.io.File;import java.io.IOException;import javax.xml.parsers.DocumentBuilder;import javax.xml.parsers.DocumentBuilderFactory;import javax.xml.parsers.ParserConfigurationException;import org.junit.Test;import org.w3c.dom.Document;import org.w3c.dom.Node;import org.w3c.dom.NodeList;import org.xml.sax.SAXException;public class JaxpParseXml {private String allNodesStr="";@Test
public void jaxpTest() throws ParserConfigurationException, SAXException, IOException{//获取工厂实例
DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();//获取DOM解析器
DocumentBuilder db=dbf.newDocumentBuilder();//获取当前XML文档的dom实例
Document dom=db.parse(new File("book.xml"));//打印这棵DOM树
printDOM(dom,0);System.out.println(allNodesStr);
//该方法主要是看书架的子节点#text的value值:\n
textNodeTest(dom);}public void printDOM(Node root,int level){printFormat(root,level);// 如果不加这句话在下面调用printDOM(nextNode,level);方法的节点不能被打印
Node nextNode=null;
NodeList childNodes=null;
if(root.hasChildNodes()){
childNodes=root.getChildNodes();++level;//每递归一次,级数+1
for(int i=0;i<childNodes.getLength();++i){nextNode=childNodes.item(i);if(nextNode.hasChildNodes())//该节点是否有子节点printDOM(nextNode,level);else
printFormat(nextNode,level);}}}public void printFormat(Node nextNode,int level){allNodesStr+=format(level)+nextNode.getNodeName()+"..."+nextNode.getNodeType()+"..."+nextNode.getNodeValue()+"\n";
}public String format(int level){//凸显层级目录StringBuilder sb=new StringBuilder("|--");while((level--)!=0)
sb.insert(0,'*');
return sb.toString();
}public void textNodeTest(Node node){char[] charArr=node.getChildNodes().item(1).getChildNodes()
.item(0).getNodeValue().toCharArray();System.out.println(charArr.length);
for(char ch : charArr)System.out.println((int)ch);//10->换行(\n)}}依照打印结果做出这颗DOM树:注意一些问题:1.#document节点为文档树的根,文档节点.2.这里有两个书架节点,注意其type不同,type=1代表XML文档中的<书架></书架>type=10代表<!DOCTYPE 书架 SYSTEM "book.dtd">
类型名 说明
Node 表示所有类型值的统一接口,IE 不支持
Document 表示文档类型
Element 表示元素节点类型
Text 表示文本节点类型
Comment 表示文档中的注释类型
CDATASection 表示CDATA 区域类型
DocumentType 表示文档声明类型
DocumentFragment 表示文档片段类型
Attr 表示属性节点类型
常量名说明 nodeType 值
ELEMENT_NODE 元素 1
ATTRIBUTE_NODE 属性 2
TEXT_NODE 文本 3
CDATA_SECTION_NODE CDATA 4
ENTITY_REFERENCE_NODE 实体参考 5
ENTITY_NODE 实体 6
PROCESSING_INSTRUCETION_NODE 处理指令 7
COMMENT_NODE 注释 8
DOCUMENT_NODE 文档根 9
DOCUMENT_TYPE_NODE doctype 10
DOCUMENT_FRAGMENT_NODE 文档片段 11
NOTATION_NODE 符号 123.注意到DOM树中没有属性节点关于这点:属性节点不是元素的子节点,它只是描述该元素节点的一些性质而已,属于元素节点结构内部的一部分
3.DOM解析对XML文档进行CRUD(以上的book.xml不引用外部的dtd文档,book.xml的encoing="GBK"): (和用JS编程对DHTML的DOM树操作思想一致)
a.对于获取xml文件的DOM以及将内存中的DOM写入硬盘中的xml文件,经常用到,把他们两个分别封装在两个方法中:
public Document getDOM(String xmlPath) throws ParserConfigurationException, SAXException, IOException{
DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();DocumentBuilder db=dbf.newDocumentBuilder();return db.parse(new File(xmlPath));//拿到book.xml的dom}//将内存中的DOM写入到xml文件中,需要用到
//javax.xml.transform.TransFormer:将源DOM树转化为结果DOM树
public void write2XML(Node dom,String path) throws TransformerFactoryConfigurationError, TransformerException, UnsupportedEncodingException, FileNotFoundException{//同获取Document实例思想一致
Transformer trans=TransformerFactory.newInstance().newTransformer();trans.transform(new DOMSource(dom),
new StreamResult(new OutputStreamWriter(new FileOutputStream(new File(path)),"GBK")));//相当于将内存中修改好的DOM树写入到book.xml中//trans.transform(new DOMSource(dom), new StreamResult(new File(path)));
/*默认插入节点的编码方式为UTF-8,上面是用转换流来指定charset,可以解决乱码
乱码原因:写入UTF-8字节,而XML文档以GBK解码导致 乱码 ,例如:"你好"被解析为"浣犲ソ"*/
//还有一点:在新的book.xml中<!DOCTYPE>引用外部dtd文件的语句被消除?(暂时未找到解决方法)
}b.读取XML中的内容/*2.获取书名元素(标签)中封装的内容*/
@Test
public void getXmlElements() throws ParserConfigurationException, SAXException, IOException{NodeList eleNode=getDOM("book.xml").getElementsByTagName("书名");//获取到书名元素(标签)System.out.println(eleNode.item(0).getTextContent());//JavaWeb//书名中虽然用了引用实体获取,但是也可以通过该方法拿到}/*3.获取页面作者元素中的属性*/
@Test
public void getXmlProperties() throws ParserConfigurationException, SAXException, IOException{/* NamedNodeMap attrMap=getDOM("book.xml").getAttributes();
System.out.println(attrMap); //注意getDOM代表#document这个节点,其没有属性,因此为null.
*/
//方法一:
NamedNodeMap attrNodes=getDOM("book.xml").getElementsByTagName("页面作者").item(0).getAttributes();for(int index=0;index<attrNodes.getLength();++index)System.out.println(attrNodes.item(index));//遍历出页面作者中所有的属性及属性值 //属性="属性值"System.out.println(attrNodes.getNamedItem("网站职务")+"\n");//网站职务="页面作者" //底层AttrImpl复写了toString方法//方法二:在知道是Element实例的情况下,强制向下转型(Node->Element),为了使用Element提供的获取属性的方法
Element eleNode=(Element) (getDOM("book.xml").getElementsByTagName("页面作者").item(0));System.out.println(eleNode.getAttribute("网站职务"));//该方法以String形式直接返回属性的值System.out.println(eleNode.getAttributeNode("网站职务"));//该方法返回Attr,可以进一步使用Attr提供的方法}c.修改DOM树中的节点:/*4.修改DOM树中的节点*/
@Test
public void updateNode() throws DOMException, ParserConfigurationException, SAXException, IOException, TransformerFactoryConfigurationError, TransformerException{//修该售价标签中封装的内容为"50元"
Document dom= getDOM("book.xml");
dom.getElementsByTagName("售价").item(0).setTextContent("50元");//该语句执行完book.xml不会有任何变化//因为仅仅是修改了内存中的DOM树,而没有重新写入硬盘上的book.xml.
write2XML(dom,"book.xml");
}/*5.在原有的书标签之前再插入一个书标签*/
@Test
public void insertNode() throws ParserConfigurationException, SAXException, IOException, TransformerFactoryConfigurationError, TransformerException{Document dom= getDOM("book.xml");
Node bookShelf=dom.getElementsByTagName("书架").item(0);
Node refChild=dom.getElementsByTagName("书").item(0);
Node bookNode=dom.createElement("书");
bookShelf.insertBefore(bookNode, refChild);//会将newChild插入到refChild之前
bookNode.appendChild(dom.createElement("你好"));//在书标签中添加一个子节点 书名write2XML(dom, "book.xml");
/*
犯的错误:
使用dom.insertBefore(newChild,refChild) :DOMException - HIERARCHY_REQUEST_ERR
API描述:如果此节点为不允许 newChild 节点类型的子节点的类型,也就是说调用该方法的节点不可能有该子节点
*/
}原XML文档:<?xml version="1.0" encoding="GBK" standalone="yes"?><书架><书><书名>JavaWeb</书名><作者 姓名="王五"/><售价>50元</售价><页面作者 个人爱好="上网" 网站职务="页面作者" 联系信息=""/></书></书架>执行完上面插入节点代码后:
<?xml version="1.0" encoding="GBK"?><书架> <书><你好/></书><书> <书名>JavaWeb</书名> <作者 姓名="王五"/> <售价>50元</售价> <页面作者 个人爱好="上网" 网站职务="页面作者" 联系信息=""/> </书> </书架>/*删除节点*/
@Test
public void deleteNode() throws ParserConfigurationException, SAXException, IOException, TransformerFactoryConfigurationError, TransformerException{Document dom=getDOM("book.xml");
Node delNode=dom.getElementsByTagName("页面作者").item(0);
Node book=dom.getElementsByTagName("书").item(0);
book.removeChild(delNode);write2XML(dom,"book.xml");
//删除一个节点,那么该节点的所有子节点将不存在
}