解析XML数据
可擴展標記語言(XML)是一個以機器可讀的形式編碼文檔的規則的集合。XML是一種流行的在網絡上分享數據的格式。頻繁地更新他們的內容的網站,比如新聞站點或博客,經常提供一個XML feed,以使外部的程序能夠了解內容變化的最新情況。對于聯網apps來說,上傳和解析XML數據是一種常見的任務。這一節將解釋如何解析XML文檔并使用它們的數據。
選擇一個Parser
我們建議使用XmlPullParser,它是Android上解析XML的一種高效的和可維護的方式。在歷史上,Android有這個接口的兩種實現:
- KXmlParser?通過XmlPullParserFactory.newPullParser().
- ExpatPullParser, 通過Xml.newPullParser().
兩種中的任何一種選擇都挺好。這一節的例子使用了ExpatPullParser,通過Xml.newPullParser()。
分析Feed
解析一個feed的第一步是確定你對哪些fields感興趣。Parser提取那些fields的數據,并忽略其余的。
這里是一個在示例app中解析的feed的摘錄。在feed中,到StackOverflow.com的每一個post以一個entry標簽的形式出現,其包含了一些嵌套的標簽:
<?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:creativeCommons="http://backend.userland.com/creativeCommonsRssModule" ..."> <title type="text">newest questions tagged android - Stack Overflow</title> ...<entry>...</entry><entry><id>http://stackoverflow.com/q/9439999</id><re:rank scheme="http://stackoverflow.com">0</re:rank><title type="text">Where is my data file?</title><category scheme="http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest/tags" term="android"/><category scheme="http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest/tags" term="file"/><author><name>cliff2310</name><uri>http://stackoverflow.com/users/1128925</uri></author><link rel="alternate" href="http://stackoverflow.com/questions/9439999/where-is-my-data-file" /><published>2012-02-25T00:30:54Z</published><updated>2012-02-25T00:30:54Z</updated><summary type="html"><p>I have an Application that requires a data file...</p></summary></entry><entry>...</entry> ... </feed>
示例app為entry標簽提取數據,及它的嵌套標簽的標題,鏈接,和總結。
實例化Parser
下一步是實例化一個parser,并啟動解析過程。在這個代碼片段中,一個parser被初始化為不處理命名空間,并使用提供的InputStream作為它的輸入。它通過一個對nextTag()的調用來啟動解析過程,并調用readFeed()方法,后者會提取并處理app感興趣的數據:
public class StackOverflowXmlParser {// We don't use namespacesprivate static final String ns = null;public List parse(InputStream in) throws XmlPullParserException, IOException {try {XmlPullParser parser = Xml.newPullParser();parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);parser.setInput(in, null);parser.nextTag();return readFeed(parser);} finally {in.close();}}... }
讀取Feed
readFeed()方法執行實際的處理feed的工作。它查找標記為“entry”的元素作為一個遞歸地feed處理的起點。如果一個標簽不是一個entry標簽,它就跳過它。一旦整個feed都被遞歸地處理完了,則readFeed()返回一個List,其中包含了它從feed中提取entries(包含嵌套的數據成員)。然后這個List被parser返回。
private List readFeed(XmlPullParser parser) throws XmlPullParserException, IOException {List entries = new ArrayList();parser.require(XmlPullParser.START_TAG, ns, "feed");while (parser.next() != XmlPullParser.END_TAG) {if (parser.getEventType() != XmlPullParser.START_TAG) {continue;}String name = parser.getName();// Starts by looking for the entry tagif (name.equals("entry")) {entries.add(readEntry(parser));} else {skip(parser);}} return entries; }
解析XML
解析一個XML feed的步驟如下:
- 每個感興趣的標簽一個“read”方法。比如,readEntry(),readTitle(),等等。Parser從input stream中讀取標簽。當它遇到一個名為entry,title,link或summary的標簽時,它會調用那個標簽對應的適當的方法。否則,則跳過標簽。
- 為每種不同類型的標簽提取數據并將parser移向下一個標簽的方法。比如:
- 對于title和summary標簽來說,parser調用readText()。這個方法通過調用parser.getText()為這些標簽提取數據。
- 對于link標簽,paser要為links提取數據,則它首先會確定link是否是它所感興趣的類型。然后它使用parser.getAttributeValue()來提取link的值。
- 對于entry標簽,parser調用readEntry()。這個方法解析entry的嵌套的標簽,并返回一個Entry對象,其中包含數據成員,title,link和summary。
- 一個遞歸的helper方法skip()。關于這個主題的更多討論,請參考跳過你不感興趣的標簽。
這個代碼片段顯示了parser如何解析entries,titles,links和summaries。
public static class Entry {public final String title;public final String link;public final String summary;private Entry(String title, String summary, String link) {this.title = title;this.summary = summary;this.link = link;} }// Parses the contents of an entry. If it encounters a title, summary, or link tag, hands them off // to their respective "read" methods for processing. Otherwise, skips the tag. private Entry readEntry(XmlPullParser parser) throws XmlPullParserException, IOException {parser.require(XmlPullParser.START_TAG, ns, "entry");String title = null;String summary = null;String link = null;while (parser.next() != XmlPullParser.END_TAG) {if (parser.getEventType() != XmlPullParser.START_TAG) {continue;}String name = parser.getName();if (name.equals("title")) {title = readTitle(parser);} else if (name.equals("summary")) {summary = readSummary(parser);} else if (name.equals("link")) {link = readLink(parser);} else {skip(parser);}}return new Entry(title, summary, link); }// Processes title tags in the feed. private String readTitle(XmlPullParser parser) throws IOException, XmlPullParserException {parser.require(XmlPullParser.START_TAG, ns, "title");String title = readText(parser);parser.require(XmlPullParser.END_TAG, ns, "title");return title; }// Processes link tags in the feed. private String readLink(XmlPullParser parser) throws IOException, XmlPullParserException {String link = "";parser.require(XmlPullParser.START_TAG, ns, "link");String tag = parser.getName();String relType = parser.getAttributeValue(null, "rel"); if (tag.equals("link")) {if (relType.equals("alternate")){link = parser.getAttributeValue(null, "href");parser.nextTag();} }parser.require(XmlPullParser.END_TAG, ns, "link");return link; }// Processes summary tags in the feed. private String readSummary(XmlPullParser parser) throws IOException, XmlPullParserException {parser.require(XmlPullParser.START_TAG, ns, "summary");String summary = readText(parser);parser.require(XmlPullParser.END_TAG, ns, "summary");return summary; }// For the tags title and summary, extracts their text values. private String readText(XmlPullParser parser) throws IOException, XmlPullParserException {String result = "";if (parser.next() == XmlPullParser.TEXT) {result = parser.getText();parser.nextTag();}return result; }... }
跳過你不關心的標簽
上面描述的XML解析的其中一步是paser跳過它不感興趣的標簽。這里是parser的skip()方法:
private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {if (parser.getEventType() != XmlPullParser.START_TAG) {throw new IllegalStateException();}int depth = 1;while (depth != 0) {switch (parser.next()) {case XmlPullParser.END_TAG:depth--;break;case XmlPullParser.START_TAG:depth++;break;}}}
這是它如何工作的:
- 如果當前的事件不是START_TAG,它會拋出一個異常。
- 它消費掉START_TAG,及所有的events直到匹配的END_TAG。
- 要確保它停在正確的END_TAG,而不是在最初的START_TAG之后它所遇到的第一個tag,它會追蹤嵌套的深度。
這樣的話,如果當前的元素具有嵌套的元素,則depth的值將一直為非0,直到parser消費掉了所有在最初的START_TAG和與其匹配的END_TAG之間的所有的events。比如,考慮parser如何跳過<author>元素,它具有2個嵌套的元素,<name>和<uri>:
- while循環的第一輪,<author>之后parser遇到的next tag是<name>的START_TAG。depth的值被增加為2。
- while循環的第二輪,parser遇到的next tag是END_TAG </name>。depth的值被減為1。
- while循環的第三輪,parser遇到的next tag是START_TAG <uri>。depth的值被增至2。
- while循環的第四輪,parser遇到的next tag是END_TAG </uri>。depth的值被減為1。
- while循環的第五輪,也是最后一輪,parser遇到的next tag是END_TAG </author>。depth的值被減為0,表明<author>元素已經被成功地跳過去了。
消費XML數據
例子應用程序在一個AsyncTask中獲取并解析XML feed。這將把處理過程從主UI線程中分離。當處理過程完成時,app更新main activity (NetworkActivity)中的UI。
在下面所示的摘錄中,loadPage()方法做了如下的事情:
- 用XML feed的URL初始化一個string變量。
- 如果用戶設置和網絡連接允許,則它會調用DownloadXmlTask().execute(url)。這一步實例化一個新的DownloadXmlTask對象(AsyncTask子類) 并執行它的execute()方法,這將會下載并解析feed,并返回一個在UI中顯示的結果字符串。
下面所示的AsyncTask子類,DownloadXmlTask,實現了如下的AsyncTask方法:
- doInBackground()?執行方法loadXmlFromNetwork()。它傳遞feed的URL作為一個參數。loadXmlFromNetwork()方法獲取并處理feed。當它結束時,它傳回一個結果字符串。
- onPostExecute()?得到返回的字符串并把它顯示在UI中。
下面是方法loadXmlFromNetwork(),它會在DownloadXmlTask中調用。它做了如下的事情:
Done.
轉載于:https://my.oschina.net/wolfcs/blog/297884
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
- 上一篇: win7操作技巧
- 下一篇: Eclipse无法打开“Failed t