文章目录
XML的解析和schema约束
1、XML的解析
XML和HTML文档一样,属于标记型文档。
HTML的解析
HTML的解析中,js使用dom解析标记型文档。
- 根据HTML的层级结构,在内存中分配了一个树形结构,然后把HTML文档的标签、属性和文本都封装称为对象。比如document对象、element对象、属性对象、文本对象、Node节点对象。
XML的解析
XML的解析方式主要有两种技术——DOM和SAX。
- dom解析:根据XML的层级结构在内存中分配一个树形结构,把XML文档的标签、属性、文本都封装为对象。优点:很方便地实现增删改操作。缺点:如果文档过大,造成内存溢出。
- sax方式解析:采用的是事件驱动,边读文档边解析。具体是从上到下,一行一行的解析xml文档,解析到某个对象,就会返回对象的名称。优点:如果文件很大,也不会造成内存溢出,方便实现查询操作。缺点:不能实现增删改操作
1.1、DOM解析
DOM解析原理:xml解析器一次性把整个xml文档加载进内存,然后在内存中构建一颗Document的对象树,通过Document对象,得到树上的节点对象,通过节点对象访问(操作)到xml文档的内容。
优点:
1、允许应用程序对数据和结构做出更改。
2、访问是双向的,可以在任何时候在树中上下导航,获取和操作任意部分的数据。
缺点:
1、通常需要加载整个XML文档来构造层次结构,消耗资源大。
2、如果XML文件比较大,容易影响解析性能且可能会造成内存溢出。
1.2、SAX解析:
SAX的工作原理简单地说就是对文档进行顺序扫描,当扫描到文档开始与结束、元素开始与结束、文档(document)结束等地方时通知事件处理函数,由事件处理函数做相应动作,然后继续同样的扫描,直至文档结束。
优点:
1、解析速度快,SAX解析器是对文档的解析过程是一种边解析边执行的过程;
2、内存消耗少,SAX解析器对文档的解析过程中,无需把整个文档都加载到内存中;
3、使用SAX解析器时,可以注册多个ContentHandler对象,并行接收事件。
缺点:
1、必须实现事件处理程序;
2、不能随机访问:SAX解析器对文档的解析是顺序进行的;
3、不能修改文档:使用SAX对文档进行解析,只能访问文档内容,无法做到向文档中添加节点,更不能删除和修改文档中的内容。
图解两种方式
图解:
2、XML解析器
具体到XML的解析,我们是需要解析器的。常见的针对dom和sax两种解析方式的解析工具有三种:
- sun公司开发的xml解析器——jaxp解析器
- dom4j组织开发的xml解析器——dom4j解析器(实际开发中使用的)
- jdom组织开发的xml解析器——jdom解析器
jdom解析器可以理解为包含于dom4j里面,dom4j功能更为强大。我查了一下,有人是这样分解析方式的:
2.1、JAXP
参考维基百科——JAXP
概述:
JAXP(Java API for XML Processing,意为XML处理的Java API)是Java XML程序设计的应用程序接口之一,它提供解析和验证XML文档的能力。JAXP是在Java社区进程下开发的,包括JSR 5 (JAXP 1.0)和 JSR 63 (JAXP 1.1和1.2)两个规范。
JAXP解析XML的三种基本接口为:
- 文档对象模型解析接口或DOM接口
- XML简单API解析接口或SAX接口
- XML流API或StAX接口(是JDK 6的一部分,为JDK 5提供单独的包)
除了解析接口,JAXP还提供了XSLT接口用来对XML文档进行数据和结构的转换。
版本
J2SE版本 | 其中的JAX版本 |
---|---|
1.4 | 1.1 |
1.5 | 1.3 |
1.6 | 1.4 |
JAXP 1.4.4于2010年9月3日发布。JAXP 1.3已经于2008年2月12日产品终结。
JAXP解析器在jdk的javax.xml.parsers包里面。里面有四个类:分别针对了dom和sax解析使用的类。
-
1、DOM方式:
DocumentBuilder:解析器类,这个类是一个抽象类,不能new,该类的实例可以从DocumentBuilderFactory.newDocumentBuilder()方法获取。
DocumentBuilderFactory:解析器工厂,这个类也是一个抽象类,不能new,通过newInstance()方法可以获取其实例。
-
2、SAX方式:
SAXParser:解析器类
SAXParserFactory:解析器工厂
2.1.1、DOM方式中主要使用的函数
(1)Document
与DOM方式有关的是www.3c.dom下的Document类:
- DocumentBuild里面有一个方法,可以解析xml——parse(“url路径”),返回的是Document整个文档,返回的document是一个接口,父节点是Node,如果在document里面找不到想要的方法,我们可以去Node里面找。
Document里面的常用方法:
- getElementByTagName(String tagname),这个方法可以得到标签,返回的是集合NodeList
- 创建标签,createElement(String tagname)
- 创建文本,createTextNode(String data)
- 把文本添加到标签下面,appendChild(Node newChild)
- 删除节点,removeChild(Node oldChild)
- 获取父节点,getParentNode()
关于NodeList list,有两个方法
- getLength(),得到其长度
- item(int index),得到index下标对应的元素
2.1.2、JAXP实际操作
(1)JAXP查询某个节点
XML文档:
<?xml version="1.0" encoding="utf-8" ?>
<!--<!DOCTYPE person SYSTEM "1.dtd">-->
<!-- 前面是感叹号+DOCTYPE+根元素名称,
system要大写 之后是dtd路径-->
<!--现在使用第二种方式,内部使用-->
<!DOCTYPE person [
<!ELEMENT person (name,age,school+,job)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
<!ELEMENT school ANY>
<!ELEMENT job EMPTY>
<!ENTITY testName "ZhanSan">
]>
<!--使用实体,一般使用内部dtd方法防止内容取不到-->
<person>
<name>&testName;</name>
<age>120</age>
<!-- <猫></猫>-->
<school></school>
<school>山东大学</school>
<job></job>
</person>
创建java程序:
package JAXP_XML;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
/**
* @author 雨夜※繁华
* @date 2021/3/1 16:23
* 实现jaxp解析xml文件——基于DOM模式
*/
public class JaxpTest_1 {
public static void main(String[] args) throws ParserConfigurationException {
//1、查询xml文件里面所有的name的值
//步骤
/*
* 1、创建工厂
* 2、根据工厂返回documentBuilder实例
* 3、解析xml文档,返回会对应的document实例
*
* 4、使用API获取所有的name标签,返回一个NodeList集合
* 5、打印该标签里面的值
*/
DocumentBuilderFactory dbF = DocumentBuilderFactory.newDefaultInstance();
//newDocumentBulder方法可能会抛出异常
DocumentBuilder db = dbF.newDocumentBuilder();
//使用documentBuilder的解析器方法,解析xml文档,返回对应的document实例_它也会抛出异常
try{
//注意,这个Document接口来的,它来自org.w3c.school包
Document document = db.parse("src\\XML\\1.xml");//当前路径
NodeList nodeList = document.getElementsByTagName("name");//获取name标签元素
//不支持高级for-each
// for(Node n:nodeList){
//
// }
for(int i=0;i<nodeList.getLength();i++){
System.out.println(nodeList.item(i).getTextContent()+" "+i);
}
}catch (IOException | SAXException E){
E.printStackTrace();
}
}
}
运行结果:
(2)Jaxp增加一个结点
比如我们的要求是在name下面,添加一个sex结点,默认值为nan。
文档如下:
<?xml version="1.0" encoding="utf-8" ?>
<person>
<name>zhansan</name>
<age>120</age>
<school></school>
<school>山东大学</school>
<job></job>
</person>
我们第一次编写的代码:
package JAXP_XML;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
/**
* @author 雨夜※繁华
* @date 2021/3/1 17:09
* Jaxp增加一个结点,比如我们的要求是在name下面,添加一个sex结点,默认值为nan。
*/
public class JaxpTest_3 {
public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newDefaultInstance();
DocumentBuilder db = documentBuilderFactory.newDocumentBuilder();
//这里是把xml树形文档在内存中创建了一个document对象来存储
Document document = db.parse("src\\XML\\1_1.xml");
//获取person的第一个结点
NodeList person = document.getElementsByTagName("person");
Node p1 = person.item(0);//第一个
// 第一步,打印它的值
System.out.println(p1.getTextContent());
//创建结点元素
Element sex = document.createElement("sex");
//创建文本
Text text = document.createTextNode("nan");
//将文本添加到sex结点下面
sex.appendChild(text);
//添加到person下面
p1.appendChild(sex);
//
}
}
运行结果:
从图里面看得出来,文档并没有加上我们想要的结点内容。因为DOM是将xml文档解析到了内存里面,我们修改的只是内存中的Document。我们还需要将Document实例写入回这个xml文件才算完成任务。
回写xml:
//回写xml文件
//使用TransformerFactory——也是一个抽象类
TransformerFactory transformerFactory = TransformerFactory.newInstance();
//可能会抛出异常
Transformer transformer = transformerFactory.newTransformer();
//可能抛出异常
transformer.transform(new DOMSource(document),new StreamResult("src\\XML\\1_1.xml"));
//这里选择都抛出
运行效果:
从上面我们可以总结DOM方式生成XML文件有如下几步:
首先是创建DOM树(即规定XML文件中的内容):
- 创建DocumentBuilderFactory对象
- 通过DocumentBuilderFactory对象创建DocumentBuilder对象
- 通过DocumentBuilder对象的newDocument()方法创建一个Document对象,该对象代表一个XML文件
- 通过Document对象的createElement()方法创建根节点
- 通过Document对象的createElement()方法创建N个子节点,并为他们赋值,再将这些子节点添加到根节点下
- 将根节点添加到Document对象下
其次是将DOM树转换为XML文件:
- 创建TransformerFactory类的对象
- 通过TransformerFactory创建Transformer对象
使用Transformer对象的transform()方法将DOM树转换为XML文件。(该方法有两个参数,第一个参数为源数据,需要创建DOMSource对象并将Document加载到其中;第二个参数为目的文件,即要生成的XML文件,需要创建StreamResult对象并指定目的文件),因为我们操作的是文件,所以我们需要流。
(3)jaxp修改一个节点——setTextContent方法
现在我们的作业是修改添加的sev标签,从nan变成nv。
public static void modifySex() throws ParserConfigurationException, SAXException, IOException, TransformerException {
//创建解析器对象
DocumentBuilderFactory dbf = DocumentBuilderFactory.newDefaultInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
//创建解析器得到解析xml的document实例
Document document = db.parse("src\\XML\\1_1.xml");
//获取要解析的节点,直接获取sex即使它是子节点
Node sex1 = document.getElementsByTagName("sex").item(0);
//修改sex的值;
sex1.setTextContent("sev");
//回写xml文档
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.transform(new DOMSource(document),
new StreamResult("src\\XML\\1_1.xml"));
System.out.println("写入完成!");
}
(4)使用jaxp删除节点——getParentNode和removeChild()
必须是父节点调用removeChild()才能删除子节点。
package JAXP_XML;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import javax.xml.transform.TransformerException;
/**
* @author 雨夜※繁华
* @date 2021/3/2 15:35
* 使用jaxp删除一个节点sex
*/
public class JaxpTest5 {
public static void main(String[] args) throws TransformerException {
deleteSex();
}
public static void deleteSex() throws TransformerException {
//创建解析器
//创建解析xml的document
//获取要删除的节点
//获取要删除节点的父节点
Utils utils = new Utils();
Document document = utils.getDocumet("src\\XML\\1_1.xml");
Node sex = document.getElementsByTagName("sex").item(0);
Node parent = sex.getParentNode();
parent.removeChild(sex);
utils.writeXml(document,"src\\XML\\1_1.xml");
//使用方法removeChild()删除该节点
//回显xml——使用封装的回显方法Utils.writeXml(Document document,String url)
}
}
Utils的方法:
public boolean writeXml(Document document,String url) throws TransformerException {
//回写xml文档
try{
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.transform(new DOMSource(document),
new StreamResult(url));
System.out.println("写入完成!");
}catch (TransformerException e){
e.printStackTrace();
System.out.println("写入失败!");
return false;
}
return true;
}
运行结果:
(5)jaxp遍历节点——把xml所有元素的值都打印出来
package JAXP_XML;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* @author 雨夜※繁华
* @date 2021/3/2 15:47
*
* 5)jaxp遍历节点——把xml所有元素的值都打印出来,相当于遍历树形结构
*
* 步骤:得到节点,得到其子节点,然后递归调用
*/
public class JaxpTest_5 {
public static void main(String[] args) {
traveElement();
}
public static void traveElement(){
Document document = new Utils().getDocumet("src\\XML\\1_1.xml");
traveNode(document);
}
private static void traveNode(Node node) {
if(node==null) return;
//打印它的值:
System.out.println(node.getNodeName());
NodeList nodeList = node.getChildNodes();
for(int i=0;i<nodeList.getLength();i++){
traveNode(nodeList.item(i));
}
}
}
运行结果,看到有#号:
所以我们要添加判断,只有是文本值我们才打印
if(node.getNodeType()==Node.ELEMENT_NODE){
//打印它的值:
System.out.println(node.getNodeName());
}
这样就完成了。具体的类型如下:
3、XML约束之schema
一个xml里面只能写一个dtd,但是可以有多个schema。那么schema是什么?
XML Schema也是一种用于定义和描述XML文档结构与内容的模式语言,其出现是为了克服DTD的局限性。
3.1、Schema的语法特征
schema是要给xml文件,它符合xml语法特征,它的语法简述如下:
- 文件后缀为.xsd
- 根标签为
<schema></schema>
,具有三个属性:
1、命名空间 xmlns=“http://www.w3.org/2001/XMLSchema” 固定的
2、引入约束文件的地址 targetNameSpace
3、元素默认格式 elementFormDefault=“qualified” 或者unqualified
实际例子如: - element表示元素,分为复杂元素和简单元素,复杂元素必须加上complexType标签
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.lh/20210302"
elementFormDefault="qualified"
>
<element name="person"><!--复杂元素的schema约束-->
<complexType>
<sequence>
<element name="name" type="string"></element>
<element name="age" type="int"></element>
<element name="school" type="string"></element>
</sequence>
</complexType>
</element>
</schema>
XMLSchema复杂元素指示器
<all></all>
表示只能出现一次- Choice:只能出现其中一个
- Sequence:元素按照顺序出现
- maxOccurs=“unbounded”表示出现次数没有限制,不能出现在element里面
如:<element name="name" type="string" maxOccurs="unbounded"></element>
<any></any>
表示任意元素- 定义属性(必须是复杂元素):在复杂元素里面添加
<attribute name="属性名" type="类型" use="required"></attribute>
使用实例:
<?xml version="1.0" encoding="UTF-8" ?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.lh/202103021"
elementFormDefault="qualified"
>
<element name="person">
<complexType>
<sequence>
<element name="id" type="int" maxOccurs="unbounded"><!-- 这里必须是复杂元素,不然不能使用maxOccurs-->
<complexType>
<all>
<element name="name" type="string"></element>
</all>
</complexType>
</element>
</sequence>
<!-- <sequence>按照次序出现
</sequence>-->
<!-- <all>元素只能出现一次
</all>-->
<!-- <any>表示任意元素
</any>-->
<!--命名空间,通过别名,可以使用不同的schema的约束,来约束某个标签-->
</complexType>
</element>
</schema>
引用实例:
3.2、schema的开发流程
编写schema约束文件schema.xsd:
<?xml version="1.0" standalone="yes" ?>
<!--
第一行必须是xml声明
//现在我们来约束1_1.xml文件
步骤:
首先判断元素是简单元素还是复杂元素
* 简单元素直接element标签
* 复杂元素,则需要添加:
<complexType name="名称">
<sequence>
//再写子元素,如
<element name="name",type="string"><?element>
</sequence>
</complexType>
完成了一个简单的schema
-->
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.lh/20210302"
elementFormDefault="qualified"
>
<element name="person"><!--复杂元素的schema约束-->
<complexType>
<sequence>
<element name="name" type="string"></element>
<element name="age" type="int"></element>
<element name="school" type="string"></element>
</sequence>
</complexType>
</element>
</schema>
第二步,在要检验的xml文档里面引入schema约束文档:
一般需要注意写法:
在根节点里面,xmlns:别名="…-instance",instance表示这是一个约束实例
然后还有一个属性xmlns=“uri” 地址,表示引入的具体文档,这里两个xmlns,
所以要使用别名
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<!--实例-->
<person xmlns:other="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.lh/20210302"
other:schemaLocation="http://www.lh/20210302 Schema/schema.xsd">
<name>zhansan</name>
<age>120</age>
<school>山东大学</school>
<job/>
</person>
整体:
采用了命名空间xmlns,根节点为schema,其有一个属性就是xmlns。
3.3、对比DTD和Schema
Schema是对XML文档结构的定义和描述,其主要的作用是用来约束XML文件,并验证XML文件有效性。DTD的作用是定义XML的合法构建模块,它使用一系列的合法元素来定义文档结构。它们之间的区别有下面几点:
- 1、Schema本身也是XML文档,DTD定义跟XML没有什么关系,Schema在理解和实际应用有很多的好处。
- 2、DTD文档的结构是“平铺型”的,如果定义复杂的XML文档,很难把握各元素之间的嵌套关系;Schema文档结构性强,各元素之间的嵌套关系非常直观。
- 3、DTD只能指定元素含有文本,不能定义元素文本的具体类型,如字符型、整型、日期型、自定义类型等。Schema在这方面比DTD强大。
- 4、Schema支持元素节点顺序的描述,DTD没有提供无序情况的描述,要定义无序必需穷举排列的所有情况。Schema可以利用xs:all来表示无序的情况。
- 5、对命名空间的支持。DTD无法利用XML的命名空间,Schema很好满足命名空间。并且,Schema还提供了include和import两种引用命名空间的方法。
4、SAX解析
4.1、SAX解析的原理
我们知道解析技术有两种,dom和sax。sax方式利用事件驱动,边读边解析。它包括:
- SAXParseFactory解析器工厂
- SAXParse解析器
- 对应的解析方法saxParse.parse(…),如下,它需要使用一个事件处理器,然后自动解析。
关于事件处理器的解析过程 - 解析开始标签——startElement方法
- 解析标签内的值——character()方法
- 解析结束标签——endElement()方法
4.2、实战演示
(1)需求一:使用SAX方法解析整个xml文档
package JAXP_XML;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.IOException;
/**
* @author 雨夜※繁华
* @date 2021/3/3 15:26
*
* 实现解析打印整个xml文件
* 现在我们要解析文档sax.xml
*/
public class JaxpTest_6_sax {
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
//第一步,创建解析器对象
SAXParserFactory saxParserFactory = SAXParserFactory.newDefaultInstance();
SAXParser saxParser = saxParserFactory.newSAXParser();
//创建一个类,继承事件处理器,其自动执行解析
saxParser.parse("src\\XML\\sax.xml",new MyDefaultHandler());//自动解析
}
}
//事件处理器,我们要重写其三个方法:startElement,character(),endElement()
class MyDefaultHandler extends DefaultHandler {
public MyDefaultHandler() {
super();
}
//解析开始标签
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
//qName就是返回的标签名
System.out.print("<"+qName+">");//不要加换行
}
//解析文本,
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
//调用字符串构造方法String(ch,start,length);得到文本
System.out.print(new String(ch,start,length));
}
//解析结束标签
public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
//qName就是返回的标签名
System.out.print("</"+qName+">");
}
}
对应的xml文档
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<person>
<p1>
<name>zhansan</name>
<age>20</age>
<hobby>sing</hobby>
</p1>
<p1>
<name>lisi</name>
<age>22</age>
<hobby>dance</hobby>
</p1>
</person>
运行效果:
(2)需求二:打印所有的name标签
定义一个flag变量,开始方法时,如果是name,就置为true,如果flag为true,就打印该标签的值,执行到结束方法时,如果是name,就置flag为flase。
package JAXP_XML;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.IOException;
/**
* @author 雨夜※繁华
* @date 2021/3/3 15:43
* 使用sax方法,解析所有的name标签的值
*/
public class JaxpTest7_sax {
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException {
//创建SAX解析器
SAXParser saxParser = SAXParserFactory.newDefaultInstance().newSAXParser();
//创建一个类,继承事件处理器,实现其三个方法
saxParser.parse("src\\XML\\sax.xml",new MyDefaultHandler2());
}
}
//事件处理器,我们要重写其三个方法:startElement,character(),endElement()
class MyDefaultHandler2 extends DefaultHandler {
private boolean flag = false;
//解析开始标签
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
//qName就是返回的标签名
if(qName.equals("name"))
{
flag = true;
}
}
//解析文本,
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
if(flag==true){
//调用字符串构造方法String(ch,start,length);得到文本
System.out.println(new String(ch,start,length));
}
}
//解析结束标签
public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
//qName就是返回的标签名
if(qName.equals("name")){
flag = false;
}
}
}
运行结果:
(3)需求三——打印第二个name标签的值
在需求二
的基础上,我们只需要设置一个索引index,算到2就打印该值即可。
对应的事件处理器:
//事件处理器,我们要重写其三个方法:startElement,character(),endElement()
class MyDefaultHandler3 extends DefaultHandler {
private boolean flag = false;
private int index = 0;
//解析开始标签
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
//qName就是返回的标签名
if(qName.equals("name"))
{
flag = true;
index++;//索引++
}
}
//解析文本,
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
if(flag==true && index==2){//打印第二个name的值
//调用字符串构造方法String(ch,start,length);得到文本
System.out.println(new String(ch,start,length));
}
}
//解析结束标签
public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
//qName就是返回的标签名
if(qName.equals("name")){
flag = false;
}
}
}
5、使用dom4j来解析xml
5.1、dom4j介绍
dom4j是一个Java的XML API,类似于jdom,用来读写XML文件的。DOM4J是开源组织提供的一个免费的、强大的XML解析工具,如果开发者需要在项目中使用那么需要下载并引入jar包。
-
下载DOM4J地址:https://dom4j.github.io/
-
引入:dom4j-1.6.1.jar (核心包)、 jaxen-1.1-beta-6.jar(Xpath支持包)
主要的类和接口
类名或接口 | 含义 |
---|---|
Attribute | Attribute定义了XML的属性 |
Branch | Branch为能够包含子节点的节点如XML元素(Element)和文档(Docuemnts)定义了一个公共的行为, |
CDATA | CDATA 定义了XML CDATA 区域 |
CharacterData | CharacterData是一个标识借口,标识基于字符的节点。如CDATA,Comment, Text. |
Comment | Comment 定义了XML注释的行为 |
Document | 定义了XML文档 |
DocumentType | DocumentType 定义XML DOCTYPE声明 |
Element | Element定义XML 元素 |
ElementHandler | ElementHandler定义了 Element 对象的处理器 |
ElementPath | 被 ElementHandler 使用,用于取得当前正在处理的路径层次信息 |
Entity | Entity定义 XML entity |
Node | Node为所有的dom4j中XML节点定义了多态行为 |
NodeFilter | NodeFilter 定义了在dom4j节点中产生的一个滤镜或谓词的行为(predicate) |
ProcessingInstruction | ProcessingInstruction 定义 XML 处理指令. |
Text | Text 定义XML 文本节点. |
Visitor | Visitor 用于实现Visitor模式. |
XPath | XPath 在分析一个字符串后会提供一个XPath 表达式 |
5.2、主要的类或接口
5.2.1、Document
1.读取XML文件,获得document对象.
SAXReader reader = new SAXReader();
Document document = reader.read(new File("input.xml"));
2.解析XML形式的文本,得到document对象.
String text = "<members></members>";
Document document = DocumentHelper.parseText(text);
3.主动创建document对象.
Document document = DocumentHelper.createDocument();
Element root = document.addElement("members");// 创建根节点
5.2.2、Element节点
1.获取文档的根节点.
Element rootElm = document.getRootElement();
2.取得某节点的单个子节点.
Element memberElm=root.element("member");// "member"是节点名
3.取得节点的文字
String text=memberElm.getText();也可以用:
String text=root.elementText("name");这个是取得根节点下的name字节点的文字.
4.取得某节点下名为"member"的所有字节点并进行遍历.
List nodes = rootElm.elements("member");
for (Iterator it = nodes.iterator(); it.hasNext();) {
Element elm = (Element) it.next();
// do something
}
5.对某节点下的所有子节点进行遍历.
//迭代器方式
for(Iterator it=root.elementIterator();it.hasNext();){
Element element = (Element) it.next();
// do something
}
6.在某节点下添加子节点.
Element ageElm = newMemberElm.addElement("age");
7.设置节点文字.
ageElm.setText("29");
8.删除某节点.
parentElm.remove(childElm);// childElm是待删除的节点,parentElm是其父节点
9.添加一个CDATA节点.
Element contentElm = infoElm.addElement("content");
contentElm.addCDATA(diary.getContent());
5.2.3、Attribute属性
1.取得某节点下的某属性
Element root=document.getRootElement();
Attribute attribute=root.attribute("size");// 属性名name
2.取得属性的文字
String text=attribute.getText();
// 这个是取得根节点下name字节点的属性firstname的值:
String text2=root.element("name").attributeValue("firstname");
3.遍历某节点的所有属性
Element root=document.getRootElement();
for(Iterator it=root.attributeIterator();it.hasNext();){
Attribute attribute = (Attribute) it.next();
String text=attribute.getText();
System.out.println(text);
}
4.设置某节点的属性和文字.
newMemberElm.addAttribute("name", "sitinspring");
5.设置属性的文字
Attribute attribute=root.attribute("name");
attribute.setText("sitinspring");
6.删除某属性
Attribute attribute=root.attribute("size");// 属性名name
root.remove(attribute);
5.3、使用dom4j实现增删改查
使用的xml文件:
查
(1)使用dom4j来查询所有name标签的值
package DOM4J_Test;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.SAXException;
import java.util.List;
/**
* @author 雨夜※繁华
* @date 2021/3/3 16:52
* 需求1:使用dom4j来查询所有的name标签的值
*/
public class Dom4j_Test1 {
public static void main(String[] args) throws SAXException, DocumentException {
//1.创建解析器对象 /可能抛出异常
SAXReader saxReader = new SAXReader();//空参
//2.读入对应的xml文件,注意返回的是org.dom4j下的document类
Document document = saxReader.read("src/XML/sax.xml");
System.out.println(document.getName());// src/XML/sax.xml
//获取其根节点
Element element = document.getRootElement();
System.out.println(element.getName());// person
//获取所有的name名称的节点,返回的是一个list
//如果是获取person,那么你是无法得到name的,该方法只能获取其下一层
// List<Element> list = element.elements("name");
//
// //使用增强for来遍历,打印其值:
// for(Element element1:list){
// System.out.println(element1.getText());
// }
//所以我们要使用下面的方式:
List<Element> list = element.elements("p1");
//使用增强for来遍历,打印其值:
for(Element element1:list){
//获取name标签
Element name = element1.element("name");//返回第一个name
System.out.println(name.getText());
}
}
}
运行结果图:
(2)查询第二个name标签的值
package DOM4J_Test;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.util.List;
/**
* @author 雨夜※繁华
* @date 2021/3/3 16:53
* 需求2:获取第二个name标签的值
*/
public class Dom4j_Test2 {
public static void main(String[] args) throws DocumentException {
//1、构造解析器对象
SAXReader saxReader = new SAXReader();
Document document = saxReader.read("src/XML/sax.xml");
//获取根节点
Element root = document.getRootElement();
//2、获取第二个p1
List<Element> pList = root.elements("p1");
Element p2 = pList.get(1);
//3、获取p1下的name
Element name = p2.element("name");
System.out.println(name.getText());
}
}
运行结果图:
(3)获取指定位置的标签值
同上,利用list的get方法。
(4)获取p1下面所有的元素
增
(5)在第一个p1标签下面添加一个元素<sex>nv</sex>
package DOM4J_Test;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @author 雨夜※繁华
* @date 2021/3/3 16:55
* 需求5:在第一个p1标签下面添加一个标签<sex>nv</sex>
*/
public class Dom4j_Test4 {
public static void main(String[] args) throws IOException, DocumentException {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read("src/XML/sax.xml");
Element root = document.getRootElement();
System.out.println(root.getText());
//获取第一个p1节点
Element p1 = root.element("p1");
// 在p1下面创建一个sex节点
Element sex = p1.addElement("sex");
//设置该节点的值
sex.setText("nan");
//最后最重要的,记得回写xml,不同于dom的jaxp
OutputFormat outputFormat = OutputFormat.createPrettyPrint();//使用的是缩进、漂亮的方式
//第一个参数是写入的文件流,第二个是格式控制
XMLWriter writer = new XMLWriter(new FileOutputStream("src/XML/sax.xml"),outputFormat);
//要写的document实例
writer.write(document);
//一个流一定要关闭
writer.close();
}
}
运行效果:
如果使用的样式控制是:
OutputFormat outputFormat = OutputFormat.createCompactFormat();//使用的是压缩方式
在特定的位置添加——在第一个p1下面age之前添加school标签
package DOM4J_Test;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import javax.print.Doc;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.List;
/**
* @author 雨夜※繁华
* @date 2021/3/3 16:53
* 需求4:在第一个p1下面age之前添加sev标签
*/
public class Dom4j_Test4 {
public static void main(String[] args) throws DocumentException, IOException {
SAXReader saxReader = new SAXReader();
Document document = saxReader.read("src/XML/sax.xml");
Element root = document.getRootElement();
//获取第一个p1标签
Element p1 = root.element("p1");
//获取p1下的所有子节点
List<Element> list = p1.elements();
int index = 0;
//遍历,找到age节点
for(int i=0;i<list.size();i++){
if(list.get(i).getName().equals("age") && i!=0){
index = i;
break;
}
}
//创建我们要添加的school节点————使用DocumentHelper.createElement()
Element school = DocumentHelper.createElement("school");
school.setText("山东大学");
//在该节点下添加我们要添加的节点,该节点在age上面
list.add(index,school);//在该节点前插入
//记得回写xml
OutputFormat outputFormat = OutputFormat.createPrettyPrint();
XMLWriter xmlWriter = new XMLWriter(
new FileOutputStream("src/XML/sax.xml"),outputFormat);
//写入document
xmlWriter.write(document);
//关闭流
xmlWriter.close();
}
}
运行效果:
下面对Dom4j的使用作一个简单的封装:
- 创建解析器的方法封装
- 回写xml的方法封装
- 把传递的文件路径进一步封装
则得到了一个Dom4jUtils
package DOM4J_Test;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import javax.xml.parsers.SAXParser;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
/**
* @author 雨夜※繁华
* @date 2021/3/4 15:42
* <p>
* 对Dom4j的使用作简单的封装
*/
public class Dom4jUtils {
//自定义一个不允许修改的路径值:
public static final String PATH = "src/XML/1_1.xml";
public static Document getDocument(String url) {
Document document = null;
SAXReader saxReader = new SAXReader();
try {
document = saxReader.read(url);
} catch (DocumentException e) {
e.printStackTrace();
}
return document;
}
public static void writeXML(String url, Document document) {
//最后最重要的,记得回写xml,不同于dom的jaxp
//OutputFormat outputFormat = OutputFormat.createPrettyPrint();//使用的是缩进、漂亮的方式
OutputFormat outputFormat = OutputFormat.createCompactFormat();//使用的是压缩方式
//第一个参数是写入的文件流,第二个是格式控制
XMLWriter writer = null;
try {
writer = new XMLWriter(new FileOutputStream(url), outputFormat);
//要写的document实例
writer.write(document);
} catch (IOException e){
e.printStackTrace();
} finally {
if (writer != null) {
//一个流一定要关闭
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
这样我们上面的测试就可以该成如下这样了:
package DOM4J_Test;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import java.io.IOException;
import java.util.List;
/**
* @author 雨夜※繁华
* @date 2021/3/4 15:50
*/
public class Dom4j_Util_Test {
public static void main(String[] args) throws DocumentException, IOException {
Document document = Dom4jUtils.getDocument(Dom4jUtils.PATH);
Element root = document.getRootElement();
//获取第一个p1标签
Element p1 = root.element("p1");
//获取p1下的所有子节点
List<Element> list = p1.elements();
int index = 0;
//遍历,找到age节点
for(int i=0;i<list.size();i++){
if(list.get(i).getName().equals("age") && i!=0){
index = i;
break;
}
}
//创建我们要添加的school节点————使用DocumentHelper.createElement()
Element school = DocumentHelper.createElement("school");
school.setText("山东大学");
//在该节点下添加我们要添加的节点,该节点在age上面
list.add(index,school);//在该节点前插入
//记得回写xml
Dom4jUtils.writeXML(Dom4jUtils.PATH,document);
}
}
改
修改第一个p1下的age标签值为40:
package DOM4J_Test;
import org.dom4j.Document;
import org.dom4j.Element;
/**
* @author 雨夜※繁华
* @date 2021/3/4 16:00
*
* 需求:修改第一个p1下的age标签值为40
*/
public class Dom4j_Test5_modify {
public static void main(String[] args) {
Document document = Dom4jUtils.getDocument(Dom4jUtils.PATH);
//获取根结点
Element root = document.getRootElement();
//获取p1结点
Element p1 = root.element("p1");
//获取age结点
Element age = p1.element("age");
//修改它的值:
age.setText("40");
//回写xml
Dom4jUtils.writeXML(Dom4jUtils.PATH,document);
}
}
删
需求:删除第一个p1的school结点
package DOM4J_Test;
import org.dom4j.Document;
import org.dom4j.Element;
/**
* @author 雨夜※繁华
* @date 2021/3/4 16:04
*
* 需求:删除school结点(必须使用父节点删除,不能自己删除自己)
*/
public class Dom4j_Test6_delete {
public static void main(String[] args) {
Document document = Dom4jUtils.getDocument(Dom4jUtils.PATH);
//获取其父节点
Element root = document.getRootElement();
Element p1 = root.element("p1");
Element school = p1.element("school");
//利用父节点删除
p1.remove(school);
//回写xml
Dom4jUtils.writeXML(Dom4jUtils.PATH,document);
}
}
运行效果:
获取第一个p1里面id的属性值:
<?xml version="1.0" encoding="UTF-8"?>
<person>
<p1 id="AAA">
<name>zhansan</name>
<age>40</age>
<hobby>sing</hobby>
</p1>
<p1>
<name>lisi</name>
<age>22</age>
<hobby>dance</hobby>
</p1>
</person>
代码:
package DOM4J_Test;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
/**
* @author 雨夜※繁华
* @date 2021/3/4 16:10
*
* 使用dom4j来获取标签里面的属性值
* 获取第一个p1里面的id的值
*/
public class Dom4j_Test7_GetAttri {
public static void main(String[] args) {
Document document = Dom4jUtils.getDocument(Dom4jUtils.PATH);
Element root = document.getRootElement();
Element p1 = root.element("p1");
//获取其属性:
//方式一:
Attribute attribute = p1.attribute("id");
//获取其值:
System.out.println(attribute.getValue());
// 方式二:
System.out.println(p1.attributeValue("id"));
}
}
效果: