上篇我們介紹了 XmlPullParser 和常用的幾個 event type ,現在我們來介紹它的實作。首先我們要拿到 parser 的實體才能夠操作 tag ,我們可以定義一個 function 來拿 parser ,XML 字串拿到之後要先轉成 ByteArrayInputStream
,然後安全使用 use
來讀 inputStream 。為什麼會講它安全呢?因為 Kotlin 原生的 use
extension 可以讓我們操作 inputStream 的時候,不用自己寫 try/catch/finally ,它裡面幫你包好好的,只要傳一個 code block 進去執行就好。
fun getXmlParser(xml: String): XmlPullParser {
ByteArrayInputStream(xml.toByteArray()).use { inputStream ->
return Xml.newPullParser().apply {
setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
setInput(inputStream, null)
nextTag()
}
}
}
拿到parser 實體後,讓我們先從怎麼 parse 最外層的 <channel>
開始吧!
fun <T> parseChannel(xml: String, action: XmlPullParser.() -> T): T {
val parser = getXmlParser(xml)
var result: T? = null
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.eventType != XmlPullParser.START_TAG) continue
if (parser.name == CHANNEL) {
result = action(parser)
break
} else {
parser.skip()
}
}
return result ?: throw XmlPullParserException("No valid channel tag in the RSS feed.")
}
這段程式碼可以看到我們參數裡面放了個 action: XmlPullParser.() -> T
,這個是一個 Kotlin receiver type 的 lambda ,主要是想要讓外面使用的人在 lambda 裡面可以使用 XmlPullPaser
的 API ,另外,還有個泛型 T
,會使用泛型的主要原因是便於這個 function 被重複使用,畢竟我們有可能會定義不同型態的資料格式,舉例來說像是我可以把 RSS 2.0 的標準資料定義成 RssStandardChannel
,iTunes 平台格式定義成 ITunesChannel
,google 的就放在 GoogleChannel
,這些都可以放進這個 function 來用。
使用 parser 的時候,我們可以透過迴圈去拿取下一個 event ,拿 eventType 和 name 來判斷是不是 Channel
,這邊因為我們只需要掃到 channel 其他的 event 就可以跳過。拿到 channel 的 event 後,我們就可以用已經定義好的 lambda 去爬下一層的 tag 。
這邊還有個小細節就是跳過 tag 的方式,這邊一樣是寫成一個 extension 方便之後重複使用。跳過的 tag 有可能會有很多子 tag , depth
就是在記錄層數,當它回到 0 的時候,代表這個 tag 底下整顆子樹都跳過了。
@Throws(XmlPullParserException::class, IOException::class)
protected fun XmlPullParser.skip() {
if (eventType!= XmlPullParser.START_TAG) {
throw IllegalStateException()
}
var depth = 1
while (depth != 0) {
when (next()) {
XmlPullParser.END_TAG-> depth--
XmlPullParser.START_TAG-> depth++
}
}
}