Android中XML文件的解析
我们知道XML格式的数据在很多软件开发中都会遇到,那么在Android开发中也不例外。那么接下来要如何去解析和创建XML文件那?下面就和大家一起研究和学习关于XML文件的解析方式和如何创建XML文件。
在Android中有三种方式可以解析XML文件,分别为PULL解析方式、SAX解析方式和DOM解析方式,那么下面就来看看这几种解析方式之间的联系和特点。
PULL解析方式:PULL解析器方式是采用事件处理的模式,主要是围绕着事件源和事件处理器工作的。一旦事件源被触发之后,事件源会调用事件处理方法进行相应的处理操作。我们只需要在事件处理方法中添加我们的处理事件逻辑即可。
特点:小巧易用、解析速度快,占用内存也较少,非常适合应用在Android移动设别中,Android系统内部在解析各种XML的时候也是采用这种方式进行解析的。
SAX解析方式:SAX解析方式和PULL解析方式有些相同,也就是他们都是基于事件的处理模式,不同的是SAX解析方式在事件源触发之后,会自动调用相应的处理器方法来处理解析逻辑,而PULL则需要自己编写事件处理方法。
特点:解析速度快、占用内存也较少,适合在Android移动设备中使用,但使用起来没有PULL那样小巧轻便。
DOM解析方式:DOM解析方式与上面的两种方式截然不同,它是采用树状结构来封装数据信息,我们可以使用DOM API来遍历XML树,一般情况下,如果想遍历和更新XML树的话,我们需要将XML文档整个文档或构造的结构加载到内存,才能进行需要的遍历和更新。
特点:解析速度很快(由于在内存中以XML树结构存储数据,遍历速度很快),但如果XML文件较大的时候,解析和加载这个大文档会耗费很多系统资源的。
通过上面,我们知道了几种在Android中可以解析XML文档的方式以及他们各自的优缺点,所以读者可以根据需要折中选择。当然,我个人习惯使用PULL解析方式来解析XML,那么接下来就先介绍使用PULL解析器来解析XML文档。
新建一个Android项目,结构目录如下:
如上图所示,我们首先需要创建一个Person类和一个XML文件person.xml并放在了assets资源目录中,一般实际开发中,XML文档来自于网络,这里是为了验证XML文档的解析。
代码分别如下:
Person.java:
public class Person {
private String name;
private int age;
private String tel;
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 String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
}
Person.xml:
<?xml version="1.0" encoding="utf-8"?>
<persons>
<person>
<name>David</name>
<age>27</age>
<tel>1500000000</tel>
</person>
<person>
<name>why</name>
<age>28</age>
<tel>1500000001</tel>
</person>
<person>
<name>jake</name>
<age>29</age>
<tel>1500000002</tel>
</person>
<person>
<name>Make</name>
<age>30</age>
<tel>1500000003</tel>
</person>
</persons>
接下来,我们需要创建一个规范的解析接口PersonParser.java,代码如下:
public interface PersonParser {
// 解析XML接口方法
public List<Person> parser(InputStream is) throws Exception;
// 生成XML序列化方法
public String serializer(List<Person> persons) throws Exception;
}
这个接口的作用主要是规范化三种解析方式的通用性,每种解析方式都需要实现该接口。
PULL解析方式:
public class PullPersonParser implements PersonParser{
@Override
public List<Person> parser(InputStream is) throws Exception {
List<Person> persons = null;
Person person = null;
// 实例化一个解析器XmlPullParser
XmlPullParser parser = Xml.newPullParser();
// 设置输入流并指定编码格式
parser.setInput(is, "UTF-8");
// 基于事件模式的处理
int eventType = parser.getEventType();
while(eventType != XmlPullParser.END_DOCUMENT) {
switch(eventType) {
case XmlPullParser.START_DOCUMENT: {
persons = new ArrayList<Person>();
}
case XmlPullParser.START_TAG: {
if(parser.getName().equals("person")) {
person = new Person();
}
else if(parser.getName().equals("name")) {
eventType = parser.next();
person.setName(parser.getText());
}
else if(parser.getName().equals("age")) {
eventType = parser.next();
person.setAge(Integer.parseInt(parser.getText()));
}
else if(parser.getName().equals("tel")) {
eventType = parser.next();
person.setTel(parser.getText());
}
}
case XmlPullParser.END_TAG: {
if(parser.getName().equals("person")) {
persons.add(person);
person = null;
}
break;
}
}
eventType = parser.next();
}
return persons;
}
@Override
public String serializer(List<Person> persons) throws Exception {
// 实例化一个XmlSerializer
XmlSerializer serializer = Xml.newSerializer();
StringWriter writer = new StringWriter();
// 设置输出为writer
serializer.setOutput(writer);
serializer.startDocument("UTF-8", true);
serializer.startTag("p", "persons");
for(Person p:persons) {
serializer.startTag("p", "person");
serializer.attribute("p", "name",p.getName());
serializer.startTag("p","age");
serializer.text(p.getAge() + "");
serializer.endTag("p", "age");
serializer.startTag("p", "tel");
serializer.text(p.getTel());
serializer.endTag("p", "tel");
serializer.endTag("p", "person");
}
serializer.endTag("p", "persons");
serializer.endDocument();
return writer.toString();
}
}
最后,我们在XMLParserAct.java中添加两个按钮,分别为reader和writer,用来解析和生成XML文档,解析出的数据,使用Log将其打印在控制台,而生成的XML文档默认存放在程序的安装目录files中。具体实现如下:
public class XMLParserAct extends Activity {
public static String TAG = "XMLParserAct";
private Button reader;
private Button writer;
PullPersonParser pullParser = new PullPersonParser();
static List<Person> persons = new ArrayList<Person>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
initView();
}
private void initView() {
reader = (Button) findViewById(R.id.reader);
writer = (Button) findViewById(R.id.writer);
OnClickListener c = new OnClickListener() {
public void onClick(View v) {
if(v == reader) {// 解析
parserPerson();
}
else if(v == writer) {// 生成
newPerson();
}
}
};
reader.setOnClickListener(c);
writer.setOnClickListener(c);
}
private void parserPerson() {
try {
InputStream is = getAssets().open("person.xml");
persons = pullParser.parser(is);
for(Person p:persons) {
Log.i("TAG",p.toString());
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
private void newPerson() {
try {
// 序列化 生成XML文档
String xml = pullParser.serializer(persons);
FileOutputStream fos = openFileOutput("person.xml",Context.MODE_PRIVATE);
fos.write(xml.getBytes("UTF-8"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行效果图---reader:
运行效果图---writer:
同时,新生成的XML文档的格式与原来的XML格式略不同,当不影响我们对其的解析和生成操作,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<persons>
<person name=”David”>
<age>27</age>
<tel>1500000000</tel>
</person>
<person name=”why”>
<age>28</age>
<tel>1500000001</tel>
</person>
<person name=”jake”>
<age>29</age>
<tel>1500000002</tel>
</person>
<person name=”Make”>
<age>30</age>
<tel>1500000003</tel>
</person>
</persons>
SAX解析器解析XML文档:
对于使用SAX解析方式解析XML文档的操作与PULL方式同样基于事件处理模式进行的。由于大部分准备工作相同,这里主要介绍不同的部分SAXPersonParser和稍微改动的Activity部分。具体如下:
SAXPersonParser代码如下:
public class SaxPersonParser implements PersonParser{
@Override
public List<Person> parser(InputStream is) throws Exception {
// 创建一个SAXParserFactory工厂实例
SAXParserFactory factory = SAXParserFactory.newInstance();
// 从工厂factory中获得具体解析实例
SAXParser parser = factory.newSAXParser();
// 自定义规则解析输入流
CustomHandler cusHandler = new CustomHandler();
// 按照自定的解析逻辑进行解析
parser.parse(is, cusHandler);
return cusHandler.getPersons();
}
@Override
public String serializer(List<Person> persons) throws Exception {
// 创建一个SAXTransformerFactory序列化工厂实例
SAXTransformerFactory factory = (SAXTransformerFactory)TransformerFactory.newInstance();
// 从factory中获得具体的序列化处理实例
TransformerHandler handler = factory.newTransformerHandler();
// 从handler中获取实际序列化实例
Transformer transFormer = handler.getTransformer();
// 设置输出的编码格式
transFormer.setOutputProperty(OutputKeys.ENCODING,"UTF-8");
// 设置是否忽略XML声明
transFormer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
// 设置是否自动添加额外空白
transFormer.setOutputProperty(OutputKeys.INDENT, "yes");
// 解析逻辑的解析结果添加到序列流中
StringWriter writer = new StringWriter();
Result result = new StreamResult(writer);
handler.setResult(result);
// 代表命名空间的URI 当URI无值时 须置为空字符串
String uri = "";
//命名空间的本地名称(不包含前缀) 当没有进行命名空间处理时 须置为空字符串
String localName = "";
// 解析
handler.startDocument();
handler.startElement(uri, localName, "books", null);
// 负责存放元素的属性信息
AttributesImpl attrs = new AttributesImpl();
char[] ch = null;
for (Person person : persons) {
attrs.clear(); // 清空属性列表
// 添加一个名为name的属性(type影响不大,这里设为string)
attrs.addAttribute(uri, localName, "name", "string", String.valueOf(person.getName()));
// 开始一个person元素 关联上面设定的name属性
handler.startElement(uri, localName, "person", attrs);
// 开始一个age元素 没有属性
handler.startElement(uri, localName, "age", null);
ch = String.valueOf(person.getAge()).toCharArray();
// 设置age元素的文本节点
handler.characters(ch, 0, ch.length);
handler.endElement(uri, localName, "age");
// 开始一个tel元素 没有属性
handler.startElement(uri, localName, "tel", null);
ch = String.valueOf(person.getTel()).toCharArray();
// 设置age元素的文本节点
handler.characters(ch, 0, ch.length);
handler.endElement(uri, localName, "tel");
handler.endElement(uri, localName, "person");
}
handler.endElement(uri, localName, "persons");
handler.endDocument();
return writer.toString();
}
private class CustomHandler extends DefaultHandler {
private List<Person> persons = null;
private Person person = null;
private StringBuilder builder = null;
@Override
public void startDocument() throws SAXException {
super.startDocument();
persons = new ArrayList<Person>();
builder = new StringBuilder();
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
if(localName.equals("person")) {
person = new Person();
}
// 重新设置StringBuilder的字符长度为0,以便重新开始读取字符节点信息
builder.setLength(0);
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
super.characters(ch, start, length);
// 将读取的字符添加到StringBuilder中
builder.append(ch, start, length);
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
super.endElement(uri, localName, qName);
if(localName.equals("name")) {
person.setName(builder.toString());
}
else if(localName.equals("age")) {
person.setAge(Integer.parseInt(builder.toString()));
}
else if(localName.equals("tel")) {
person.setTel(builder.toString());
}
else if(localName.equals("person")) {
persons.add(person);
}
}
// 返回解析后得到的数据
public List<Person> getPersons() {
return persons;
}
}
而activity部分,只需要稍作改动即可,如下所示:
private void parserPerson() {
try {
InputStream is = getAssets().open("person.xml");
//pullParser = new PullPersonParser();
//persons = pullParser.parser(is);
saxParser = new SaxPersonParser();
persons = saxParser.parser(is);
for(Person p:persons) {
Log.i("TAG",p.toString());
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
private void newPerson() {
try {
// 序列化 生成XML文档
//String xml = pullParser.serializer(persons);
String xml = saxParser.serializer(persons);
FileOutputStream fos = openFileOutput("person.xml",Context.MODE_PRIVATE);
fos.write(xml.getBytes("UTF-8"));
} catch (Exception e) {
e.printStackTrace();
}
}
注意:
代码中,我们定义了自己的事件处理逻辑,重写了DefaultHandler的几个重要的事件方法。DefaultHandler是一个事件处理器,可以接收解析器报告的所有事件,处理所发现的数据。它实现了EntityResolver接口、DTDHandler接口、ErrorHandler接口和ContentHandler接口。这几个接口代表不同类型的事件处理器。我们着重介绍一下ContentHandler接口。结构如图:
这几个比较重要的方法已被我用红线标注,DefaultHandler实现了这些方法,但在方法体内没有做任何事情,因此我们在使用时必须覆写相关的方法。
最重要的是startElement方法、characters方法和endElement方法。当执行文档时遇到起始节点,startElement方法将会被调用,我们可以获取起始节点相关信息;
然后characters方法被调用,我们可以获取节点内的文本信息;
最后endElement方法被调用,我们可以做收尾的相关操作。
上面使用的SAX方式的解析结果和生成XML文档的结果与PULL解析方式是一样的,具体的效果图不在这里罗列。
DOM解析方式解析XML:
DomPersonParser.java:
public class DomBookParser implements BookParser {
@Override
public List<Book> parse(InputStream is) throws Exception {
List<Book> books = new ArrayList<Book>();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); //取得DocumentBuilderFactory实例
DocumentBuilder builder = factory.newDocumentBuilder(); //从factory获取DocumentBuilder实例
Document doc = builder.parse(is); //解析输入流 得到Document实例
Element rootElement = doc.getDocumentElement();
NodeList items = rootElement.getElementsByTagName("book");
for (int i = 0; i < items.getLength(); i++) {
Book book = new Book();
Node item = items.item(i);
NodeList properties = item.getChildNodes();
for (int j = 0; j < properties.getLength(); j++) {
Node property = properties.item(j);
String nodeName = property.getNodeName();
if (nodeName.equals("id")) {
book.setId(Integer.parseInt(property.getFirstChild().getNodeValue()));
} else if (nodeName.equals("name")) {
book.setName(property.getFirstChild().getNodeValue());
} else if (nodeName.equals("price")) {
book.setPrice(Float.parseFloat(property.getFirstChild().getNodeValue()));
}
}
books.add(book);
}
return books;
}
@Override
public String serialize(List<Book> books) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.newDocument(); //由builder创建新文档
Element rootElement = doc.createElement("books");
for (Book book : books) {
Element bookElement = doc.createElement("book");
bookElement.setAttribute("id", book.getId() + "");
Element nameElement = doc.createElement("name");
nameElement.setTextContent(book.getName());
bookElement.appendChild(nameElement);
Element priceElement = doc.createElement("price");
priceElement.setTextContent(book.getPrice() + "");
bookElement.appendChild(priceElement);
rootElement.appendChild(bookElement);
}
doc.appendChild(rootElement);
TransformerFactory transFactory = TransformerFactory.newInstance();//取得TransformerFactory实例
Transformer transformer = transFactory.newTransformer(); //从transFactory获取Transformer实例
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); // 设置输出采用的编码方式
transformer.setOutputProperty(OutputKeys.INDENT, "yes"); // 是否自动添加额外的空白
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); // 是否忽略XML声明
StringWriter writer = new StringWriter();
Source source = new DOMSource(doc); //表明文档来源是doc
Result result = new StreamResult(writer);//表明目标结果为writer
transformer.transform(source, result); //开始转换
return writer.toString();
}
}
而activity部分,只需要稍作改动即可,如下所示:
private void parserPerson() {
try {
InputStream is = getAssets().open("person.xml");
//pullParser = new PullPersonParser();
//persons = pullParser.parser(is);
//saxParser = new SaxPersonParser();
//persons = saxParser.parser(is);
domParser = new DomPersonParser();
persons = domParser .parser(is);
for(Person p:persons) {
Log.i("TAG",p.toString());
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
解析和创建XML的效果和上面的两种方式相同。
技术交流群:179914858