Gemtext: add a sweet streaming parser

This commit is contained in:
dece 2021-12-07 23:02:34 +01:00
parent 3e42f2bace
commit f2eae54234
2 changed files with 75 additions and 3 deletions

View file

@ -0,0 +1,64 @@
package dev.lowrespalmtree.comet
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import java.nio.ByteBuffer
import java.nio.CharBuffer
import java.nio.charset.Charset
open class Line
class EmptyLine : Line()
class ParagraphLine(val text: String) : Line()
class TitleLine(val level: Int, val text: String) : Line()
class LinkLine(val url: String, val label: String) : Line()
class PreFenceLine(val caption: String) : Line()
class PreTextLine(val text: String) : Line()
class BlockquoteLine(val text: String) : Line()
class ListItemLine(val text: String) : Line()
fun parseData(
inChannel: Channel<ByteArray>,
charset: Charset,
scope: CoroutineScope
): Channel<Line> {
val channel = Channel<Line>()
scope.launch {
var isPref = false
var buffer = ByteArray(0)
for (data in inChannel) {
buffer += data
var nextLineFeed: Int = -1
while (buffer.isNotEmpty() && buffer.indexOf(0x0A).also { nextLineFeed = it } > -1) {
val lineData = buffer.sliceArray(0 until nextLineFeed)
buffer = buffer.sliceArray(nextLineFeed + 1 until buffer.size)
val lineString = charset.decode(ByteBuffer.wrap(lineData))
val line = parseLine(lineString, isPref)
when (line) {
is PreFenceLine -> isPref = !isPref
}
channel.send(line)
}
}
}
return channel
}
private fun parseLine(line: CharBuffer, isPreformatted: Boolean): Line =
when {
line.isEmpty() -> EmptyLine()
line.startsWith("###") -> TitleLine(3, getCharsFrom(line, 3))
line.startsWith("##") -> TitleLine(2, getCharsFrom(line, 2))
line.startsWith("#") -> TitleLine(1, getCharsFrom(line, 1))
line.startsWith(">") -> BlockquoteLine(getCharsFrom(line, 1))
line.startsWith("```") -> PreFenceLine(getCharsFrom(line, 3))
line.startsWith("* ") -> ListItemLine(getCharsFrom(line, 2))
line.startsWith("=>") -> getCharsFrom(line, 2).split(" ", limit = 2)
.run { LinkLine(get(0), if (size == 2) get(1) else "") }
else -> if (isPreformatted) PreTextLine(line.toString()) else ParagraphLine(line.toString())
}
private fun getCharsFrom(line: CharBuffer, index: Int) = line.substring(index).removePrefix(" ")
private const val TAG = "Gemtext"

View file

@ -89,9 +89,17 @@ class MainActivity : AppCompatActivity() {
private suspend fun handleRequestSuccess(response: Response) {
val charset = Charset.defaultCharset()
for (data in response.data) {
val decoded = charset.decode(ByteBuffer.wrap(data)).toString()
source += decoded
for (line in parseData(response.data, charset, viewModelScope)) {
when (line) {
is EmptyLine -> { source += "\n" }
is ParagraphLine -> { source += line.text + "\n" }
is TitleLine -> { source += "TTL-${line.level} ${line.text}\n" }
is LinkLine -> { source += "LNK ${line.url} + ${line.label}\n" }
is PreFenceLine -> { source += "PRE ${line.caption}\n" }
is PreTextLine -> { source += line.text + "\n" }
is BlockquoteLine -> { source += "QUO ${line.text}\n" }
is ListItemLine -> { source += "LST ${line.text}\n" }
}
sourceLiveData.postValue(source)
}
}