相信各位android開發者,對SAX已經並不陌生了,SAX(Simple API for XML),是一個使用非常廣泛的XML解析標准,通常使用Handler模式來處理XML文檔,這種處理模式和我們平常習慣的理解方式很不同,身邊也經常有一些朋友在剛接觸SAX的時候會覺得理解起來有些困難。其實SAX並不復雜,只不過是換了一種思維方式,正如它的名字所表示的,為了讓我們以更簡單的方式來處理XML文檔,下面我們就開始吧。
我們通常的理解方式是,我們給出一個輸入(比如xml文檔的地址),然後程序返回給我們數據(比如解析後的xml文檔結構),我們在返回給我們的結果中進行相應的操作,而SAX以一種更簡單的方式來處理XML文檔的解析,也就是處理器模式,一個使用SAX的簡單示例:
- SAXParserFactory spf = SAXParserFactory.newInstance();
- SAXParser sp = spf.newSAXParser();
- XMLReader reader = sp.getXMLReader();
- reader.setContentHandler(myHandler);
- reader.parse(new InputSource(new URL(url).openStream()));
復制代碼正如上面的代碼,我們使用一系列工廠方法生成了一個XMLReader對象,隨後,最關鍵的一行就是reader.setContentHandler,這裡為這個reader設置了一個處理器,這個處理器的具體內容是要我們來完成的,稍後會詳細介紹,最後調用parse方法完成文檔的解析。這是SAX的一個基本流程。
下面我們來詳細介紹一下處理器,SAX處理器使用的是一種和我們平時的理解方式不太一樣的處理形式,是在遍歷文檔的同時,讓我們來進行文檔的處理。
用一個實際的例子來解釋更為方便,假如有下面這樣一個XML文檔:
- <student>
- <name>張三</name>
- <age>22</age>
- <sn>1001</sn>
- </student>
- <student>
- <name>李四</name>
- <age>21</age>
- <sn>1002</sn>
- </student>
復制代碼使用SAX的時候,解析器會對XML文檔進行深度優先遍歷,在遍歷的時候,會根據條件調用處理器中的方法,如上面的XML文檔,首先會遍歷到第一個student的起始節點,這時我們可以在處理器中進行一些需要的處理,隨後會分別遍歷name,age,sn起始節點和結束節點,以此類推,這樣說起來可能還不夠直觀,下面我們就來看看一個處理器的基本結構
- public class MyHandler extends DefaultHandler {
- public void startElement(String uri, String localName, String qName,
- }
- public void endElement(String uri, String localName, String qName)
- throws SAXException {
- }
- public void characters(char[] ch, int start, int length)
- throws SAXException {
- }
- }
如上面的代碼,這裡有幾個比較重要的方法,startElement是進入到起始節點的時候會調用的方法,例如上面的XML文件,進入到<student>節點時,就會調用startElement方法。
endElement方法,在結束一個節點的時候會調用,例如進入到</student>節點時,該方法會被調用。
characters方法,在進入XML節點的文本節點(TextNode)時會被調用,例如<name>張三</name>,在便利到‘張三’這個文本節點的時候,這個方法會被調用。
另外還有兩個回調方法,分別為startDocument,endDocument,顧名思義,這兩個方法為進入文檔和離開文檔時要調用的方法。
下面我們就來自己寫一個處理器來解析上面的XML文檔。首先我們需要將每個節點封裝成一個實體對象:
- public class Student {
- private String name;
- private int age;
- private String sn;
- 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 getSn() {
- return sn;
- }
- public void setSn(String sn) {
- this.sn = sn;
- }
- }
下面再來完成處理器的代碼:
- public class MyHandler extends DefaultHandler {
- private List<Student> studentList;
- private boolean inStudent = false;
- private boolean studentName = false;
- private boolean studentAge = false;
- private boolean studentSN = false;
- private Student curStudent ;
- public MyHandler() {
- studentList = new ArrayList<Student>();
- }
- @Override
- public void startElement(String uri, String localName, String qName,
- Attributes attributes) throws SAXException {
- String tagName = localName.length() != 0 ? localName : qName;
- tagName = tagName.toLowerCase().trim();
- if(tagName.equals("student")) {
- inStudent = true;
- curStudent = new Student();
- }
- if(inStudent) {
- if(tagName.equals("name")) {
- studentName = true;
- }else if(tagName.equals("age")) {
- studentAge = true;
- }else if(tagName.equals("sn")) {
- studentSN = true;
- }
- }
- }
- @Override
- public void endElement(String uri, String localName, String qName)
- throws SAXException {
- String tagName = localName.length() != 0 ? localName : qName;
- tagName = tagName.toLowerCase().trim();
- if(tagName.equals("student")) {
- inStudent = true;
- studentList.add(curStudent);
- }
- if(inStudent) {
- if(tagName.equals("name")) {
- studentName = false;
- }else if(tagName.equals("age")) {
- studentAge = false;
- }else if(tagName.equals("sn")) {
- studentSN = false;
- }
- }
- }
- @Override
- public void characters(char[] ch, int start, int length)
- throws SAXException {
- if(studentName) {
- curStudent.setName(curStudent.getName() + new String(ch,start,length));
- }else if (studentAge) {
- curStudent.setAge(Integer.parseInt(new String(ch,start,length)));
- }else if(studentSN) {
- curStudent.setSn(curStudent.getSn() + new String(ch, start, length));
- }
- }
- }
如上面的代碼,我們使用了一系列的布爾標志變量來保存文檔的遍歷狀態,先從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>張 三</name>,這裡相當於包含了兩個文本節點,如果不使用連接的方式而采用直接設置的方式,那麼我們最終只能得到最後一次設置的值,因為前面設置的被覆蓋了。那麼我們最終取得到的名字就是‘三’了。
這個處理器的核心分功能就算完成了,下面我們還需要增加一個方法,用來返回處理後的內容:
- public List<Student> getStudentList() {
- return studentList;
- }
完成了處理器之後,我們就可以用剛開始介紹的方法來解析XML文檔了:
- SAXParserFactory spf = SAXParserFactory.newInstance();
- SAXParser sp = spf.newSAXParser();
- XMLReader reader = sp.getXMLReader();
- List<Student> list;
- reader.setContentHandler(myHandler);
- reader.parse(new InputSource(new URL(url).openStream()));
- list = myHandler.getStudentList();
可以看到,解析完XML文檔之後,我們就可以用處理器重的getStudentList方法取得解析後的數據了。
最後總結一下,SAX並不復雜,只要理解了它的思維方式,我們就可以游刃有余,使它成為我們開發的利器,這篇文章向大家介紹了SAX的一些基本知識,希望能起到一個拋磚引玉的作用,大家能夠使用它來創造出更多好的應用,當然可能有一些地方解釋的還不是十分完美,如果有一些不好理解的地方,還望大家指出。:lol
另外下面是SAX的一個官方網站,裡面有一些介紹和代碼示例,英文不錯的童鞋可以來這裡參考一下:loveliness:
http://www.saxproject.org/