supporting nested tags within a paragraph

This commit is contained in:
Adam Brown 2022-10-29 12:24:32 +01:00
parent 446d729981
commit 23b004ff02
2 changed files with 31 additions and 7 deletions

View File

@ -9,7 +9,7 @@ private const val LI_VALUE_CAPTURE = "value=\""
internal class HtmlParser { internal class HtmlParser {
fun parseHtmlTags(input: String, searchIndex: Int, builder: PartBuilder): SearchIndex = input.findTag( fun parseHtmlTags(input: String, searchIndex: Int, builder: PartBuilder, nestingLevel: Int = 0): SearchIndex = input.findTag(
fromIndex = searchIndex, fromIndex = searchIndex,
onInvalidTag = { builder.appendText(input[it].toString()) }, onInvalidTag = { builder.appendText(input[it].toString()) },
onTag = { tagOpen, tagClose -> onTag = { tagOpen, tagClose ->
@ -45,7 +45,7 @@ internal class HtmlParser {
else -> { else -> {
appendTextBeforeTag(searchIndex, tagOpen, builder, input) appendTextBeforeTag(searchIndex, tagOpen, builder, input)
val tagContent = input.substring(tagClose + 1, exitIndex) val tagContent = input.substring(tagClose + 1, exitIndex)
handleTagWithContent(input, tagName, wholeTag, builder, tagContent, exitTagCloseIndex) handleTagWithContent(input, tagName, wholeTag, builder, tagContent, exitTagCloseIndex, nestingLevel)
} }
} }
} }
@ -61,6 +61,7 @@ internal class HtmlParser {
builder: PartBuilder, builder: PartBuilder,
tagContent: String, tagContent: String,
exitTagCloseIndex: Int, exitTagCloseIndex: Int,
nestingLevel: Int,
) = when (tagName) { ) = when (tagName) {
"a" -> { "a" -> {
val findHrefUrl = wholeTag.substringAfter("href=").replace("\"", "").removeSuffix(">") val findHrefUrl = wholeTag.substringAfter("href=").replace("\"", "").removeSuffix(">")
@ -80,7 +81,18 @@ internal class HtmlParser {
} }
"p" -> { "p" -> {
builder.appendText(tagContent) if (tagContent.isNotEmpty() && nestingLevel < 2) {
var lastIndex = 0
iterateIndex(start = 0) { searchIndex ->
lastIndex = searchIndex
parseHtmlTags(tagContent, searchIndex, builder, nestingLevel = nestingLevel + 1)
}
if (lastIndex < tagContent.length) {
builder.appendText(tagContent.substring(lastIndex))
}
}
builder.appendNewline() builder.appendNewline()
exitTagCloseIndex exitTagCloseIndex
} }
@ -135,10 +147,9 @@ internal class HtmlParser {
} }
private fun parseList(parentTag: String, parentContent: String, builder: PartBuilder) { private fun parseList(parentTag: String, parentContent: String, builder: PartBuilder) {
var nextIndex = 0
var index = 1 var index = 1
while (nextIndex != END_SEARCH) { iterateIndex(start = 0) { nextIndex ->
nextIndex = singleTagParser(parentContent, "li", nextIndex, builder) { wholeTag, tagContent -> singleTagParser(parentContent, "li", nextIndex, builder) { wholeTag, tagContent ->
val content = when (parentTag) { val content = when (parentTag) {
"ol" -> { "ol" -> {
index = wholeTag.indexOf(LI_VALUE_CAPTURE).let { index = wholeTag.indexOf(LI_VALUE_CAPTURE).let {
@ -163,7 +174,6 @@ internal class HtmlParser {
} }
} }
private fun singleTagParser(content: String, wantedTagName: String, searchIndex: Int, builder: PartBuilder, onTag: (String, String) -> Unit): SearchIndex { private fun singleTagParser(content: String, wantedTagName: String, searchIndex: Int, builder: PartBuilder, onTag: (String, String) -> Unit): SearchIndex {
return content.findTag( return content.findTag(
fromIndex = searchIndex, fromIndex = searchIndex,
@ -195,4 +205,12 @@ internal class HtmlParser {
return intput.indexOf(TAG_OPEN, startingFrom) return intput.indexOf(TAG_OPEN, startingFrom)
} }
private fun iterateIndex(start: SearchIndex, action: (SearchIndex) -> SearchIndex): SearchIndex {
var nextIndex = start
while (nextIndex != END_SEARCH) {
nextIndex = action(nextIndex)
}
return nextIndex
}
} }

View File

@ -24,6 +24,12 @@ class RichMessageParserTest {
expected = RichText(setOf(Normal("Hello world!\nfoo bar\nafter paragraph"))) expected = RichText(setOf(Normal("Hello world!\nfoo bar\nafter paragraph")))
) )
@Test
fun `parses nesting within p tags`() = runParserTest(
input = "<p><b>Hello world!</b></p>",
expected = RichText(setOf(Bold("Hello world!")))
)
@Test @Test
fun `replaces quote entity`() = runParserTest( fun `replaces quote entity`() = runParserTest(
input = "Hello world! &quot;foo bar&quot;", input = "Hello world! &quot;foo bar&quot;",