Android编程解析XML方法详解(SAX,DOM与PULL)

时间:2022-11-21 22:40:23

本文实例讲述了android编程解析xml方法。分享给大家供大家参考,具体如下:

xml在各种开发中都广泛应用,android也不例外。作为承载数据的一个重要角色,如何读写xml成为android开发中一项重要的技能。今天就由我向大家介绍一下在android平台下几种常见的xml解析和创建的方法。

在android中,常见的xml解析器分别为sax解析器、dom解析器和pull解析器,下面,我将一一向大家详细介绍。

sax解析器:

sax(simple api for xml)解析器是一种基于事件的解析器,它的核心是事件处理模式,主要是围绕着事件源以及事件处理器来工作的。当事件源产生事件后,调用事件处理器相应的处理方法,一个事件就可以得到处理。在事件源调用事件处理器中特定方法的时候,还要传递给事件处理器相应事件的状态信息,这样事件处理器才能够根据提供的事件信息来决定自己的行为。

sax解析器的优点是解析速度快,占用内存少。非常适合在android移动设备中使用。

dom解析器:

dom是基于树形结构的的节点或信息片段的集合,允许开发人员使用dom api遍历xml树、检索所需数据。分析该结构通常需要加载整个文档和构造树形结构,然后才可以检索和更新节点信息。

由于dom在内存中以树形结构存放,因此检索和更新效率会更高。但是对于特别大的文档,解析和加载整个文档将会很耗资源。

pull解析器:

pull解析器的运行方式和sax类似,都是基于事件的模式。不同的是,在pull解析过程中,我们需要自己获取产生的事件然后做相应的操作,而不像sax那样由处理器触发一种事件的方法,执行我们的代码。pull解析器小巧轻便,解析速度快,简单易用,非常适合在android移动设备中使用,android系统内部在解析各种xml时也是用pull解析器。

以上三种解析器,都是非常实用的解析器,我将会一一介绍。我们将会使用这三种解析技术完成一项共同的任务。

我们新建一个项目,项目结构如下:

Android编程解析XML方法详解(SAX,DOM与PULL)

我会在项目的assets目录中放置一个xml文档books.xml,内容如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="utf-8"?>
<books>
  <book>
    <id>1001</id>
    <name>thinking in java</name>
    <price>80.00</price>
  </book>
  <book>
    <id>1002</id>
    <name>core java</name>
    <price>90.00</price>
  </book>
  <book>
    <id>1003</id>
    <name>hello, andriod</name>
    <price>100.00</price>
  </book>
</books>

然后我们分别使用以上三种解析技术解析文档,得到一个list<book>的对象,先来看一下book.java的代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.scott.xml.model;
public class book {
  private int id;
  private string name;
  private float price;
  public int getid() {
    return id;
  }
  public void setid(int id) {
    this.id = id;
  }
  public string getname() {
    return name;
  }
  public void setname(string name) {
    this.name = name;
  }
  public float getprice() {
    return price;
  }
  public void setprice(float price) {
    this.price = price;
  }
  @override
  public string tostring() {
    return "id:" + id + ", name:" + name + ", price:" + price;
  }
}

最后,我们还要把这个集合对象中的数据生成一个新的xml文档,如图:

Android编程解析XML方法详解(SAX,DOM与PULL)

生成的xml结构跟原始文档略有不同,是下面这种格式:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<books>
 <book id="1001">
  <name>thinking in java</name>
  <price>80.0</price>
 </book>
 <book id="1002">
  <name>core java</name>
  <price>90.0</price>
 </book>
 <book id="1003">
  <name>hello, andriod</name>
  <price>100.0</price>
 </book>
</books>

接下来,就该介绍操作过程了,我们先为解析器定义一个bookparser接口,每种类型的解析器需要实现此接口。bookparser.java代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.scott.xml.parser;
import java.io.inputstream;
import java.util.list;
import com.scott.xml.model.book;
public interface bookparser {
  /**
   * 解析输入流 得到book对象集合
   * @param is
   * @return
   * @throws exception
   */
  public list<book> parse(inputstream is) throws exception;
  /**
   * 序列化book对象集合 得到xml形式的字符串
   * @param books
   * @return
   * @throws exception
   */
  public string serialize(list<book> books) throws exception;
}

好了,我们就该一个一个的实现该接口,完成我们的解析过程。

使用sax解析器:

saxbookparser.java代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package com.scott.xml.parser;
import java.io.inputstream;
import java.io.stringwriter;
import java.util.arraylist;
import java.util.list;
import javax.xml.parsers.saxparser;
import javax.xml.parsers.saxparserfactory;
import javax.xml.transform.outputkeys;
import javax.xml.transform.result;
import javax.xml.transform.transformer;
import javax.xml.transform.transformerfactory;
import javax.xml.transform.sax.saxtransformerfactory;
import javax.xml.transform.sax.transformerhandler;
import javax.xml.transform.stream.streamresult;
import org.xml.sax.attributes;
import org.xml.sax.saxexception;
import org.xml.sax.helpers.attributesimpl;
import org.xml.sax.helpers.defaulthandler;
import com.scott.xml.model.book;
public class saxbookparser implements bookparser {
  @override
  public list<book> parse(inputstream is) throws exception {
    saxparserfactory factory = saxparserfactory.newinstance(); //取得saxparserfactory实例
    saxparser parser = factory.newsaxparser(); //从factory获取saxparser实例
    myhandler handler = new myhandler(); //实例化自定义handler
    parser.parse(is, handler); //根据自定义handler规则解析输入流
    return handler.getbooks();
  }
  @override
  public string serialize(list<book> books) throws exception {
    saxtransformerfactory factory = (saxtransformerfactory) transformerfactory.newinstance();//取得saxtransformerfactory实例
    transformerhandler handler = factory.newtransformerhandler();      //从factory获取transformerhandler实例
    transformer transformer = handler.gettransformer();           //从handler获取transformer实例
    transformer.setoutputproperty(outputkeys.encoding, "utf-8");      // 设置输出采用的编码方式
    transformer.setoutputproperty(outputkeys.indent, "yes");        // 是否自动添加额外的空白
    transformer.setoutputproperty(outputkeys.omit_xml_declaration, "no");  // 是否忽略xml声明
    stringwriter writer = new stringwriter();
    result result = new streamresult(writer);
    handler.setresult(result);
    string uri = ""//代表命名空间的uri 当uri无值时 须置为空字符串
    string localname = ""; //命名空间的本地名称(不包含前缀) 当没有进行命名空间处理时 须置为空字符串
    handler.startdocument();
    handler.startelement(uri, localname, "books", null);
    attributesimpl attrs = new attributesimpl();  //负责存放元素的属性信息
    char[] ch = null;
    for (book book : books) {
      attrs.clear(); //清空属性列表
      attrs.addattribute(uri, localname, "id", "string", string.valueof(book.getid()));//添加一个名为id的属性(type影响不大,这里设为string)
      handler.startelement(uri, localname, "book", attrs);  //开始一个book元素 关联上面设定的id属性
      handler.startelement(uri, localname, "name", null); //开始一个name元素 没有属性
      ch = string.valueof(book.getname()).tochararray();
      handler.characters(ch, 0, ch.length);  //设置name元素的文本节点
      handler.endelement(uri, localname, "name");
      handler.startelement(uri, localname, "price", null);//开始一个price元素 没有属性
      ch = string.valueof(book.getprice()).tochararray();
      handler.characters(ch, 0, ch.length);  //设置price元素的文本节点
      handler.endelement(uri, localname, "price");
      handler.endelement(uri, localname, "book");
    }
    handler.endelement(uri, localname, "books");
    handler.enddocument();
    return writer.tostring();
  }
  //需要重写defaulthandler的方法
  private class myhandler extends defaulthandler {
    private list<book> books;
    private book book;
    private stringbuilder builder;
    //返回解析后得到的book对象集合
    public list<book> getbooks() {
      return books;
    }
    @override
    public void startdocument() throws saxexception {
      super.startdocument();
      books = new arraylist<book>();
      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("book")) {
        book = new book();
      }
      builder.setlength(0);  //将字符长度设置为0 以便重新开始读取元素内的字符节点
    }
    @override
    public void characters(char[] ch, int start, int length) throws saxexception {
      super.characters(ch, start, length);
      builder.append(ch, start, length); //将读取的字符数组追加到builder中
    }
    @override
    public void endelement(string uri, string localname, string qname) throws saxexception {
      super.endelement(uri, localname, qname);
      if (localname.equals("id")) {
        book.setid(integer.parseint(builder.tostring()));
      } else if (localname.equals("name")) {
        book.setname(builder.tostring());
      } else if (localname.equals("price")) {
        book.setprice(float.parsefloat(builder.tostring()));
      } else if (localname.equals("book")) {
        books.add(book);
      }
    }
  }
}

代码中,我们定义了自己的事件处理逻辑,重写了defaulthandler的几个重要的事件方法。下面我为大家着重介绍一下defaulthandler的相关知识。defaulthandler是一个事件处理器,可以接收解析器报告的所有事件,处理所发现的数据。它实现了entityresolver接口、dtdhandler接口、errorhandler接口和contenthandler接口。这几个接口代表不同类型的事件处理器。我们着重介绍一下contenthandler接口。结构如图:

Android编程解析XML方法详解(SAX,DOM与PULL)

这几个比较重要的方法已被我用红线标注,defaulthandler实现了这些方法,但在方法体内没有做任何事情,因此我们在使用时必须覆写相关的方法。最重要的是startelement方法、characters方法和endelement方法。当执行文档时遇到起始节点,startelement方法将会被调用,我们可以获取起始节点相关信息;然后characters方法被调用,我们可以获取节点内的文本信息;最后endelement方法被调用,我们可以做收尾的相关操作。

最后,我们需要调用sax解析程序,这个步骤在mainactivity中完成:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.scott.xml;
import java.io.fileoutputstream;
import java.io.inputstream;
import java.util.list;
import android.app.activity;
import android.content.context;
import android.os.bundle;
import android.util.log;
import android.view.view;
import android.widget.button;
import com.scott.xml.model.book;
import com.scott.xml.parser.bookparser;
import com.scott.xml.parser.saxbookparser;
public class mainactivity extends activity {
  private static final string tag = "xml";
  private bookparser parser;
  private list<book> books;
  @override
  public void oncreate(bundle savedinstancestate) {
    super.oncreate(savedinstancestate);
    setcontentview(r.layout.main);
    button readbtn = (button) findviewbyid(r.id.readbtn);
    button writebtn = (button) findviewbyid(r.id.writebtn);
    readbtn.setonclicklistener(new view.onclicklistener() {
      @override
      public void onclick(view v) {
        try {
          inputstream is = getassets().open("books.xml");
          parser = new saxbookparser(); //创建saxbookparser实例
          books = parser.parse(is); //解析输入流
          for (book book : books) {
            log.i(tag, book.tostring());
          }
        } catch (exception e) {
          log.e(tag, e.getmessage());
        }
      }
    });
    writebtn.setonclicklistener(new view.onclicklistener() {
      @override
      public void onclick(view v) {
        try {
          string xml = parser.serialize(books); //序列化
          fileoutputstream fos = openfileoutput("books.xml", context.mode_private);
          fos.write(xml.getbytes("utf-8"));
        } catch (exception e) {
          log.e(tag, e.getmessage());
        }
      }
    });
  }
}

界面就两个按钮,顺便给大家贴上:

Android编程解析XML方法详解(SAX,DOM与PULL)

点击“readxml”按钮,将会调用sax解析器解析文档,并在日志台打印相关信息:

Android编程解析XML方法详解(SAX,DOM与PULL)

然后再点击“writexml”按钮,将会在该应用包下的files目录生成一个books.xml文件:

Android编程解析XML方法详解(SAX,DOM与PULL)

使用dom解析器:

dombookparser.java代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package com.scott.xml.parser;
import java.io.inputstream;
import java.io.stringwriter;
import java.util.arraylist;
import java.util.list;
import javax.xml.parsers.documentbuilder;
import javax.xml.parsers.documentbuilderfactory;
import javax.xml.transform.outputkeys;
import javax.xml.transform.result;
import javax.xml.transform.source;
import javax.xml.transform.transformer;
import javax.xml.transform.transformerfactory;
import javax.xml.transform.dom.domsource;
import javax.xml.transform.stream.streamresult;
import org.w3c.dom.document;
import org.w3c.dom.element;
import org.w3c.dom.node;
import org.w3c.dom.nodelist;
import com.scott.xml.model.book;
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();
  }
}

然后再mainactivity中只需改一个地方:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
readbtn.setonclicklistener(new view.onclicklistener() {
@override
public void onclick(view v) {
  try {
    inputstream is = getassets().open("books.xml");
 //       parser = new saxbookparser();
    parser = new dombookparser();
    books = parser.parse(is);
    for (book book : books) {
      log.i(tag, book.tostring());
    }
  } catch (exception e) {
    log.e(tag, e.getmessage());
  }
}
);

执行结果是一样的。

使用pull解析器:

pullbookparser.java代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package com.scott.xml.parser;
import java.io.inputstream;
import java.io.stringwriter;
import java.util.arraylist;
import java.util.list;
import org.xmlpull.v1.xmlpullparser;
import org.xmlpull.v1.xmlserializer;
import android.util.xml;
import com.scott.xml.model.book;
public class pullbookparser implements bookparser {
  @override
  public list<book> parse(inputstream is) throws exception {
    list<book> books = null;
    book book = null;
//   xmlpullparserfactory factory = xmlpullparserfactory.newinstance();
//   xmlpullparser parser = factory.newpullparser();
    xmlpullparser parser = xml.newpullparser(); //由android.util.xml创建一个xmlpullparser实例
    parser.setinput(is, "utf-8");        //设置输入流 并指明编码方式
    int eventtype = parser.geteventtype();
    while (eventtype != xmlpullparser.end_document) {
      switch (eventtype) {
      case xmlpullparser.start_document:
        books = new arraylist<book>();
        break;
      case xmlpullparser.start_tag:
        if (parser.getname().equals("book")) {
          book = new book();
        } else if (parser.getname().equals("id")) {
          eventtype = parser.next();
          book.setid(integer.parseint(parser.gettext()));
        } else if (parser.getname().equals("name")) {
          eventtype = parser.next();
          book.setname(parser.gettext());
        } else if (parser.getname().equals("price")) {
          eventtype = parser.next();
          book.setprice(float.parsefloat(parser.gettext()));
        }
        break;
      case xmlpullparser.end_tag:
        if (parser.getname().equals("book")) {
          books.add(book);
          book = null;
        }
        break;
      }
      eventtype = parser.next();
    }
    return books;
  }
  @override
  public string serialize(list<book> books) throws exception {
//   xmlpullparserfactory factory = xmlpullparserfactory.newinstance();
//   xmlserializer serializer = factory.newserializer();
    xmlserializer serializer = xml.newserializer(); //由android.util.xml创建一个xmlserializer实例
    stringwriter writer = new stringwriter();
    serializer.setoutput(writer);  //设置输出方向为writer
    serializer.startdocument("utf-8", true);
    serializer.starttag("", "books");
    for (book book : books) {
      serializer.starttag("", "book");
      serializer.attribute("", "id", book.getid() + "");
      serializer.starttag("", "name");
      serializer.text(book.getname());
      serializer.endtag("", "name");
      serializer.starttag("", "price");
      serializer.text(book.getprice() + "");
      serializer.endtag("", "price");
      serializer.endtag("", "book");
    }
    serializer.endtag("", "books");
    serializer.enddocument();
    return writer.tostring();
  }
}

然后再对mainactivity做以下更改:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
readbtn.setonclicklistener(new view.onclicklistener() {
  @override
  public void onclick(view v) {
    try {
      inputstream is = getassets().open("books.xml");
//         parser = new saxbookparser();
//         parser = new dombookparser();
      parser = new pullbookparser();
      books = parser.parse(is);
      for (book book : books) {
        log.i(tag, book.tostring());
      }
    } catch (exception e) {
      log.e(tag, e.getmessage());
    }
  }
});

和其他两个执行结果都一样。

对于这三种解析器各有优点,我个人比较倾向于pull解析器,因为sax解析器操作起来太笨重,dom不适合文档较大,内存较小的场景,唯有pull轻巧灵活,速度快,占用内存小,使用非常顺手。读者也可以根据自己的喜好选择相应的解析技术。

希望本文所述对大家android程序设计有所帮助。