在android中解析XML文件有很多方法,今天主要介绍下SAX解析。

1、SAX简介

SAX是基于事件驱动模型,可以捕获到读取文档过程中产生的事件,比如开始文档、结束文档、开始元素、结束元素、文本内容事件等。通过定义一个事件处理器,在这些事件触发后,来实现数据的获取。通过使用XMLReader类来注册事件处理器,在Android中有如下4个事件处理器接口,如下图:

补充:事件驱动模型是事件源发出事件,监听器捕获事件并做出相应的过程。这里,事件源就是发出事件的对象,监听器则是对事件感兴趣的对象。

深入分析android中用SAX解析XML文件并纠错_第1张图片

图片引用:http://www.cnblogs.com/felix-hua/archive/2012/01/10/2317404.html

注:在实际应用中不需要全部实现这4个接口,SDK提供了DefaultHandler类来处理。DefaultHandler类已经实现了这4个接口,用户只需继承该类即可。

XMLReader中的方法:

//注册处理XML文档解析事件ContentHandlerpublic void setContentHandler(ContentHandler handler)//开始解析一个XML文档public void parse(InputSorce input) throws SAXException

在上述四个接口中,最重要的就是ContentHandler这个接口,下面是对这个接口方法的详细说明:

//开始XML文档的回调函数,在文档开头时调用,其中可做预处理工作public void startDocument()throws SAXException//元素开始标签的回调函数,处理元素开始事件。//参数:namespacesURI为元素所在的名称空间,localName为不带命名空间前缀的标签名,qName为带命名空间前缀的标签名,atts为元素的属性名和值集合public void startElement(String namespacesURI , String localName , String qName , Attributes atts) throws SAXException//元素结束标签的回调函数,处理元素结束事件。public void endElement(String namespacesURI , String localName , String qName) throws SAXException//处理元素的字符内容,从参数中可以获得内容//参数:ch为文件的字符串内容,start为读到的字符串在这个数组中的起始位置,length为读到的字符串在这个数组中的长度.public void characters(char[] ch , int start , int length)  throws SAXException//结束XML文档的回调函数,在文档结束时调用,其中可做一些回收工作。public void endElement(String uri, String localName, String qName)throws SAXException


2、SAX实现解析的步骤

//1、新建一个工厂类SAXParserFactory,代码如下:SAXParserFactory factory = SAXParserFactory.newInstance();//2、让工厂类产生一个SAX的解析类SAXParser,代码如下:SAXParser parser = factory.newSAXParser();//3、从SAXPsrser中得到一个XMLReader实例,代码如下:XMLReader reader = parser.getXMLReader();//4、自己实现一个事件处理类handler,并将之注册到XMLReader中,一般最重要的就是ContentHandler。代码如下:ParserPerson parserPerson = new ParserPerson();//自己实现的handler,详细代码后面有介绍reader.setContentHandler(handler);//5、将一个xml文档或者资源变成一个java可以处理的InputStream流in后,开始解析,代码如下:parser.parse(in);  

3、SAX的优缺点:

优点:解析速度快,占用内存少。解析时不用调入整个文档,所有占用资源少。尤其在嵌入式环境中,如android,极力推荐使用SAX解析。

缺点:流式处理,因而不像DOM解析一样将文档长期驻留在内存中,数据不是持久的。需要在事件中保存数据。


4、应用实例:

代码中所使用的类如下,另外考虑代码的可扩展性,本例采用了策略模式。对每个事件处理器进行封装,将每一个事件处理器封装到一个具有共同接口的独立类ParseXML中。从而使得它们可以在不影响客户端情况下发生,也能相互替换。

//Person:人员基本信息,是一个bean类。//ParseXML类:抽象类,定义一个抽象方法,用来获取解析的结果。//ParsePersonXML类:ParseXML的子类,其含有继承DefaultHandler类的内嵌类ParsePerson,该类实现了SAX解析的事件处理器。//ContextClient类:封装事件处理器,屏蔽高层模块对事件处理器的直接访问。//MainActivity类: 通过一个TextView来显示解析的结果。

首先定义一个XML文件PersonItem,其目录为res/raw/person_item.xml,内容如下:

<?xml version="1.0" encoding="UTF-8"?><Persons><Person  account="zhangsan">    <name>张三</name>    <email>[email protected]</email>    <tel>18866661111</tel>    <boss>manager</boss>    <newProject>1</newProject>    <superPower>1</superPower></Person><Person account="lisi">    <name>李四</name>    <email>[email protected]</email>    <tel>16688883333</tel>    <boss>manager</boss>    <newProject>0</newProject>    <superPower>0</superPower></Person></Persons>
然后相应代码实现:

Person类:

public class Person {  String mName;//姓名  String mAccount;//账户  String mTel;//电话  String mEmail;//邮箱  String mBoss;//上司  String mCompetence;//超级权限  String mProprietor;//新建项目权利  public Person()  {    this.mAccount = "";    this.mName = "";    this.mTel = "";    this.mEmail = "";    this.mBoss = "";    this.mCompetence = "";  }  public Person(String paramString1, String paramString2,  String paramString3, String paramString4,   String paramString5, String paramString6)  {    this.mAccount = paramString1;    this.mName = paramString2;    this.mTel = paramString3;    this.mEmail = paramString4;    this.mBoss = paramString5;    this.mCompetence = paramString6;  }  public String getmAccount()  {    return this.mAccount;  }  public String getmBoss()  {    return this.mBoss;  }  public String getmCompetence()  {    return this.mCompetence;  }  public String getmEmail()  {    return this.mEmail;  }  public String getmName()  {    return this.mName;  }  public String getmProprietor()  {    return this.mProprietor;  }  public String getmTel()  {    return this.mTel;  }  public void setmAccount(String paramString)  {    this.mAccount = paramString;  }  public void setmBoss(String paramString)  {    this.mBoss = paramString;  }  public void setmCompetence(String paramString)  {    this.mCompetence = paramString;  }  public void setmEmail(String paramString)  {    this.mEmail = paramString;  }  public void setmName(String paramString)  {    this.mName = paramString;  }  public void setmProprietor(String paramString)  {    this.mProprietor = paramString;  }  public void setmTel(String paramString)  {    this.mTel = paramString;  }}
ParseXML类:

import java.io.InputStream;import java.util.ArrayList;public abstract class ParseXML {public abstract ArrayList<Object> getXMLInfoByList(InputStream in);}
ParsePersonXML类:
import java.io.IOException;import java.io.InputStream;import java.util.ArrayList;import javax.xml.parsers.ParserConfigurationException;import javax.xml.parsers.SAXParser;import javax.xml.parsers.SAXParserFactory;import org.xml.sax.Attributes;import org.xml.sax.SAXException;import org.xml.sax.helpers.DefaultHandler;import android.util.Log;public class ParsePersonXML extends ParseXML {@Overridepublic ArrayList<Object> getXMLInfoByList(InputStream in){Log.d("ParsePersonXML","not execution yet");ParserPerson parserPerson = new ParserPerson();SAXParserFactory localSAXParseFaxtory = SAXParserFactory.newInstance();try{SAXParser saxParser = localSAXParseFaxtory.newSAXParser();saxParser.parse(in, parserPerson);}catch(ParserConfigurationException e){e.printStackTrace();}catch(SAXException e){e.printStackTrace();}catch(IOException e){e.printStackTrace();}Log.d("ParsePersonXML","ParsePersonXML:"+parserPerson.getPersons().size());return parserPerson.getPersons();}private class ParserPerson extends DefaultHandler{private StringBuilder builder;//存储当前正在解析结点的标签名。private String currentElement;private Person person;private ArrayList<Object> personList;/** * 开始XML文档的回调函数,在文档开头时调用,其中可做预处理工作 */@Overridepublic void startDocument() throws SAXException {// TODO Auto-generated method stubthis.personList = new ArrayList<Object>();super.startDocument();}/** * 元素开始标签的回调函数,处理元素开始事件。 * uri: 命名空间。 * localName: 不带命名空间前缀的标签名。 * qName:带命名空间前缀的标签名。 * attributes: 属性名和值对的集合。 */@Overridepublic void startElement(String uri, String localName, String qName,Attributes attributes) throws SAXException {// TODO Auto-generated method stubthis.currentElement = localName;if("Person".equals(this.currentElement)){this.person = new Person();this.personList.add(this.person);//获取属性值person.setmAccount(attributes.getValue("account"));}}/** * 结束XML文档的回调函数,在文档结束时调用,其中可做一些回收工作。 */@Overridepublic void endElement(String uri, String localName, String qName)throws SAXException {// TODO Auto-generated method stub//Person标签解析完毕,将其存储到集合中,并清空person,准备下一个Person的解析。this.currentElement = null;}/** * 结束XML文档的回调函数,文档结束时调用,其中可做一些回收工作。 */@Overridepublic void endDocument() throws SAXException {// TODO Auto-generated method stubsuper.endDocument();}/** * 处理元素的字符内容,从参数中可以获得内容 * ch: 文件的字符串内容. * start: 读到的字符串在这个数组中的起始位置. * length: 读到的字符串在这个数组中的长度. * 使用new String(ch,start,length)就可以获取内容。 */@Overridepublic void characters(char[] ch, int start, int length)throws SAXException {// TODO Auto-generated method stubif(this.currentElement == null)return;//根据标签名,读取相应的值。String str = new String(ch,0,length);if("name".equals(this.currentElement)){person.setmName(str);}else if("email".equals(this.currentElement)){person.setmEmail(str);}else if("tel".equals(this.currentElement)){person.setmTel(str);}else if("boss".equals(this.currentElement)){person.setmBoss(str);}else if("newProject".equals(this.currentElement)){person.setmProprietor(str);}else if("superPower".equals(this.currentElement)){person.setmCompetence(str);}}public ArrayList<Object> getPersons(){Log.d("ParsePersonXML","ParserPerson:"+this.personList.size());return this.personList;}}}

ContextClient类的实现:

import java.io.InputStream;import java.util.ArrayList;public class ContextClient {private ParseXML mParseXML;public ContextClient(ParseXML parseXML){this.mParseXML = parseXML;}public ArrayList<Object> getParseInfo(InputStream in){return this.mParseXML.getXMLInfoByList(in);}}

MainActivity类的实现:

import java.io.IOException;import java.io.InputStream;import java.util.ArrayList;import android.app.Activity;import android.os.Bundle;import android.widget.TextView;public class MainActivity extends Activity {    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        TextView tv = (TextView)findViewById(R.id.tv1);        try{        ParsePersonXML parsePerson = new ParsePersonXML();        ContextClient client = new ContextClient(parsePerson);        InputStream in = getResources().openRawResource(R.raw.person_item);        ArrayList<Object> list = client.getParseInfo(in);        in.close();        for(Object p: list)        {        Person person = (Person)p;        if(person != null)        {        String str1 = ("1".equals(person.getmProprietor()))?"超级权限":"普通权限";        String str2 = ("1".equals(person.getmCompetence()))?"读写项目":"只读项目";        StringBuilder builder = new StringBuilder();        tv.append(builder.toString());        tv.append("\n\r姓名: "+person.getmName()+"\r\n账户:"+person.getmAccount()        + "\n\r电话: "+person.getmTel()+"\n\r邮箱: "+person.getmEmail()        +"\n\r权限:  "+str1+" && "+str2+"\n\r ");        }        }        }catch(IOException e){        e.printStackTrace();        }            }}

下面是效果图:

深入分析android中用SAX解析XML文件并纠错_第2张图片

5、易犯错误纠正

  在继承DefaultHandler实现的ParsePerson事件处理器中,一定要理清解析的逻辑,再进行相应的处理。不然很容易解析结果为空之类的错误。网上有些blog在处理文档开头、结束等回调函数上,都会犯一些错误,这在某些情况下会直接导致解析结果为空,因而手机端不会显示结果为空。根本原因没有掌握好事件处理器的调用机制,解析的逻辑。

1)currentElement存储当前解析的标签,一定要注意初始化和清空的时机。在startElement函数中初始化,个人建议首先初始化然后再判断处理,这样比较符合解析逻辑。好些资料包括某些书里,在startElement中都是先判断处理再初始化。这在某些情况下是能顺利解析出来的,但有些时候却不行,比如只有一个标签<Person account="zhangsan"><Person>,就解析不出数据的。最简单的方法就是不用currentElement,后面会贴出标准代码。

2)分配Person对象和将该对象添加到集合的时机。添加Person对象建议在分配空间时就添加到集合中,或者在endElement函数中添加。不建议在characters函数中添加,因为当前的currentElement不一定是Person。

3)在解析一个完整的<name>value</name>时,characters函数是很有可能会执行多次的。遇到内容中有"\n"或“、“t”就会再次调用该函数执行,利用StringBuilder对象将其追加上去,这样就不会丢失内容。

最后我们可以得知上面的ParsePerson还有些繁琐,甚至可能会丢失部分数据。我参考网上资料,整理出标准的ParsePerson事件处理器。绕了这么一个大圈子,各位看官不要生气,我只是希望您能够真正理解SAX解析,希望下面的代码对大家有用,如下:

private class ParserPerson extends DefaultHandler{private StringBuilder builder = new StringBuilder();private Person person;private ArrayList<Object> personList;/** * 开始XML文档回调函数,文档开头时调用,其中可做预处理工作 *///@Overridepublic void startDocument() throws SAXException {// TODO Auto-generated method stubthis.personList = new ArrayList<Object>();super.startDocument();}/** * 元素开始标签的回调函数 * uri: 命名空间。 * localName: 不带命名空间前缀的标签名。 * qName:带命名空间前缀的标签名。 * attributes: 属性名和值对的集合。 */@Overridepublic void startElement(String uri, String localName, String qName,Attributes attributes) throws SAXException {// TODO Auto-generated method stubif("Person".equals(localName)){this.person = new Person();this.personList.add(this.person);//获取属性值person.setmAccount(attributes.getValue("account"));}this.builder.setLength(0);}/** * 遇到元素结束标签的回调函数, */@Overridepublic void endElement(String uri, String localName, String qName)throws SAXException {// TODO Auto-generated method stub//Person标签解析完毕,将其存储到集合中,并清空person,准备下一个Person的解析。if("name".equals(localName)){person.setmName(this.builder.toString());}else if("email".equals(localName)){person.setmEmail(this.builder.toString());}else if("tel".equals(localName)){person.setmTel(this.builder.toString());}else if("boss".equals(localName)){person.setmBoss(this.builder.toString());}else if("newProject".equals(localName)){person.setmProprietor(this.builder.toString());}else if("superPower".equals(localName)){person.setmCompetence(this.builder.toString()); }}/** * 结束XML文档的回调函数,文档结束时调用,其中可做一些回收工作。 */@Overridepublic void endDocument() throws SAXException {// TODO Auto-generated method stubsuper.endDocument();}/** * 遇到元素值时回调此函数. * ch: 文件的字符串内容. * start: 读到的字符串在这个数组中的起始位置. * length: 读到的字符串在这个数组中的长度. * 使用new String(ch,start,length)就可以获取内容。 */@Overridepublic void characters(char[] ch, int start, int length)throws SAXException {// TODO Auto-generated method stub//不管调用多少次该函数,都不会丢失内容。this.builder.append(ch,start,length);}public ArrayList<Object> getPersons(){Log.d("ParsePersonXML","ParserPerson:"+this.personList.size());return this.personList;}}

在这个代码里面,主要是用了不带命名空间前缀的标签名localName来做相应处理,如果XML含有命名空间前缀,大家自行修改。

下面贴出XML文件person_item

<?xml version="1.0" encoding="UTF-8"?><Persons>    <Person account="test"></Person>    <Person ></Person><Person account="zhangsan">    <name>张三</name>    <email>[email protected]</email>    <tel>18866661111</tel>    <boss>manager</boss>    <newProject>1</newProject>    <superPower>1</superPower></Person><Person account="lisi">    <name>李四</name>    <email>[email protected]</email>    <tel>16688883333</tel>    <boss>manager</boss>    <newProject>0</newProject>    <superPower>0</superPower></Person></Persons>
这是效果图,Person分配对象后也赋予了初始值,且在MainActivity中没进行相应判断处理,因而看起来有些怪异,见谅:



参考1:http://www.cnblogs.com/felix-hua/archive/2012/01/10/2317404.html

参考2:http://blog.csdn.net/feng88724/article/details/7013675




更多相关文章

  1. Android 4.0按键事件以及电源管理流程分析
  2. Android 文档的阅读顺序!
  3. android 软键盘Enter键事件处理
  4. android中的事件传递和处理机制
  5. Android触摸屏事件派发机制详解与源码分析二(ViewGroup篇)
  6. Android中的事件模型
  7. Android触发事件总结(触摸屏事件,手势识别,键盘事件,模拟鼠标/按键事

随机推荐

  1. Android开发——you must set design_wid
  2. Android(安卓)设置应用启动动画
  3. Android(安卓)别样的数据字典解析
  4. Android(安卓)Studio 导入项目 出现安装E
  5. Android(安卓)Studio实现两次返回键退出
  6. Android(安卓)Studio 使用友盟进行多渠道
  7. 从assets文件夹中读取txt文件
  8. 二:android的helloworld和初步认识
  9. Android(安卓)NDK开发(windows)
  10. Android(安卓)之约束布局