很久没有写博客了,我认为黑马最最可贵的地方在于培养一个开发人员写博客的习惯。在写博客的过程当中,可以巩固以前的旧知识,同时增加了发现新问题的几率,对于提高自己是一个很好的途径。废话不多说,就将XML的两个常用解析器拿来记录一下。
一、Pull、DOM4J解析器概述
在概述部分,我都将这两个解析器按照自己的理解来阐述一下。自己是菜鸟,就不要写出高大上的话,这样才能真正把知识转化为自己的。
1.Pull解析器
Pull解析器是Android内置的XML解析器。Pull的内部,实现的是基于事件驱动的SAX解析思想,一次读取一行XML文件,并加载一行,解析一行。Pull的有点在于其可以控制SAX读取指针的动作,程序让它往下走,指针才会读取下一行。
2.DOM4J
DOM4J是开发JDOM的意外产物。据说开发JDOM的一帮子人内讧,一帮说要这样,一帮说要那样,然后分家。分家出去的那帮人做了DOM4J,又据说性能等各方面完暴JDOM,成了现在最流行的XML解析器。书写简单,效率高,集合了SAX和DOM思想,怎一个“屌”字了得~
二、两种解析器的应用
1.Pull解析器使用步骤
使用Pull解析XML文件的大致步骤如下:
第一步:
创建一个解析器工厂
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
第二步:
通过解析器工厂,获取一个解析器对象
XmlPullParser parser = factory.newPullParser();
第三步:
为解析器设置一个输入流,关联需要解析的XML文件
parser.setInput(new FileInputStream("student.xml"), "utf-8");
假设这个需要解析的就是这个student.xml文件,并指定解析使用的字符编码是utf-8格式。
第四步:
获取解析器事件类型,可以根据事件类型来执行相对应的操作
int eventType = parser.getEventType();
事件类型是一个整型数据,代表着解析器类当中的诸如START_DOCUMENT的一系列常量。
下面的例子,就是解析一个student.xml文件,并将文件中的每个元素,都封装成Student对象,存放入List集合中,这个例子的关键点在于分清楚每一个事件类型应该采取什么样的操作。
先来看看这个XML文件:
<?xml version="1.0" encoding="UTF-8"?>
<students>
<student number="s007">
<name>zs</name>
<age>14</age>
<sex>male</sex>
<score>91</score>
</student>
<student number="s002">
<name>wangwu</name>
<age>24</age>
<sex>male</sex>
<score>87</score>
</student>
<student number="s003">
<name>lisi</name>
<age>12</age>
<sex>female</sex>
<score>89</score>
</student>
<student number="s004">
<name>zhaoliu</name>
<age>24</age>
<sex>male</sex>
<score>78</score>
</student>
</students>
number、name、age、sex、score这些元素,应该被封装成一个Student类,这个Student类如下:
package com.heisejiuhuche.pull;
public class Student {
/*
* 私有的成员属性,都是XML文件当中的元素
*/
private String number;
private String name;
private int age;
private String sex;
private double score;
/*
* 带参数的构造方法,用于封装XML文件当中解析出来的数据
*/
public Student(String number, String name, int age, String sex, double score) {
super();
this.number = number;
this.name = name;
this.sex = sex;
this.age = age;
this.score = score;
}
/*
* 复写toString方法
*/
@Override
public String toString() {
return "Student [number=" + number + ", name=" + name + ", age=" + age
+ ", score=" + score + "]";
}
/*
* 一些列的get set方法
*/
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
/*
* 空参数构造方法
*/
public Student() {
super();
}
}
写好了Student类,下面要做的就是解析XML文件。解析的整体思路就是利用Pull解析器的getEventType()方法,获取解析器当前的标签状态,判断该标签是起始标签还是结束标签,做出相应的动作。如果是起始标签,又可以使用解析器的getName()方法获取标签名,通过判断标签名,再进行进一步的操作。代码中有详细的注释。
示例代码:
package com.heisejiuhuche.pull;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserFactory;
/**
* 这个类将解析student.xml,将student元素的每个属性,封装到Student类当中
* 然后将每个Student对象存入集合
* 最后打印这个集合验证每个对象解析并存储成功
*
* 在实现过程中最重要的是明确解析的每一步应该做出什么样的动作,例如哪一步该
* 初始化集合,哪一步该封装数据,哪一步该存储对象,在注释中有一一说明
* @author jeremy
*
*/
public class PullRead {
public static void main(String[] args) throws Exception {
//得到PullParser解析器工厂
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
//从工厂得到解析器parser
XmlPullParser parser = factory.newPullParser();
//设置parser解析器的输入流,关联需要解析的xml文件,并指定解析编码为utf-8
parser.setInput(new FileInputStream("student.xml"), "utf-8");
//声明空集合,用于以后存储Student对象
List<Student> list = null;
//声明Student对象,用于封装解析出来的数据
Student student = null;
//Pull是基于事件驱动,所以首先要获得解析器的事件类型
int eventType = parser.getEventType();
//如果事件类型不等于XmlPullParse类的结束标签这个静态常量,就执行循环体
while(eventType != XmlPullParser.END_DOCUMENT) {
String tagName = parser.getName(); //获得此时此刻解析器指针位置的标签名
switch (eventType) { //switch语句对事件类型进行判断,做出相应动作
case XmlPullParser.START_TAG: //如果当前标签是开始标签
/*
* 判断如果是students根标签,即初始化list集合
*/
if(tagName.equals("students")) {
list = new ArrayList<Student>();
}
/*
* 如果是student元素,就初始化Student对象,获取student元素的
* 唯一属性:number,并赋值给student的number属性
*/
if(tagName.equals("student")) {
student = new Student();
String number = parser.getAttributeValue(0);
student.setNumber(number);
}
/*
* 一下以此类推,判断标签名,然后调用parser解析器的nextText()方法
* 获取文本值,并赋值给Student类的相应属性
*/
if(tagName.equals("name")) {
String name = parser.nextText();
student.setName(name);
}
if(tagName.equals("age")) {
String age = parser.nextText();
student.setAge(Integer.parseInt(age));
}
if(tagName.equals("sex")) {
String sex = parser.nextText();
student.setSex(sex);
}
if(tagName.equals("score")) {
String score = parser.nextText();
student.setScore(Double.parseDouble(score));
}
break;
case XmlPullParser.END_TAG: //如果标签状态为结束标签
/*
* 如果是student元素的结束标签,就将一个封装好的Student对象
* 存储到list集合中,并将student对象置为空,以便下次使用
* 置空操作为可选
*/
if(tagName.equals("student")) {
list.add(student);
student = null;
}
break;
default:
break;
}
//parser解析器读完一行之后,控制解析器继续往下读一行
parser.next();
//并获得当前事件类型
eventType = parser.getEventType();
}
//打印整个集合
System.out.println(list);
}
}
程序输出结果:
[Student [number=s007, name=zs, age=14, score=91.0], Student [number=s002, name=wangwu, age=24, score=87.0], Student [number=s003, name=lisi, age=12, score=89.0], Student [number=s004, name=zhaoliu, age=24, score=78.0]]
解析并封装成功~
除了解析,Pull也提供了写如一个XML文件的方法,通过XmlSerializer对象的一系列方法(序列化)实现。写入的方式相对简单,就是调用startDocument(),startTag(),text()等方法,创建标签即文本内容即可。下面的代码将创建一个名为stu.xml文件。
示例代码:
package com.heisejiuhuche.pull;
import java.io.FileOutputStream;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlSerializer;
public class PullWrite {
public static void main(String[] args) throws Exception {
//创建解析器工厂
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
//获取XmlSerializer对象
XmlSerializer serializer = factory.newSerializer();
//设置serializer对象的输出流,关联要输出的文件,并指定输出编码为utf-8
serializer.setOutput(new FileOutputStream("stu.xml"), "utf-8");
//startDocument方法接收两个参数,分别是xml文档声明的encoding的值和standalone的值
serializer.startDocument("utf-8", true);
/*
* 创建students根元素,以下方法中的第一个参数null是名称空间,这里没有名称空间,故为null
* 以此类推,创建student元素,创建name元素,并赋值
* 要注意的一点就是,写完start方法,就马上写end方法,然后将子元素写在start和end之间
* 就像他们出现在最终xml文件中的格式一样
*/
serializer.startTag(null, "students");
serializer.startTag(null, "student");
serializer.startTag(null, "name");
serializer.text("lisi");
serializer.endTag(null, "name");
serializer.startTag(null, "age");
serializer.text("18");
serializer.endTag(null, "age");
serializer.startTag(null, "sex");
serializer.text("male");
serializer.endTag(null, "sex");
serializer.startTag(null, "score");
serializer.text("98");
serializer.endTag(null, "score");
serializer.endTag(null, "student");
serializer.endTag(null, "students");
serializer.endDocument();
}
}
生成的stu.xml文档:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<students>
<student>
<name>lisi</name>
<age>18</age>
<sex>male</sex>
<score>98</score>
</student>
</students>
2.DOM4J解析器使用步骤
老大哥来了,使用起来真的是方便。DOM4J集合了SAX和DOM的思想,在关联解析文件的时候,用的是SAXReader对象,而对XML进行增删改查的过程,用的又是DOM的思想。不愧是解析思想的集大成者。废话不多说,用代码说明一下DOM4J的使用,真的是简单~
2.1DOM4J解析XML文件的步骤
第一步:
创建SAXReader对象
SAXReader reader = new SAXReader();
第二步:
关联解析文件,得到该文件的Document对象,这是DOM思想的开始
Document document = reader.read("文件路径");
2.2DOM4J回写(这里对于回写就不做说明啦)的步骤
DOM4J的回写是通过XMLWriter对象,配合OutputFormat对象实现的,简单看一下吧,用起来很方便。
第一步:
创建OutputFormat对象,这个对象是用于格式化输出的XML文件,通过两个静态方法createPrettyPrint()和createCompactFormat()来创建格式化的输出和紧凑的输出;格式化的就是我们一般看到的XML格式,compact的格式就是将XML文件输出在一行,供计算机阅读;这里我们就是用pretty格式啦!
OutputFormat format = OutputFormat.createPrettyPrint();
第二步:
创建XMLWriter对象,这个对象接收OutputStream和OutputFormat对象作为参数
XMLWriter writer = new XMLWriter(new FileOutputStream(filePath), format);
第三步:
回写修改之后的XML对象,这个XML对象现在是以Document的形式存在于内存,调用write方法将他写回到XML文件
writer.write(document);
第四步:
由于XMLWriter要开流,所以要关闭资源
writer.close();
完成啦~
下面就用具体的代码来阐述如何用DOM4J来对XML文件进行增删改查的操作。先来看看Student.xml这个需要被操作的文件。
Student.xml
<?xml version="1.0" encoding="UTF-8"?>
<students>
<student number="s007">
<name>zs</name>
<age>14</age>
<sex>male</sex>
<score>91</score>
</student>
<student number="s002">
<name>wangwu</name>
<age>24</age>
<sex>male</sex>
<score>87</score>
</student>
<student number="s003">
<name>lisi</name>
<age>12</age>
<sex>female</sex>
<score>89</score>
</student>
<student number="s004">
<name>zhaoliu</name>
<age>24</age>
<sex>male</sex>
<score>78</score>
</student>
</students>
由于大部分方法都要获取Document对象,也都要回写Document对象,所以先将这两个方法封装到一个工具类中,方便调用。
DOM4JUtils工具类
package com.heisejiuhuche.dom4j;
import java.io.FileOutputStream;
import java.io.IOException;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
/**
* DOM4JUtils工具类,用于获取Document对象和回写操作
* @author jeremy
*
*/
public class Dom4JUtils {
//私有化构造方法,不能创建其对象
private Dom4JUtils() {}
//获取Document对象的静态方法,获取步骤上文已经说明
public static Document getDocument(String filePath) throws DocumentException {
SAXReader reader = new SAXReader();
Document document = reader.read(filePath);
return document;
}
//回写的静态方法,回写步骤上文已经说明
public static void write2File(Document document, String filePath) throws IOException {
OutputFormat format = OutputFormat.createPrettyPrint();
XMLWriter writer = new XMLWriter(new FileOutputStream(filePath), format);
writer.write(document);
writer.close();
}
}
写好了工具类,就可以开始对XML文件进行解析了,下面的代码中运用了JUnit Test的方式对每一个方法进行测试,就不需要写一个main方法,省去了在main方法中一个一个调用的麻烦。
JUnit小技巧:
在方法上面,加上@Test注解(下面有示例),然后在报错行按下Ctrl+1导入JUnit包,接着就可以双击方法名——>Run as——>JUnit Test即可运行该方法;如果看到下图的绿色条,就是方法运行成功,没有报错
package com.heisejiuhuche.dom4j;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.junit.Test;
public class Dom4J {
String filePath = "student.xml";
/**
* 查询
* 1.查询第一个student元素的number 属性值
* 2.查询第二个student元素的name子元素的 文本
* @throws DocumentException
*/
@Test
public void find() throws DocumentException {
//获取document对象
Document document = Dom4JUtils.getDocument(filePath);
//获取document对象的跟标签,即students标签
Element rootElement = document.getRootElement();
//通过students跟标签获取student标签,element方法可以获取指定的标签
Element student = rootElement.element("student");
//通过attribute方法通过指定的属性名获取属性,通过getText方法获取属性值
System.out.println(student.attribute("number").getText());
//通过elements方法获取所有student元素的集合,通过get方法,获取集合中的第二个student元素
Element student2 = (Element)rootElement.elements("student").get(1);
//打印第二个元素的name的值
System.out.println(student2.element("name").getText());
}
/**
* 添加
* 1.给第一个student元素添加属性 country=China
* 2.给第二个student元素添加子元素<anotherName>小四</anotherName>
*/
@Test
public void add() throws Exception {
//获取document
Document document = Dom4JUtils.getDocument(filePath);
//获取根元素
Element rootElement = document.getRootElement();
//获取第一个student元素,通过addAttribute方法增加属性,方法接收属性名,属性值两个参数
rootElement.element("student").addAttribute("country", "China");
//获取第二个student元素
Element student2 = (Element)rootElement.elements("student").get(1);
//通过createElement方法创建指定名称的子元素标签
Element anotherName = DocumentHelper.createElement("anotherName");
//通过setText()方法设定标签值
anotherName.setText("xiaosi");
//通过add方法将新的标签添加到第二个stduent元素下
student2.add(anotherName);
//修改完的document对象还处于内存,要调用工具类的回写方法将document对象回写到文件
Dom4JUtils.write2File(document, filePath);
}
/**
* 添加
*
给students添加一个student元素
<student number="s004">
<name>zhaoliu</name>
<age>24</age>
<sex>male</sex>
<score>99</score>
</student>
*
*/
@Test
public void add2() throws Exception {
Document document = Dom4JUtils.getDocument(filePath);
Element rootElement = document.getRootElement();
//parseText方法,可以将xml文本直接解析为xml标签和值,很方便,该方法返回一个Doucment对象
Document student = DocumentHelper.parseText("<student number='s004'><name>zhaoliu</name><age>24</age><sex>male</sex><score>99</score></student>");
//注意,这里要获取这个document对象的根元素,才能得到新添加的student元素对象
Element studentRoot = student.getRootElement();
//将新的student元素添加到students根元素下
rootElement.add(studentRoot);
//回写
Dom4JUtils.write2File(document, filePath);
}
/**
* 删除
* 1.删除第一个student元素的country
* 2.删除第二个student元素的<score>
*/
@Test
public void remove() throws Exception {
Document document = Dom4JUtils.getDocument(filePath);
Element rootElement = document.getRootElement();
Element student = rootElement.element("student");
//通过attribute方法得到指定明名称的属性,即country属性
Attribute attr = student.attribute("country");
//通过remove方法删除该属性
student.remove(attr);
//获得第二个学生对象
Element student2 = (Element)rootElement.elements("student").get(1);
//删除score标签
student2.remove(student2.element("score"));
//修改完成后回写生效
Dom4JUtils.write2File(document, filePath);
}
/**
* 修改
* 1.修改第一个student元素的属性 number=s007
* 2.修改第二个student元素的sex子元素 为 male
*/
@Test
public void update() throws Exception {
Document document = Dom4JUtils.getDocument(filePath);
Element rootElement = document.getRootElement();
Element student = rootElement.element("student");
//addAttribute方法有个特点,如果属性不存在,则添加,如果属性存在,则修改该属性
student.addAttribute("number", "s007");
//获取第二个student对象
Element student2 = (Element)rootElement.elements("student").get(1);
//获取sex元素并将值修改为male
student2.element("sex").setText("male");;
//回写生效
Dom4JUtils.write2File(document, filePath);
}
}
三、小案例
这个案例实现了一个简易的商品管理系统,用户可以选择需要的操作,完成对商品XML文件的增删改查操作。有兴趣就看看吧,不想写注释了。。。有点累咯大家见谅~大致的实现思想就是基于DOM4J的解析和回写。当然了,第一次启动程序的时候,需要先添加一些列的商品,然后在进行其他操作。代码里没有很严谨,没有对输入做校验,所以才叫简易系统嘛哈哈,我就不为难自己了~
pro.xml
<?xml version="1.0" encoding="UTF-8"?>
<products>
<product id="000000000002">
<name>shanddd</name>
<price>666.66666</price>
<number>666</number>
<description>shangpinxiuxiuxiu</description>
</product>
<product id="001">
<name>shangpin01</name>
<price>11.11</price>
<number>111</number>
<description>shangpin01</description>
</product>
<product id="003">
<name>shangpin03</name>
<price>888.88</price>
<number>888</number>
<description>shangpin03xiugai</description>
</product>
<product id="005">
<name>shangpin05</name>
<price>55.55</price>
<number>555</number>
<description>shangpin05</description>
</product>
<product id="006">
<name>shagpin06</name>
<price>66.66</price>
<number>666</number>
<description>shangpin06</description>
</product>
<product id="007">
<name>shangpin07</name>
<price>77.77</price>
<number>777</number>
<description>shangpin07</description>
</product>
<product id="008">
<name>shangpin08</name>
<price>88.88</price>
<number>888</number>
<description>shangpin08</description>
</product>
</products>
ProductManagingSystem
package com.heisejiuhuche.dom4j;
import java.util.List;
import java.util.Scanner;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.junit.Test;
public class ProductManagingSystem {
static Scanner scanner = new Scanner(System.in);
static String filePath = "pro.xml";
public static void main(String[] args) {
while(true) {
userInterface();
}
}
//请选择您所需的操作:
/*
<products>
<product>
<id></id>
<name></name>
<price></price>
<number></number>
<description></description>
</product>
</products>
*
*
*/
private static void userInterface() {
System.out.println("欢迎使用黑马64期产品管理系统!");
System.out.println("请选择您所需的操作:");
System.out.println("1.查询商品信息");
System.out.println("2.添加商品信息");
System.out.println("3.删除商品信息");
System.out.println("4.更改商品信息");
System.out.println("5.退出");
int option = scanner.nextInt();
switch (option) {
case 1:
showProductInfo();
break;
case 2:
addProductInfo();
break;
case 3:
removeProductInfo();
break;
case 4:
updateProductInfo();
break;
case 5:
System.out.println("谢谢使用!");
System.exit(0);
break;
default:
break;
}
}
private static void updateProductInfo() {
try {
System.out.println("请输入商品名称:");
String name = scanner.next();
// System.out.println("请输入商品id:");
// String id = scanner.next();
System.out.println("请输入商品价格:");
double price = scanner.nextDouble();
System.out.println("请输入商品数量:");
int number = scanner.nextInt();
System.out.println("请输入商品介绍:");
String description = scanner.next();
Document document = Dom4JUtils.getDocument(filePath);
Element rootElement = document.getRootElement(); //products
List<Element> proList = rootElement.elements("product"); //all product element
for(Element ele : proList) {
String proName = ele.element("name").getText(); //ele is a product element
if(proName.equals(name)) {
ele.element("price").setText(price + "");
ele.element("number").setText(number + "");
ele.element("description").setText(description);
}
}
Dom4JUtils.write2File(document, filePath);
} catch(Exception e) {
e.printStackTrace();
}
}
private static void removeProductInfo() {
try {
System.out.println("请输入商品名称:");
String name = scanner.next();
Document document = Dom4JUtils.getDocument(filePath);
Element rootElement = document.getRootElement();
List<Element> proList = rootElement.elements("product");
for(Element ele : proList) {
String proName = ele.element("name").getText(); //ele is a product element
if(proName.equals(name)) {
rootElement.remove(ele);
}
}
Dom4JUtils.write2File(document, filePath);
} catch(Exception e) {
e.printStackTrace();
}
}
private static void addProductInfo() {
try {
System.out.println("请输入商品名称:");
String name = scanner.next();
System.out.println("请输入商品id:");
String id = scanner.next();
System.out.println("请输入商品价格:");
double price = scanner.nextDouble();
System.out.println("请输入商品数量:");
int number = scanner.nextInt();
System.out.println("请输入商品介绍:");
String description = scanner.next();
Product pro = new Product(id, name, price, number, description);
Document rootDocument = Dom4JUtils.getDocument(filePath);
Element rootElement = rootDocument.getRootElement();
Document document = DocumentHelper.parseText("<product id='" + pro.getId() + "'><name>"
+ pro.getName() + "</name><price> "
+ pro.getPrice() + " </price><number> "
+ pro.getNumber() + " </number><description> "
+ pro.getDescription()
+ " </description></product>");
Element proElement = document.getRootElement();
rootElement.add(proElement);
Dom4JUtils.write2File(rootDocument, filePath);
} catch(Exception e) {
e.printStackTrace();
}
}
private static void showProductInfo() {
try {
Document document = Dom4JUtils.getDocument(filePath);
Element rootElement = document.getRootElement();
List<Element> list = rootElement.elements("product");
for(Element eles : list) {
System.out.println(eles.attribute("id").getName() + ": " + eles.attributeValue("id"));
List<Element> eleList = eles.elements();
for(Element e : eleList) {
System.out.println(e.getName() + ": " + e.getText());
}
System.out.println("======================================");
}
} catch(Exception e) {
e.printStackTrace();
}
}
}
KEEP CALM AND CARRY ON~