eoe 移动开发者论坛

 找回密码
 加入eoe

QQ登录

只需一步,快速开始

查看: 8243|回复: 49
收起左侧

Android开发必备武器,处理XML的利器--SAX快速上手

    [复制链接]

该用户从未签到

24

主题

97

帖子

288

e币
发表于 2010-9-11 23:01:51 | 显示全部楼层 |阅读模式

没有eoe的账号,级别还太低,出门如何吹牛逼?

您需要 登录 才可以下载或查看,没有帐号?加入eoe

x
相信各位Android开发者,对SAX已经并不陌生了,SAX(Simple API for XML),是一个使用非常广泛的XML解析标准,通常使用Handler模式来处理XML文档,这种处理模式和我们平常习惯的理解方式很不同,身边也经常有一些朋友在刚接触SAX的时候会觉得理解起来有些困难。其实SAX并不复杂,只不过是换了一种思维方式,正如它的名字所表示的,为了让我们以更简单的方式来处理XML文档,下面我们就开始吧。

      我们通常的理解方式是,我们给出一个输入(比如xml文档的地址),然后程序返回给我们数据(比如解析后的xml文档结构),我们在返回给我们的结果中进行相应的操作,而SAX以一种更简单的方式来处理XML文档的解析,也就是处理器模式,一个使用SAX的简单示例:



  1.   SAXParserFactory spf = SAXParserFactory.newInstance();
  2.   SAXParser sp = spf.newSAXParser();
  3.   XMLReader reader = sp.getXMLReader();

  4.   
  5.   reader.setContentHandler(myHandler);            
  6.   reader.parse(new InputSource(new URL(url).openStream()));
复制代码


     正如上面的代码,我们使用一系列工厂方法生成了一个XMLReader对象,随后,最关键的一行就是reader.setContentHandler,这里为这个reader设置了一个处理器,这个处理器的具体内容是要我们来完成的,稍后会详细介绍,最后调用parse方法完成文档的解析。这是SAX的一个基本流程。

     下面我们来详细介绍一下处理器,SAX处理器使用的是一种和我们平时的理解方式不太一样的处理形式,是在遍历文档的同时,让我们来进行文档的处理。
     用一个实际的例子来解释更为方便,假如有下面这样一个XML文档:


  1. <student>
  2.   <name>张三</name>
  3.   <age>22</age>
  4.   <sn>1001</sn>
  5. </student>
  6. <student>
  7.   <name>李四</name>
  8.   <age>21</age>
  9.   <sn>1002</sn>
  10. </student>
复制代码


      使用SAX的时候,解析器会对XML文档进行深度优先遍历,在遍历的时候,会根据条件调用处理器中的方法,如上面的XML文档,首先会遍历到第一个student的起始节点,这时我们可以在处理器中进行一些需要的处理,随后会分别遍历name,age,sn起始节点和结束节点,以此类推,这样说起来可能还不够直观,下面我们就来看看一个处理器的基本结构


  1. public class MyHandler extends DefaultHandler {
  2.    
  3.     public void startElement(String uri, String localName, String qName,   
  4.     }
  5.    
  6.     public void endElement(String uri, String localName, String qName)
  7.             throws SAXException {   
  8.     }
  9.    
  10.     public void characters(char[] ch, int start, int length)
  11.             throws SAXException {
  12.     }  
  13. }
复制代码


      如上面的代码,这里有几个比较重要的方法,startElement是进入到起始节点的时候会调用的方法,例如上面的xml文件,进入到<student>节点时,就会调用startElement方法。
     endElement方法,在结束一个节点的时候会调用,例如进入到</student>节点时,该方法会被调用。
     characters方法,在进入XML节点的文本节点(TextNode)时会被调用,例如<name>张三</name>,在便利到‘张三’这个文本节点的时候,这个方法会被调用。

     另外还有两个回调方法,分别为startDocument,endDocument,顾名思义,这两个方法为进入文档和离开文档时要调用的方法。


     下面我们就来自己写一个处理器来解析上面的XML文档。首先我们需要将每个节点封装成一个实体对象:


  1. public class Student {
  2.     private String name;
  3.    
  4.     private int age;
  5.    
  6.     private String sn;
  7.     public String getName() {
  8.         return name;
  9.     }
  10.     public void setName(String name) {
  11.         this.name = name;
  12.     }
  13.     public int getAge() {
  14.         return age;
  15.     }
  16.     public void setAge(int age) {
  17.         this.age = age;
  18.     }
  19.     public String getSn() {
  20.         return sn;
  21.     }
  22.     public void setSn(String sn) {
  23.         this.sn = sn;
  24.     }
  25.    
  26.    
  27. }

复制代码


     下面再来完成处理器的代码:


  1. public class MyHandler extends DefaultHandler {
  2.    
  3.     private List<Student> studentList;
  4.    
  5.     private boolean inStudent = false;
  6.    
  7.     private boolean studentName = false;
  8.    
  9.     private boolean studentAge = false;
  10.    
  11.     private boolean studentSN = false;
  12.    
  13.     private Student curStudent ;
  14.    
  15.     public MyHandler() {
  16.         
  17.         studentList = new ArrayList<Student>();
  18.     }
  19.     @Override
  20.     public void startElement(String uri, String localName, String qName,
  21.             Attributes attributes) throws SAXException {
  22.         
  23.         String tagName = localName.length() != 0 ? localName : qName;
  24.         tagName = tagName.toLowerCase().trim();
  25.         
  26.         if(tagName.equals("student")) {
  27.             inStudent  = true;
  28.             curStudent = new Student();
  29.         }
  30.         
  31.         if(inStudent) {
  32.             
  33.             if(tagName.equals("name")) {
  34.                 studentName = true;
  35.             }else if(tagName.equals("age")) {
  36.                 studentAge = true;
  37.             }else if(tagName.equals("sn")) {
  38.                 studentSN = true;
  39.             }
  40.         }
  41.         
  42.     }
  43.    
  44.     @Override
  45.     public void endElement(String uri, String localName, String qName)
  46.             throws SAXException {
  47.         
  48.         String tagName = localName.length() != 0 ? localName : qName;
  49.         tagName = tagName.toLowerCase().trim();
  50.         
  51.         if(tagName.equals("student")) {
  52.             inStudent  = true;
  53.             studentList.add(curStudent);
  54.         }
  55.         
  56.         if(inStudent) {
  57.             
  58.             if(tagName.equals("name")) {
  59.                 studentName = false;
  60.             }else if(tagName.equals("age")) {
  61.                 studentAge = false;
  62.             }else if(tagName.equals("sn")) {
  63.                 studentSN = false;
  64.             }
  65.         }
  66.     }
  67.    
  68.     @Override
  69.     public void characters(char[] ch, int start, int length)
  70.             throws SAXException {
  71.         
  72.         if(studentName) {
  73.             curStudent.setName(curStudent.getName() + new String(ch,start,length));
  74.         }else if (studentAge) {
  75.             curStudent.setAge(Integer.parseInt(new String(ch,start,length)));
  76.         }else if(studentSN) {
  77.             curStudent.setSn(curStudent.getSn() + new String(ch, start, length));            
  78.         }
  79.     }
  80. }
复制代码


    如上面的代码,我们使用了一系列的布尔标志变量来保存文档的遍历状态,先从startElement说起,当我们进入到student节点的时候,我们将inStudent状态设置为true,表示我们已经处于student节点之中,同时创建了一个student对象,相应地,在endElement方法中,我们遇到student结束的时候,会把这个对象添加到我们的studentList中,并将inStudent状态设置为false。同样的,在startElement方法中判断instudent状态,如果当前已经处于student节点中,并且遍历到name,age或者sn节点时,我们也将相应的标志设置为true。这样在遍历的文本节点的时候就可以在characters方法中通过判断这些标志位来为Student对象设置相应的属性。

    注意到,这里curStudent.setName(curStudent.getName() + new String(ch,start,length)),我们用以前的值和新的值连接起来,而不是直接设置curStudent.setName(new String(ch,start,length))。这是因为在遍历<name>.....</name>这中间的文本节点的时候,有些时候这对标签中的内容可能会被看做多个文本节点,比如包含Html实体的情况下 <name>张&nbsp;三</name>,这里相当于包含了两个文本节点,如果不使用连接的方式而采用直接设置的方式,那么我们最终只能得到最后一次设置的值,因为前面设置的被覆盖了。那么我们最终取得到的名字就是‘三’了。

    这个处理器的核心分功能就算完成了,下面我们还需要增加一个方法,用来返回处理后的内容:


  1.     public List<Student> getStudentList() {
  2.         return studentList;
  3.     }
复制代码


   完成了处理器之后,我们就可以用刚开始介绍的方法来解析XML文档了:



  1.   SAXParserFactory spf = SAXParserFactory.newInstance();
  2.   SAXParser sp = spf.newSAXParser();
  3.   XMLReader reader = sp.getXMLReader();

  4.   List<Student> list;
  5.   reader.setContentHandler(myHandler);            
  6.   reader.parse(new InputSource(new URL(url).openStream()));

  7. list = myHandler.getStudentList();
复制代码


     可以看到,解析完XML文档之后,我们就可以用处理器重的getStudentList方法取得解析后的数据了。

     最后总结一下,SAX并不复杂,只要理解了它的思维方式,我们就可以游刃有余,使它成为我们开发的利器,这篇文章向大家介绍了SAX的一些基本知识,希望能起到一个抛砖引玉的作用,大家能够使用它来创造出更多好的应用,当然可能有一些地方解释的还不是十分完美,如果有一些不好理解的地方,还望大家指出。:lol

     另外下面是SAX的一个官方网站,里面有一些介绍和代码示例,英文不错的童鞋可以来这里参考一下:loveliness:
     http://www.saxproject.org/

评分

参与人数 1e望 +2 e币 +12 收起 理由
cnmahj + 2 + 12 写的很好。

查看全部评分

签到天数: 203 天

连续签到: 1 天

[LV.7]常住居民III

40

主题

1956

帖子

9227

e币
发表于 2010-9-11 23:46:44 | 显示全部楼层
:D:D

该用户从未签到

0

主题

9

帖子

1

e币
发表于 2010-9-12 09:53:31 | 显示全部楼层
sax遇到cdata不规范的,比如说有空格或换行,应该如何来处理呢,楼主有啥高见。

该用户从未签到

2

主题

94

帖子

146

e币
发表于 2010-9-12 11:24:51 | 显示全部楼层
谢谢了!!多谢分享!

该用户从未签到

18

主题

94

帖子

125

e币
发表于 2010-9-18 16:50:09 | 显示全部楼层
都没有代码...の

该用户从未签到

3

主题

33

帖子

55

e币
发表于 2010-9-19 01:10:19 | 显示全部楼层
新手复制了,什么是sax我都不知道,大概以后会用到,谢了楼主

该用户从未签到

0

主题

13

帖子

17

e币
发表于 2010-9-19 17:56:27 | 显示全部楼层
非常好的解释,对我的帮助太大了

该用户从未签到

2

主题

32

帖子

75

e币
发表于 2010-9-29 09:15:03 | 显示全部楼层
学习

该用户从未签到

2

主题

25

帖子

30

e币
发表于 2010-9-29 10:00:35 | 显示全部楼层
回复 6# cowwen


    呵呵 这个是解析xml文件的。算是一种util类吧 ,还有很多jdom、dom4j……

签到天数: 3 天

连续签到: 1 天

[LV.2]偶尔看看I

3

主题

228

帖子

335

e币
发表于 2010-9-29 10:06:11 | 显示全部楼层
看看

该用户从未签到

3

主题

33

帖子

55

e币
发表于 2010-9-29 20:28:24 | 显示全部楼层
回复 9# wujianhai


    哦,谢谢楼主的解答~

该用户从未签到

8

主题

280

帖子

435

e币
发表于 2010-10-22 16:55:19 | 显示全部楼层
很好 正好用的着 就是不明白具体怎么取出每一个节点

该用户从未签到

9

主题

236

帖子

566

e币
发表于 2010-10-28 16:56:18 | 显示全部楼层
是自己写的还是ctrl+v来的啊?代码不全,而且不好用,敢不敢自己运行成功了在贴出来误人子弟

该用户从未签到

24

主题

97

帖子

288

e币
 楼主| 发表于 2010-10-28 17:29:55 | 显示全部楼层
本帖最后由 springfieldx 于 2010-10-28 18:00 编辑

呵呵,我不敢保证写的水平有多高,但我敢保证这些都是我亲自调试过,一个字一个字的写出来的。
有问题欢迎指正~ 不过能否指出问题具体在哪里,你是否理解并亲自运行过上面的代码呢~,我也好能让它更加完善是不是呢。

该用户从未签到

9

主题

236

帖子

566

e币
发表于 2010-10-29 09:17:58 | 显示全部楼层
ok,MyHandler extends DefaultHandler 这个的第3行是不是缺东西?myHandler这个你声明过么?或者说你对它解释过么?我是菜鸟不假,你能否解释一下
*滑动验证:
您需要登录后才可以回帖 登录 | 加入eoe

本版积分规则

推荐阅读
赞助商们

QQ|联系我们|小黑屋|手机版|eoe 移动开发者论坛 ( 京ICP备11018032 京公网安11010802020210  

GMT+8, 2017-11-19 05:34 , Processed in 1.293714 second(s), 46 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表
关闭

扫一扫 关注eoe官方微信