adding super naive html parsing for style options
This commit is contained in:
parent
3517cf492a
commit
e13ce95b83
|
@ -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 = "")
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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) }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue