From 446d7299814b84a94587059846aa7bad9a8feaf0 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Sat, 29 Oct 2022 11:23:44 +0100 Subject: [PATCH] adding basic list support --- .../sync/internal/sync/message/HtmlParser.kt | 69 ++++++++++++++++++- .../internal/sync/RichMessageParserTest.kt | 21 ++++++ 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/message/HtmlParser.kt b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/message/HtmlParser.kt index c7b1ece..b979f66 100644 --- a/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/message/HtmlParser.kt +++ b/matrix/services/sync/src/main/kotlin/app/dapk/st/matrix/sync/internal/sync/message/HtmlParser.kt @@ -5,10 +5,11 @@ import app.dapk.st.matrix.common.UserId private const val TAG_OPEN = '<' private const val TAG_CLOSE = '>' private const val NO_RESULT_FOUND = -1 +private const val LI_VALUE_CAPTURE = "value=\"" internal class HtmlParser { - fun parseHtmlTags(input: String, searchIndex: Int, builder: PartBuilder) = input.findTag( + fun parseHtmlTags(input: String, searchIndex: Int, builder: PartBuilder): SearchIndex = input.findTag( fromIndex = searchIndex, onInvalidTag = { builder.appendText(input[it].toString()) }, onTag = { tagOpen, tagClose -> @@ -59,7 +60,7 @@ internal class HtmlParser { wholeTag: String, builder: PartBuilder, tagContent: String, - exitTagCloseIndex: Int + exitTagCloseIndex: Int, ) = when (tagName) { "a" -> { val findHrefUrl = wholeTag.substringAfter("href=").replace("\"", "").removeSuffix(">") @@ -84,6 +85,11 @@ internal class HtmlParser { exitTagCloseIndex } + "ul", "ol" -> { + parseList(tagName, tagContent, builder) + exitTagCloseIndex + } + "h1", "h2", "h3", "h4", "h5" -> { builder.appendBold(tagContent) builder.appendNewline() @@ -128,8 +134,65 @@ internal class HtmlParser { } } + private fun parseList(parentTag: String, parentContent: String, builder: PartBuilder) { + var nextIndex = 0 + var index = 1 + while (nextIndex != END_SEARCH) { + nextIndex = singleTagParser(parentContent, "li", nextIndex, builder) { wholeTag, tagContent -> + val content = when (parentTag) { + "ol" -> { + index = wholeTag.indexOf(LI_VALUE_CAPTURE).let { + if (it == -1) { + index + } else { + val start = it + LI_VALUE_CAPTURE.length + wholeTag.substring(start).substringBefore('\"').toInt() + } + } + + "$index. $tagContent".also { + index++ + } + } + + else -> "- $tagContent" + } + builder.appendText(content) + builder.appendNewline() + } + } + } + + + private fun singleTagParser(content: String, wantedTagName: String, searchIndex: Int, builder: PartBuilder, onTag: (String, String) -> Unit): SearchIndex { + return content.findTag( + fromIndex = searchIndex, + onInvalidTag = { builder.appendText(content[it].toString()) }, + onTag = { tagOpen, tagClose -> + val wholeTag = content.substring(tagOpen, tagClose + 1) + val tagName = wholeTag.substring(1, wholeTag.indexOfFirst { it == '>' || it == ' ' }) + + if (tagName == wantedTagName) { + val exitTag = "" + val exitIndex = content.indexOf(exitTag, startIndex = tagClose) + val exitTagCloseIndex = exitIndex + exitTag.length + if (exitIndex == END_SEARCH) { + builder.appendText(content[searchIndex].toString()) + searchIndex.next() + } else { + val tagContent = content.substring(tagClose + 1, exitIndex) + onTag(wholeTag, tagContent) + exitTagCloseIndex + } + } else { + END_SEARCH + } + } + ) + } + fun test(startingFrom: Int, intput: String): Int { - return intput.indexOf('<', startingFrom) + return intput.indexOf(TAG_OPEN, startingFrom) } } \ No newline at end of file diff --git a/matrix/services/sync/src/test/kotlin/app/dapk/st/matrix/sync/internal/sync/RichMessageParserTest.kt b/matrix/services/sync/src/test/kotlin/app/dapk/st/matrix/sync/internal/sync/RichMessageParserTest.kt index 13e8649..5be1d1b 100644 --- a/matrix/services/sync/src/test/kotlin/app/dapk/st/matrix/sync/internal/sync/RichMessageParserTest.kt +++ b/matrix/services/sync/src/test/kotlin/app/dapk/st/matrix/sync/internal/sync/RichMessageParserTest.kt @@ -74,6 +74,27 @@ class RichMessageParserTest { expected = RichText(setOf(Normal("Hello world!\nnext line\nanother line"))) ) + + @Test + fun `parses lists`() = runParserTest( + Case( + input = "", + expected = RichText(setOf(Normal("- content in list item\n- another item in list"))) + ), + Case( + input = "
  1. content in list item
  2. another item in list
", + expected = RichText(setOf(Normal("1. content in list item\n2. another item in list"))) + ), + Case( + input = """
  1. content in list item
  2. another item in list
""", + expected = RichText(setOf(Normal("5. content in list item\n6. another item in list"))) + ), + Case( + input = """
  1. content in list item
  2. another item in list
  3. another change
  4. without value
""", + expected = RichText(setOf(Normal("3. content in list item\n4. another item in list\n10. another change\n11. without value"))) + ), + ) + @Test fun `parses urls`() = runParserTest( Case(