Android 解析 XML

Android 解析 XML的方式﹐使用SAX及DOM的方法。
同時以自定的Adapter來顯示資料。

在現代技術中不論使用何種語言﹐解析XML文件資料已是很常見的方法﹐XML比起過去的INI格式更有彈性更能描述豐富的資料﹐且做為一種共同性的文件格式更能讓大家有同樣的規範遵詢﹐不會因為獨樹一格的格式使得使用技巧變得多樣而複雜。

在 Android 中解析XML的方式有三種DOM﹑SAX及PULL﹐這些使用的差異性在網路上已有多篇文章介紹﹐範例也有許多。然而凡事總要自己親自嘗試過才能知其中的奧妙。因此自己自訂了一個XML格式分別使用DOM及SAX方式來讀取做相同的事﹐親自感受當中有何不同。

 

XML檔(Books.xml)

<?xml version="1.0" encoding="UTF-8"?>
<books>
	<book id="P631" name="Android 2.X 應用程式開發實戰" publishers="碁峯" price="580">
		<auther name="林城"/>
	</book>
	<book id="P697" name="深入淺出 Android 遊戲程式開發範例大全" publishers="博碩文化" price="620">
		<auther name="吳亞峰"/>
		<auther name="蘇亞光"/>
	</book> 
	<book id="XP6037" name="Silverlight 4 商業級應用程式開發" publishers="悅知文化" price="590">
		<auther name="Frank LaVigne. Cameron Albert"/>
	</book>
</books>

將Books.xml 這個檔案放在 src 之下﹐如下圖所示

xml_point

 

主畫面 Layout(main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
	<TextView  
    	android:layout_width="fill_parent" 
    	android:layout_height="wrap_content" 
    	android:text="讀取XML資料"/>
	<Button android:layout_width="wrap_content" 
		android:layout_height="wrap_content" 
		android:text="使用SAX" 
		android:id="@+id/saxbtn"/>
	<Button android:layout_width="wrap_content" 
		android:layout_height="wrap_content" 
		android:text="使用DOM剖析" 
		android:id="@+id/dombtn"/>
</LinearLayout>

主畫面很簡單就只有兩個Button﹐分別表示將使用SAX方式與DOM方式來解析XML資料檔。

mail_layout2

接下來將分別使用SAX與DOM的方式讀取Books.xml然後以ListView元件呈現資料﹐而這裏要做的是用不同的技術分別呈現﹐使用的Layout都一樣﹐這裏會建立一個主要呈現的Layout檔booklist.xml

booklist.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent" android:orientation="vertical">
    <TextView android:id="@+id/list_title" 
    	android:layout_width="wrap_content" 
    	android:layout_height="wrap_content" 
    	android:text="書籍列表" 
    	android:layout_gravity="center"/>
    <ListView android:id="@+id/bookListView" 
    	android:layout_width="fill_parent" 
    	android:layout_height="fill_parent"/>
</LinearLayout>

這個layout很簡單﹐只有一個TextView當Title﹐還有一個ListView﹐這個ListView就是要用來顯示解析Books.xml的資料。為了這個ListView﹐需要再建一個layout檔book_data.xml﹐這個book_data.xml用來表示在ListView中的每一筆資料呈現方式。

book_data.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_height="match_parent" 
  android:orientation="vertical" 
  android:layout_width="fill_parent">
    <LinearLayout android:id="@+id/linearLayout1" 
    	android:orientation="horizontal" 
    	android:layout_height="wrap_content" 
    	android:layout_width="fill_parent">
        <TextView android:text="TextView" 
        	android:layout_width="wrap_content" 
        	android:layout_height="wrap_content" 
        	android:id="@+id/bookNameTxt" 
        	android:textColor="@color/yellow"/>
    </LinearLayout>
    <LinearLayout android:id="@+id/linearLayout2" 
    	android:orientation="horizontal" 
    	android:layout_height="wrap_content" 
    	android:layout_width="fill_parent">
        <TextView android:text="TextView" 
        	android:textColor="@color/white" 
        	android:gravity="left" 
        	android:id="@+id/publishersTxt" 
        	android:layout_width="wrap_content" 
        	android:layout_height="wrap_content"></TextView>
        <TextView android:text="TextView" 
        	android:textColor="@color/green" 
        	android:gravity="right" 
        	android:id="@+id/priceTxt" 
        	android:layout_height="wrap_content" 
        	android:layout_width="match_parent"></TextView>
    </LinearLayout>
</LinearLayout>

Layout還是以圖形來看比較容易看得懂﹐在book_data.xml的排版中每一筆資料的呈現方式第一列為書名(黃色的TextView)﹐第二列則有出版社(白色的TextView)及價錢(綠色的TextView)。

book_data_layout

 

根據 Books.xml的資料我們知道至少有id(書籍編號)﹑name(書名)﹑publishers(出版社)﹑price(價錢)...這些屬性值﹐而從我們想要顯示的資料來看至少也要有name(書名)﹑publishers(出版社)﹑price(價錢)這三個欄位﹐我們可以定義一個class檔(Book.java)來表示一本書籍的基本資料。

Book.java

package com.sample.AndroidUseXML;

public class Book {
	public String id;
	public String name;
	public String publishers;
	public String price;
	
	public Book(){}
	
	public Book(String id, String name, String publishers, String price){
		this.id=id;
		this.name=name;
		this.publishers=publishers;
		this.price=price;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getPublishers() {
		return publishers;
	}

	public void setPublishers(String publishers) {
		this.publishers = publishers;
	}

	public String getPrice() {
		return price;
	}

	public void setPrice(String price) {
		this.price = price;
	}
	
}

在這個專案一開始create的Activity是UseXML﹐這裏就是單純的去觸發兩個button的動作而已。

UseXML.java

package com.sample.AndroidUseXML;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class UseXML extends Activity {
    Button saxbtn;
    Button dombtn;
    String list_title;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        findView();
        setListener();
    }
    
    private void findView(){
    	saxbtn=(Button)findViewById(R.id.saxbtn);
    	dombtn=(Button)findViewById(R.id.dombtn);
    }
    
    private void setListener(){
    	saxbtn.setOnClickListener(saxbtnls);
    	dombtn.setOnClickListener(dombtnls);
    }
    
    private Button.OnClickListener saxbtnls=new Button.OnClickListener(){
		@Override
		public void onClick(View arg0) {
			Intent intent=new Intent();
	    	intent.setClass(UseXML.this, UseSaxParse.class);
	    	
	    	//傳遞資料給Activity-------------
	    	Bundle bundle = new Bundle();
	    	list_title="使用SAX解析";
	    	bundle.putString("list_title", list_title);
	    	intent.putExtras(bundle);
	    	
	    	startActivity(intent);
		}
    };
    
    private Button.OnClickListener dombtnls=new Button.OnClickListener(){
		@Override
		public void onClick(View v) {
			Intent intent=new Intent();
	    	intent.setClass(UseXML.this, UseDomParse.class);
	    	
	    	//傳遞資料給Activity-------------
	    	Bundle bundle = new Bundle();
	    	list_title="使用DOM解析";
	    	bundle.putString("list_title", list_title);
	    	intent.putExtras(bundle);
	    	
	    	startActivity(intent);
		}
    };
    

}

DOM篇

由UseXML中第53行﹐我們知道要使用DOM的方式我們呼叫了UseDomParse的Activity

UseDomParse.java

package com.sample.AndroidUseXML;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.ListView;
import android.widget.TextView;

public class UseDomParse extends Activity {
	String TAG="UseDomParse";
	TextView list_title;
	ListView bookList;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.booklist);
		
		findView();
		ShowXMLData();		
	}
	
	private void findView(){
		list_title=(TextView)findViewById(R.id.list_title);
		bookList=(ListView)findViewById(R.id.bookListView);
	}
	
	private void ShowXMLData(){
		Bundle bundle=this.getIntent().getExtras();
		list_title.setText("書籍列表("+bundle.getString("list_title")+")");
		
		List<Book> books=new ArrayList<Book>();
		books=getXMLData();
		
		bookList.setAdapter(new BookAdapter(this,books));
	}

	private List<Book> getXMLData() {
		List<Book> books = new ArrayList<Book>();
		try {
			InputStream inStream = UseDomParse.class.getClassLoader()
					.getResourceAsStream("Books.xml");  //讀取檔案

//			InputStream inStream = UseDomParse.class.getClassLoader()
//			.getResourceAsStream("com/sample/AndroidUseXML/Books2.xml");  //讀取檔案
			
			books = DomParseXML.ReadBookXML(inStream);
		} catch (Exception er) {
			Log.e(TAG, er.getMessage());
		}
		return books;
	}
}

第21行指明了要用booklist.xml的layout。

第32行的ShowXMLData()是用來顯示資料。

第37行的books=getXMLData();是用來取得以DOM解析資料後得到的List物件。

第39行的bookList這一個ListView將使用自定的DataAdapter做為資料來源。

第45行的位置是讀取檔案﹐因為檔案是直接放在src之下。如果檔案是放在com.sample.AndroidUseXML之下﹐那麼就必須加上路徑﹐如48行。

在第48行books = DomParseXML.ReadBookXML(inStream); 這裏是我們主要用DOM去解析方法。

 

DomParseXML.java

package com.sample.AndroidUseXML;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 * 使用Dom讀取Books.xml的資料
 * @author Kevin Chen
 *InputStream可以是網路也可以是文件
 */
public class DomParseXML {
	public static List<Book> ReadBookXML(InputStream inStream) throws Exception{
		List<Book> books=new ArrayList<Book>();
		DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
		DocumentBuilder builder=factory.newDocumentBuilder();
		Document document=builder.parse(inStream);  //以樹狀格式存於記憶體中﹐比較耗記憶體
		Element root=document.getDocumentElement();
		NodeList nodes=root.getElementsByTagName("book");
		
		for(int i=0;i<nodes.getLength();i++){
			Element bookElement=(Element)nodes.item(i);
			Book book=new Book();
			book.setId(bookElement.getAttribute("id"));
			book.setName(bookElement.getAttribute("name"));
			book.setPublishers(bookElement.getAttribute("publishers"));
			book.setPrice(bookElement.getAttribute("price"));
			
			books.add(book);
		}
		
		return books;
	}
}

 

BookAdapter.java

package com.sample.AndroidUseXML;

import java.util.List;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

public class BookAdapter extends BaseAdapter {
	private LayoutInflater inflater;
	List<Book> books=null;
	
	public BookAdapter(Context context,List<Book> books){
		inflater=LayoutInflater.from(context);
		this.books=books;
	}

	@Override
	public int getCount() {
		return books.size();
	}

	@Override
	public Object getItem(int arg0) {
		return arg0;
	}

	@Override
	public long getItemId(int arg0) {
		return arg0;
	}

	@Override
	public View getView(int arg0, View arg1, ViewGroup arg2) {
		ViewHolder viewHolder;
		if(arg1==null){
			arg1=inflater.inflate(R.layout.book_data, null);
			viewHolder=new ViewHolder();
			viewHolder.bookNameTxt=(TextView)arg1.findViewById(R.id.bookNameTxt);
			viewHolder.publishersTxt=(TextView)arg1.findViewById(R.id.publishersTxt);
			viewHolder.priceTxt=(TextView)arg1.findViewById(R.id.priceTxt);
			arg1.setTag(viewHolder);
		}else{
			viewHolder=(ViewHolder)arg1.getTag();
		}
//		String name1="";
//		String publ1="";
//		String price1="";
//		name1=books.get(arg0).name;
//		publ1=books.get(arg0).publishers;
//		price1=books.get(arg0).price;
		
		viewHolder.bookNameTxt.setText(books.get(arg0).name);
		viewHolder.publishersTxt.setText(books.get(arg0).publishers);
		viewHolder.priceTxt.setText("NT."+books.get(arg0).price+" 元");
		
		return arg1;
	}
	
	private class ViewHolder{
		TextView bookNameTxt;
		TextView publishersTxt;
		TextView priceTxt;
	}

}

 

SAX篇

以DOM的方式﹐可以看得出寫的程式並不多﹐如果改以SAX就不大一樣了。

由UseXML中第37行﹐我們知道要使用SAX的方式我們呼叫了UseSaxParse的Activity。

UseSaxParse.java

package com.sample.AndroidUseXML;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.ListView;
import android.widget.TextView;

public class UseSaxParse extends Activity {
	String TAG="UseSAX";
	TextView list_title;
	ListView bookList;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.booklist);
		
		findView();
		ShowXMLData();
	}

	private void findView(){
		list_title=(TextView)findViewById(R.id.list_title);
		bookList=(ListView)findViewById(R.id.bookListView);
	}
	
	private void ShowXMLData(){
		Bundle bundle=this.getIntent().getExtras();
		list_title.setText("書籍列表("+bundle.getString("list_title")+")");
		
		List<Book> books=new ArrayList<Book>();
		books=getXMLData();
		
		bookList.setAdapter(new BookAdapter(this,books));
	}
	
	private List<Book> getXMLData(){
		List<Book> books=new ArrayList<Book>();
		try{
			InputStream inStream=UseSaxParse.class.getClassLoader().getResourceAsStream("Books.xml");
			books = SAXParseXML.readXML(inStream);
		}catch(Exception er){
			Log.e(TAG, er.getMessage());
		}
		
		return books;
	}
}

第46行的SAXParseXML

SAXParseXML.java

package com.sample.AndroidUseXML;

import java.io.InputStream;
import java.util.List;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

public class SAXParseXML {
	public static List<Book> readXML(InputStream inStream) throws Exception{
		SAXParserFactory factory=SAXParserFactory.newInstance();
		SAXParser saxParse=factory.newSAXParser();  //建立解析器
		
		SAXContentHandler handler=new SAXContentHandler();
		saxParse.parse(inStream, handler);
		inStream.close();
		
		return handler.getBooks();
	}
}

第14行的SAXContentHandler是關鍵﹐必須建立一個class繼承自DefaultHandler﹐並且在這當中去實作以下以個方法

startDocument()

characters(char[] ch, int start, int length)

startElement(String uri, String localName, String qName, Attributes attributes)

public void endElement(String uri, String localName, String qName)

 

SAXContentHandler.java

package com.sample.AndroidUseXML;

import java.util.ArrayList;
import java.util.List;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class SAXContentHandler extends DefaultHandler {
	private List<Book> books;
	private Book book;
	private String preTag;
	
	public List<Book> getBooks(){
		return books;
	}

	/**
	 * 接收文件的開始
	 */
	@Override
	public void startDocument() throws SAXException {
		books = new ArrayList<Book>();
	}

	/**
	 * 接收字元資料的通知
	 */
	@Override
	public void characters(char[] ch, int start, int length)
			throws SAXException {

	}

	/**
	 * 接收元素開始的通知
	 */
	@Override
	public void startElement(String uri, String localName, String qName,
			Attributes attributes) throws SAXException {
		if("book".equals(localName)){
			book=new Book();
			book.setId(attributes.getValue("","id"));
			book.setName(attributes.getValue("", "name"));
			book.setPublishers(attributes.getValue("publishers"));
			book.setPrice(attributes.getValue("price"));
		}
		preTag=localName;
	}

	/**
	 * 接收元素結尾的通知
	 */
	@Override
	public void endElement(String uri, String localName, String qName)
			throws SAXException {
		if("book".equals(localName) && book!=null){
			books.add(book);
			book=null;
		}
		preTag=null;
	}
	
	
}

實際的執行結果

result