Асинхронный XML синтаксический анализ с SAX в Kotlin - PullRequest
0 голосов
/ 29 мая 2020

У меня есть синтаксический анализатор 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 возвращал асинхронный объект, такой как поток, и отправлял строки своему клиенту, как только они были прочитаны, вместо того, чтобы ждать всего файл для обработки.

1 Ответ

0 голосов
/ 30 мая 2020

Я бы предпочел kotlinx.coroutines.Channel вместо kotlinx.coroutines.Flow для этого варианта использования, поскольку это горячий поток данных, запускаемый методом parse(). Вот что говорится в Kotlin Language Guide .

Потоки - это холодные потоки, похожие на последовательности - код внутри построителя потоков не запускается, пока поток не будет собран

Вот быстрая реализация, которую вы можете попробовать.

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)
        }
    }
}
...