我們現在有了許多的基礎的 parser function 了,我們直接來看怎麼樣組合這些 function 來 parse 一些東西吧!以 library 裡面的 Android parser 為例,我們的目標是拿到 RSS 2.0 標準格式裡的 channel 資料。首先,我們先定義 channel 長的樣子包在一個 data class 裡面,而這個 data class 叫做 RssStandardChannelData
,完整的 data class 可以參考這個連結,這邊就不把完整程式碼貼上來了。Channel 裡面又有包含一些資料,我們就可以定義在另一個 data class 裡面,像是 category 、 item 和 cloud 等等。
接著,我們就可以來寫 channel 的 parser function 。
@Throws(IOException::class, XmlPullParserException::class)
private fun XmlPullParser.readRssStandardChannel(): RssStandardChannelData {
require(XmlPullParser.START_TAG, null, CHANNEL)
// 1
var title: String? = null
var image: Image? = null
val categories = mutableListOf<Category>()
var cloud: Cloud? = null
var textInput: TextInput? = null
var skipHours: List<Int>? = null
val items = mutableListOf<RssStandardItemData>()
// 省略其他變數宣告
// 2
while (next() != XmlPullParser.END_TAG) {
if (eventType != XmlPullParser.START_TAG) continue
// 3
when (name) {
TITLE -> title = readString(TITLE)
IMAGE -> image = readImage()
CATEGORY -> categories.add(readCategory())
CLOUD -> cloud = readCloud()
TTL -> ttl = readString(TTL)?.toIntOrNull()
RATING -> rating = readString(RATING)
TEXT_INPUT -> textInput = readTextInput()
SKIP_HOURS -> skipHours = readSkipHours()
ITEM -> items.add(readRssStandardItem())
// 省略其他處理
else -> skip()
}
}
require(XmlPullParser.END_TAG, null, CHANNEL)
// 4
return RssStandardChannelData(...)
}
在這個 function 裡,我把他分成四個部分來講解,分別對應上方程式碼註解上的數字:
readString
之後再轉成想要的型別。如果 tag 還有另一個 tag ,可以再額外定義一個 data class 來代表該子 tag 。如果 tag 名稱不在處理的範圍,則可以呼叫 skip 去跳過該 tag 。在處理的過程中,有呼叫一些其他的 function ,像是 readCategory readTextInput readSkipHours 和 readRssStandardItem 等,它們內部處理的邏輯是和 readRssStandardChannel 相同,這些 function 可以在這邊找到。現在,我們掌握了使用原生的 XmlPullParser 的方法,可以組合出我們想要的資料 parser ,但別高興得太早!上一段程式碼是不是看起來有哪邊怪怪的?假設我們有很多種的資料型態和 tag 要取資料,那我們不就要每一種都寫一個 function 去處理?如果我們有方法可以去產生這些程式碼,我們不用自己手動寫。這個就是我們的 annotation processor 可以辦得到的事,下篇開始會講 annotation 和 annotation processor !