首页 > 解决方案 > 在 Kotlin 中使用 SAX 进行异步 XML 解析

问题描述

我有一个 SAX 解析器读取 XML 文件(特别是 .xlsx 文件)并将内容作为 Row 对象列表返回:大致是这样的

fun readExcelContent(data: InputStream) {
    val pkg = OPCPackage.open(file)
    val reader = XSSFReader(pkg)
    val sst = reader.sharedStringsTable
    val parser = XMLHelper.newXMLReader()
    val handler = ExcelSheetHandler(sst)
    parser.contentHandler = handler
    val sheet = reader.sheetsData.next()
    val source = InputSource(sheet)
    parser.parse(source)

    return handler.content
}

扩展并负责填充列表ExcelSheetHandler的类在哪里:DefaultHandler

class ExcelSheetHandler(sst: SharedStringsTable): DefaultHandler() {

    private val content = mutableListOf<Row>()

    @Throws(SAXException::class)
    override fun endElement(uri: String?, localName: String?, name: String) {
        // If it's the end of a content element, add a row to content
    }
}

它基本上是对Apache POI howto中事件模型示例的轻微修改。

我想知道是否有一种方法可以readExcelContent返回异步对象(例如流),并在读取行后立即将行发送给其客户端,而不必等待整个文件被处理。

标签: kotlinapache-poisaxkotlin-coroutines

解决方案


我更喜欢这个kotlinx.coroutines.Channelkotlinx.coroutines.Flow例,因为这是由该parse()方法触发的热数据流。以下是Kotlin 语言指南所述的内容。

流是类似于序列的冷流——流构建器中的代码在收集流之前不会运行

这是您可以尝试的快速实现。

class ExcelSheetHandler : DefaultHandler() {

    private val scope = CoroutineScope(Dispatchers.Default)
    private val rows = Channel<Row>()

    override fun endDocument() {
        // To avoid suspending forever!
        rows.close()
    }

    @Throws(SAXException::class)
    override fun endElement(uri: String?, localName: String?, name: String) {
        readRow(uri, localName, name)
    }

    private fun readRow(uri: String?, localName: String?, name: String) = runBlocking {
        // If it's the end of a content element, add a row to content
        rows.send(row)
    }

    // Client code - if it needs to be somewhere else
    // you can expose a reference to Channel object
    private fun processRows() = scope.launch {
        for(row in rows) {
            // Do something
            println(row)
        }
    }
}

推荐阅读