一.基礎知識
在《OPhone XML解析學習--Sax方式》中我們學習了OPhone /Android上使用Java的SAX進行XML解析的方式。而在OPhone/Android平臺上使用SAX解析XML,除了使用Java的API外,還可以使用OPhone/Android SDK帶的API來實現。OPhone/Android SDK中和SAX解析相關的包爲android.sax,在這個包中OPhone/Android提供了都有的SAX API,使用它們可以更加方便的進行SAX方式的XML解析。
當xml文件中在不同的位置處有相同的元素標籤名時,在相應的事件回調處理函數中往往就需要進行判斷處理。比如USGS的xml形式的地震數據爲:
- <?xml version="1.0"?>
- <feed xmlns="http://www.w3.org/2005/Atom" xmlns:georss="http://www.georss.org/georss">
- <updated>2010-09-15T04:41:18Z</updated>
- <title>USGS M2.5+ Earthquakes</title>
- <subtitle>Real-time, worldwide earthquake list for the past day</subtitle>
- <link rel="self" href="http://earthquake.usgs.gov/earthquakes/catalogs/1day-M2.5.xml"/>
- <link href="http://earthquake.usgs.gov/earthquakes/"/>
- <author><name>U.S. Geological Survey</name></author>
- <id>http://earthquake.usgs.gov/</id>
- <icon>/favicon.ico</icon>
- <entry>
- <id>urn:earthquake-usgs-gov:ak:10078833</id>
- <title>M 2.9, Southern Alaska</title>
- <updated>2010-09-15T04:14:03Z</updated>
- <link rel="alternate" type="text/html" href="http://earthquake.usgs.gov/earthquakes/recenteqsww/Quakes/ak10078833.php"/>
- <summary type="html">
- <![CDATA[<img src="http://earthquake.usgs.gov/images/globes/60_-155.jpg" alt="59.909°N 153.124°W" align="left" hspace="20" /><p>Wednesday, September 15, 2010 04:14:03 UTC<br>Tuesday, September 14, 2010 08:14:03 PM at epicenter</p><p><strong>Depth</strong>: 98.90 km (61.45 mi)</p>]]>
- </summary>
- <georss:point>59.9094 -153.1241</georss:point>
- <georss:elev>-98900</georss:elev>
- <category label="Age" term="Past hour"/>
- </entry>
- <entry>
- <!-- 還有entry條目,省略-->
- </entry>
- </feed>
我們看到在<entry>開始前就有<updated>、<title>和<link>等元素標籤,而<entry></entry>元素中也包含這些標籤名,在SAX解析時都會產生相應的事件,但我們實際需要處理的爲<entry></entry>元素中的這些標籤產生的事件,因此我們設置了一個變量
private Boolean startEntryElementFlag = false;
來進行判斷。對以上的xml數據來說,這樣的處理還不會出現問題,但是如果需要解析一個更加複雜的XML文檔,則類似的需要對不同位置處的相同標籤名進行判斷這樣的處理可能會帶來各種各樣的Bug。
而使用android.sax包中的API來進行SAX方式的解析則不會有以上的問題。實際上使用android.sax包還有點結合了我們以後會詳細介紹的DOM方式,首先獲取需要解析部分的根元素,然後使用getChild方法獲取具體的某個子元素,之後就可以爲具體的元素設置事件處理的回調函數,比如爲一個元素分別設置元素開始的事件處理setStartElementListener,元素包含的文本內容結束的事件處理setEndTextElementListener和元素結束的事件處理setEndElementListener。
概括的來說,android.sax包進行XML解析的過程爲用DOM的方式獲取具體位置處的元素,然後爲其設置需要的事件處理函數。具體的實現我們可以看實例開發部分的代碼。
OPhone/Android SDK中提供的和XML相關的還有一個類:android.util.Xml,在該類中提供了比較實用的XML相關的類方法,比如開始解析的parse方法,和直接創建XmlPullParser及XmlSerializer(這兩塊內容以後介紹)的方法等。
下面我們就用上面介紹的OPhone/Android SDK中的SAX方式來實現解析XML形式的USGS地震數據的Demo例子。
二.實例開發
我們要完成的效果圖如下圖1所示:
圖1 ListView列表顯示的地震數據
和上一部分Demo例子的一樣,也是解析完地震數據後用ListView列表的方式顯示每條地震的震級和地名信息。
新建一個OPhone工程OPhoneXMLDemoSaxII。
添加進上一個Demo工程OPhoneXMLDemoSax中的EarthquakeEntry.java文件,如果需要從本地讀取xml數據的話,同時在assets文件夾下添加保存爲xml格式了的USGS地震數據USGS_Earthquake_1M2_5.xml,如果需要聯網讀取的話,在manifest.xml文件中添加權限:
- <uses-permission android:name="android.permission.INTERNET" />
- 並修改res/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"
- >
- <ListView
- android:id="@+id/list"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- />
- </LinearLayout>
接下來就來新建添加一個類AndroidSaxEarthquakeHandler,以OPhone/Android SDK提供的包android.sdk的API來完成解析地震數據的具體邏輯實現,內容如下:
- public class AndroidSaxEarthquakeHandler {
- //xml解析用到的Tag
- private String kRootElementName = "feed";
- private String kEntryElementName = "entry";
- private String kLinkElementName = "link";
- private String kTitleElementName = "title";
- private String kUpdatedElementName = "updated";
- private String kGeoRSSPointElementName = "point";
- private String kGeoRSSElevElementName = "elev";
- static final String ATOM_NAMESPACE = "http://www.w3.org/2005/Atom";
- static final String GEORSS_NAMESPACE = "http://www.georss.org/georss";
- //用於保存xml解析獲取的結果
- private ArrayList<EarthquakeEntry> earthquakeEntryList;
- private EarthquakeEntry earthquakeEntry;
- //解析xml數據
- public ArrayList<EarthquakeEntry> parse(InputStream inStream)
- {
- earthquakeEntryList = new ArrayList<EarthquakeEntry>();
- RootElement root = new RootElement(ATOM_NAMESPACE, kRootElementName);
- Element entry = root.getChild(ATOM_NAMESPACE, kEntryElementName);
- //具體解析xml
- //處理entry標籤
- entry.setStartElementListener(new StartElementListener() {
- @Override
- public void start(Attributes attributes) {
- // TODO Auto-generated method stub
- earthquakeEntry = new EarthquakeEntry();
- }
- });
- entry.setEndElementListener(new EndElementListener() {
- @Override
- public void end() {
- // TODO Auto-generated method stub
- earthquakeEntryList.add(earthquakeEntry);
- }
- });
- //處理title標籤
- entry.getChild(ATOM_NAMESPACE, kTitleElementName).setEndTextElementListener(new EndTextElementListener() {
- @Override
- public void end(String currentData) {
- // TODO Auto-generated method stub
- //提取強度信息
- String magnitudeString = currentData.split(" ")[1];
- int end = magnitudeString.length()-1;
- magnitudeString = magnitudeString.substring(0, end);
- double magnitude = Double.parseDouble(magnitudeString);
- earthquakeEntry.setMagnitude(magnitude);
- //提取位置信息
- String place = currentData.split(",")[1].trim();
- earthquakeEntry.setPlace(place);
- }
- });
- //處理updated標籤
- entry.getChild(ATOM_NAMESPACE, kUpdatedElementName).setEndTextElementListener(new EndTextElementListener() {
- @Override
- public void end(String currentData) {
- // TODO Auto-generated method stub
- //構造更新時間
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
- Date qdate = new GregorianCalendar(0,0,0).getTime();
- try {
- qdate = sdf.parse(currentData);
- } catch (ParseException e) {
- e.printStackTrace();
- }
- earthquakeEntry.setDate(qdate);
- }
- });
- //處理point標籤
- entry.getChild(GEORSS_NAMESPACE, kGeoRSSPointElementName).setEndTextElementListener(new EndTextElementListener() {
- @Override
- public void end(String currentData) {
- // TODO Auto-generated method stub
- //提取經緯度信息
- String[] latLongitude = currentData.split(" ");
- Location location = new Location("dummyGPS");
- location.setLatitude(Double.parseDouble(latLongitude[0]));
- location.setLongitude(Double.parseDouble(latLongitude[1]));
- earthquakeEntry.setLocation(location);
- }
- });
- //處理elev標籤
- entry.getChild(GEORSS_NAMESPACE, kGeoRSSElevElementName).setEndTextElementListener(new EndTextElementListener() {
- @Override
- public void end(String currentData) {
- // TODO Auto-generated method stub
- //提取海拔高度信息
- double evel;
- //因爲USGS數據有可能會輸錯,比如爲"--10000",多了一個"-"號
- try {
- evel = Double.parseDouble(currentData);
- } catch (Exception e) {
- // TODO: handle exception
- e.printStackTrace();
- evel = 0;
- }
- Log.v("Sax_Elev", String.valueOf(evel));
- earthquakeEntry.setElev(evel);
- }
- });
- //處理link標籤
- entry.getChild(ATOM_NAMESPACE, kLinkElementName).setStartElementListener(new StartElementListener() {
- @Override
- public void start(Attributes attributes) {
- // TODO Auto-generated method stub
- //獲取link鏈接
- String webLink = attributes.getValue("href");
- earthquakeEntry.setLink(webLink);
- }
- });
- //調用android.util.Xml開始解析
- try {
- Xml.parse(inStream, Xml.Encoding.UTF_8, root.getContentHandler());
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- return earthquakeEntryList;
- }
- }
開頭處定義瞭解析需要的元素標籤名稱,因爲getChild方法獲取子元素時需要命名空間,因此還新定義了USGS的xml數據中包含的兩個命名空間:
static final String ATOM_NAMESPACE = "http://www.w3.org/2005/Atom";
static final String GEORSS_NAMESPACE = "http://www.georss.org/georss";
在定義的用於解析xml數據的方法中
public ArrayList<EarthquakeEntry> parse(InputStream inStream)
首先獲取xml文檔的根元素:
RootElement root = new RootElement(ATOM_NAMESPACE, kRootElementName);
有了根元素之後,就可以使用類似DOM的getChild方法獲取具體的某個位置處的子元素,並且可以爲具體的子元素註冊事件處理器和在對應的回調函數中實現具體的處理邏輯。
從程序中我們可以看到,我們只爲<entry>元素和<entry></entry>包含的子元素註冊了事件處理器,因此即使xml文檔開始處有<updated>、<title>和<link>等同名的元素標籤,但也不會進行處理。因此和上一部分的Demo相比,就不再需要設置標誌變量用來判斷,而且看起來也更加簡單了。
雖然寫法不同了,但是對具體標籤的處理邏輯和上一部分Demo中是一樣的,因爲處理的xml文檔內容沒有變。
程序的最後調用android.util.Xml類的類方法parse直接進行解析,也更加方便了。
- //調用android.util.Xml開始解析
- try {
- Xml.parse(inStream, Xml.Encoding.UTF_8, root.getContentHandler());
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
其中Xml類parse方法的ContentHandler參數由根元素通過getContentHandler()方式獲得。
最後添加OPhoneXMLDemoSaxII.java文件中的內容,內容和上一個Demo工程OPhoneXMLDemoSax中的OPhoneXMLDemoSax.java基本一樣,
- public class OPhoneXMLDemoSaxII extends Activity {
- /** Called when the activity is first created. */
- //定義顯示的List相關變量
- ListView list;
- ArrayAdapter<EarthquakeEntry> adapter;
- ArrayList<EarthquakeEntry> earthquakeEntryList;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- //獲取地震數據流
- InputStream earthquakeStream = readEarthquakeDataFromFile();
- //Android Sax方式進行解析
- AndroidSaxEarthquakeHandler androidSaxHandler = new AndroidSaxEarthquakeHandler();
- earthquakeEntryList = androidSaxHandler.parse(earthquakeStream);
- //用ListView進行顯示
- list = (ListView)this.findViewById(R.id.list);
- adapter = new ArrayAdapter<EarthquakeEntry>(this, android.R.layout.simple_list_item_1, earthquakeEntryList);
- list.setAdapter(adapter);
- }
- private InputStream readEarthquakeDataFromFile()
- {
- //從本地獲取地震數據
- InputStream inStream = null;
- try {
- inStream = this.getAssets().open("USGS_Earthquake_1M2_5.xml");
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- return inStream;
- }
- private InputStream readEarthquakeDataFromInternet()
- {
- //從網絡上獲取實時地震數據
- URL infoUrl = null;
- InputStream inStream = null;
- try {
- infoUrl = new URL("http://earthquake.usgs.gov/earthquakes/catalogs/1day-M2.5.xml");
- URLConnection connection = infoUrl.openConnection();
- HttpURLConnection httpConnection = (HttpURLConnection)connection;
- int responseCode = httpConnection.getResponseCode();
- if(responseCode == HttpURLConnection.HTTP_OK)
- {
- inStream = httpConnection.getInputStream();
- }
- } catch (MalformedURLException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- return inStream;
- }
- }
只是把進行SAX解析的部分換成了如下方式:
- //Android Sax方式進行解析
- AndroidSaxEarthquakeHandler androidSaxHandler = new AndroidSaxEarthquakeHandler();
- earthquakeEntryList = androidSaxHandler.parse(earthquakeStream);
完成了,可以保存運行看下效果。
三.總結
OPhone/Android平臺提供了相當強大的XML解析支持,不僅包含了Java SDK中用來XML處理的API,而且OPhone/Android SDK還提供了特有的用於SAX解析XML的包android.sax。使用這個包中的API可以更加方便解析,特別是當要解析的xml文檔中在不同的層級位置處有多個相同名稱的標籤但需要分別進行不同處理時,同時也有更好的魯棒性,減少解析時產生Bug的可能性。
以上我們介紹的都是SAX方式解析XML,而解析XML常用的還有DOM方式,這部分內容我們以後接着學習。