adding super naive html parsing for style options

This commit is contained in:
Adam Brown 2022-10-23 20:04:38 +01:00
parent 3517cf492a
commit e13ce95b83
3 changed files with 192 additions and 0 deletions

View File

@ -0,0 +1,19 @@
package app.dapk.st.matrix.common
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class RichText(@SerialName("parts") val parts: Set<Part>) {
@Serializable
sealed interface Part {
@Serializable
data class Normal(@SerialName("content") val content: String) : Part
data class Link(@SerialName("url") val url: String, @SerialName("label") val label: String) : Part
data class Bold(@SerialName("content") val content: String) : Part
data class Italic(@SerialName("content") val content: String) : Part
data class BoldItalic(@SerialName("content") val content: String) : Part
}
}
fun RichText.asString() = parts.joinToString(separator = "")

View File

@ -0,0 +1,57 @@
package app.dapk.st.matrix.sync.internal.sync
import app.dapk.st.matrix.common.RichText
data class Tag(val name: String, val inner: String, val content: String)
class RichMessageParser {
fun parse(input: String): RichText {
val buffer = mutableSetOf<RichText.Part>()
var openIndex = 0
var closeIndex = 0
while (openIndex != -1) {
val foundIndex = input.indexOf('<', startIndex = openIndex)
if (foundIndex != -1) {
closeIndex = input.indexOf('>', startIndex = openIndex)
if (closeIndex == -1) {
openIndex++
} else {
val wholeTag = input.substring(foundIndex, closeIndex + 1)
val tagName = wholeTag.substring(1, wholeTag.indexOfFirst { it == '>' || it == ' ' })
val exitTag = "<$tagName/>"
val exitIndex = input.indexOf(exitTag, startIndex = closeIndex + 1)
val tagContent = input.substring(closeIndex + 1, exitIndex)
val tag = Tag(name = tagName, wholeTag, tagContent)
println("found $tag")
if (openIndex != foundIndex) {
buffer.add(RichText.Part.Normal(input.substring(openIndex, foundIndex)))
}
openIndex = exitIndex + exitTag.length
when (tagName) {
"a" -> {
val findHrefUrl = wholeTag.substringAfter("href=").replace("\"", "").removeSuffix(">")
buffer.add(RichText.Part.Link(url = findHrefUrl, label = tag.content))
}
"b" -> buffer.add(RichText.Part.Bold(tagContent))
"i" -> buffer.add(RichText.Part.Italic(tagContent))
}
}
} else {
// exit
if (openIndex < input.length) {
buffer.add(RichText.Part.Normal(input.substring(openIndex)))
}
break
}
}
return RichText(buffer)
}
}

View File

@ -0,0 +1,116 @@
package app.dapk.st.matrix.sync.internal.sync
import app.dapk.st.matrix.common.RichText
import org.amshove.kluent.shouldBeEqualTo
import org.junit.Ignore
import org.junit.Test
class RichMessageParserTest {
private val parser = RichMessageParser()
@Test
fun `parses plain text`() = runParserTest(
input = "Hello world!",
expected = RichText(setOf(RichText.Part.Normal("Hello world!")))
)
@Test
fun `parses nested b tags`() = runParserTest(
Case(
input = """hello <b>wor<b/>ld""",
expected = RichText(
setOf(
RichText.Part.Normal("hello "),
RichText.Part.Bold("wor"),
RichText.Part.Normal("ld"),
)
)
),
)
@Test
fun `parses nested i tags`() = runParserTest(
Case(
input = """hello <i>wor<i/>ld""",
expected = RichText(
setOf(
RichText.Part.Normal("hello "),
RichText.Part.Italic("wor"),
RichText.Part.Normal("ld"),
)
)
),
)
@Ignore // TODO
@Test
fun `parses nested tags`() = runParserTest(
Case(
input = """hello <b><i>wor<i/><b/>ld""",
expected = RichText(
setOf(
RichText.Part.Normal("hello "),
RichText.Part.BoldItalic("wor"),
RichText.Part.Normal("ld"),
)
)
),
Case(
input = """<a href="www.google.com"><a href="www.google.com">www.google.com<a/><a/>""",
expected = RichText(
setOf(
RichText.Part.Link(url = "www.google.com", label = "www.google.com"),
RichText.Part.Link(url = "www.bing.com", label = "www.bing.com"),
)
)
)
)
@Test
fun `parses 'a' tags`() = runParserTest(
Case(
input = """hello world <a href="www.google.com">a link!<a/> more content.""",
expected = RichText(
setOf(
RichText.Part.Normal("hello world "),
RichText.Part.Link(url = "www.google.com", label = "a link!"),
RichText.Part.Normal(" more content."),
)
)
),
Case(
input = """<a href="www.google.com">www.google.com<a/><a href="www.bing.com">www.bing.com<a/>""",
expected = RichText(
setOf(
RichText.Part.Link(url = "www.google.com", label = "www.google.com"),
RichText.Part.Link(url = "www.bing.com", label = "www.bing.com"),
)
)
),
)
private fun runParserTest(vararg cases: Case) {
val errors = mutableListOf<Throwable>()
cases.forEach {
runCatching { runParserTest(it.input, it.expected) }.onFailure { errors.add(it) }
}
if (errors.isNotEmpty()) {
throw CompositeThrowable(errors)
}
}
private fun runParserTest(input: String, expected: RichText) {
val result = parser.parse(input)
result shouldBeEqualTo expected
}
}
private data class Case(val input: String, val expected: RichText)
class CompositeThrowable(inner: List<Throwable>) : Throwable() {
init {
inner.forEach { addSuppressed(it) }
}
}