Created
January 17, 2024 17:18
-
-
Save Kashif-E/5c06b38a561423f4096e68f0e166dafe to your computer and use it in GitHub Desktop.
An example of how html can be converted to annotated string in compose
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
val html = """ | |
<h5>Requesting a refund on markaz</h5> | |
<p>Ap Delivery partner ke issue ke baray main hamin mukhtalif tareeqo se bta skta hain?</p> | |
<ul> | |
<li> Pehla order laganay k baad verification call ka intezar karain aur call receive kar kay apna order confirm karwayen. Orders ke tadaad ziada honay ki surat mei verification ko 2 days bhi lag sakte hain </li> | |
<li>Agar apke order ka status shipping booked likha ajai tou iska matlab apki verification ho chuki hai</li> | |
<li>Agar aap pehli dafa order kar rahay hain tou aap bara order nahi laga sakte. Ap ka pehla order deliver hone ke baad ap 2-3 multiple order laga sakte hain</li> | |
</ul> | |
<ol> | |
<li> Pehla order laganay k baad verification call ka intezar karain aur call receive kar kay apna order confirm karwayen. Orders ke tadaad ziada honay ki surat mei verification ko 2 days bhi lag sakte hain </li> | |
<li>Agar apke order ka status shipping booked likha ajai tou iska matlab apki verification ho chuki hai</li> | |
<li>Agar aap pehli dafa order kar rahay hain tou aap bara order nahi laga sakte. Ap ka pehla order deliver hone ke baad ap 2-3 multiple order laga sakte hain</li> | |
</ol> | |
""".trimIndent() | |
@Composable | |
fun htmlToAnnotatedString( | |
html: String, | |
fontStyle: SpanStyle = MaterialTheme.typography.body2.toSpanStyle(), | |
boldFontStyle: SpanStyle = MaterialTheme.typography.body2.copy(fontWeight = FontWeight.Bold) | |
.toSpanStyle(), | |
h5: SpanStyle = MaterialTheme.typography.h5.toSpanStyle(), | |
italicStyle: SpanStyle = SpanStyle(fontStyle = FontStyle.Italic) | |
): AnnotatedString { | |
return buildAnnotatedString { | |
var currentStyle: SpanStyle? = null | |
var isOrderedList = false | |
var currentNumber = 0 | |
var currentTag = "" | |
val ksoupHandler = KsoupHtmlHandler.Builder().onOpenTag { name, attributes, isImplied -> | |
currentTag = name.trim() | |
when (name.trim()) { | |
"h1" -> { | |
currentStyle = fontStyle | |
} | |
"h2" -> { | |
currentStyle = boldFontStyle | |
} | |
"h5" -> { | |
currentStyle = h5 | |
} | |
"h3" -> { | |
currentStyle = boldFontStyle | |
} | |
"p" -> { | |
currentStyle = fontStyle | |
} | |
"ul" -> { | |
isOrderedList = false | |
} | |
"ol" -> { | |
isOrderedList = true | |
currentNumber = 0 | |
} | |
"li" -> { | |
if (isOrderedList) { | |
currentNumber++ | |
appendStyledContent(" $currentNumber. ", currentStyle!!, false) | |
} else { | |
appendStyledContent(" • ", currentStyle!!, false) | |
} | |
} | |
"strong", "b" -> { | |
currentStyle = currentStyle?.copy(fontWeight = FontWeight.Bold) ?: boldFontStyle | |
} | |
"em", "i" -> { | |
currentStyle = currentStyle?.copy(fontStyle = FontStyle.Italic) ?: italicStyle | |
} | |
} | |
}.onCloseTag { name, isImplied -> | |
currentStyle = fontStyle | |
}.onText { text -> | |
if (currentTag != "ul" || currentTag != "ol") { | |
currentStyle?.let { style -> | |
appendStyledContent(text, style, currentTag != "p") | |
} | |
} | |
}.build() | |
val parser = KsoupHtmlParser(handler = ksoupHandler) | |
parser.parseComplete(html) | |
parser.end() | |
} | |
} | |
fun String.insertCharacterAtSpacing(spacing: Int): String { | |
val result = StringBuilder() | |
var count = 0 | |
for (char in this) { | |
result.append(char) | |
count++ | |
if (count % spacing == 0) { | |
result.append(" ") | |
// result.append(characterToInsert) | |
} | |
} | |
return result.toString() | |
} | |
/* | |
@Composable | |
fun htmlToAnnotatedString( | |
html: String, | |
fontStyle: SpanStyle = MaterialTheme.typography.body2.toSpanStyle(), | |
boldFontStyle: SpanStyle = MaterialTheme.typography.body2.copy(fontWeight = FontWeight.Bold) | |
.toSpanStyle(), | |
h5: SpanStyle = MaterialTheme.typography.h5.toSpanStyle(), | |
): AnnotatedString { | |
return buildAnnotatedString { | |
val lines = html.split("\n").map { it.trim() } | |
var listItemCount = 0 | |
var inOrderedList = false | |
var inUnorderedList = false | |
val ksoupHandler = KsoupHtmlHandler.Builder().onOpenTag { name, attributes, isImplied -> | |
println("Open tag: $name") | |
}.onText {text-> | |
}.build() | |
val parser = KsoupHtmlParser(handler = ksoupHandler) | |
lines.forEach { line -> | |
when { | |
line.startsWith("<h1>") -> { | |
val content = line.removeSurrounding("<h1>", "</h1>") | |
appendStyledContent(content, fontStyle) | |
append("\n\n") | |
} | |
line.startsWith("<h2>") -> { | |
val content = line.removeSurrounding("<h2>", "</h2>") | |
appendStyledContent( | |
content, boldFontStyle | |
) | |
append("\n\n") | |
} | |
line.startsWith("<h5>") -> { | |
val content = line.removeSurrounding("<h5>", "</h5>") | |
appendStyledContent( | |
content, h5 | |
) | |
append("\n\n") | |
} | |
line.startsWith("<h3>") -> { | |
val content = line.removeSurrounding("<h3>", "</h3>") | |
appendStyledContent( | |
content, boldFontStyle | |
) | |
append("\n\n") | |
} | |
line.startsWith("<p>") -> { | |
val content = line.removeSurrounding("<p>", "</p>") | |
appendStyledContent(content, fontStyle) | |
append("\n\n") | |
} | |
line.startsWith("<ul>") -> { | |
inUnorderedList = true | |
} | |
line.startsWith("<ol>") -> { | |
inOrderedList = true | |
listItemCount = 0 | |
} | |
line.startsWith("<li>") -> { | |
if (inOrderedList || inUnorderedList) { | |
val content = line.removeSurrounding("<li>", "</li>") | |
val bulletOrNumber = if (inOrderedList) "${++listItemCount}. " else "• " | |
withStyle(fontStyle) { | |
append(bulletOrNumber + content + "\n") | |
} | |
} | |
} | |
line.startsWith("</ul>") -> { | |
inUnorderedList = false | |
} | |
line.startsWith("</ol>") -> { | |
inOrderedList = false | |
} | |
} | |
} | |
} | |
} | |
*/ | |
private fun AnnotatedString.Builder.appendStyledContent( | |
content: String, | |
baseStyle: SpanStyle, | |
shouldAddSpacing: Boolean | |
) { | |
var currentIndex = 0 | |
val regex = """<(/?strong|/?em|/?b|/?i)>""".toRegex() | |
val matches = regex.findAll(content).toList() | |
val newContent = if (shouldAddSpacing) content + "\n" else content | |
if (matches.isEmpty()) { | |
withStyle(baseStyle) { | |
append(newContent) | |
} | |
return | |
} | |
matches.forEach { match -> | |
val matchStart = match.range.first | |
if (matchStart > currentIndex) { | |
withStyle(baseStyle) { | |
append(newContent.substring(currentIndex, matchStart)) | |
} | |
} | |
currentIndex = match.range.last + 1 | |
when (match.value) { | |
"<strong>", "<b>" -> pushStyle(baseStyle.copy(fontWeight = FontWeight.Bold)) | |
"</strong>", "</b>" -> pop() | |
"<em>", "<i>" -> pushStyle(baseStyle.copy(fontStyle = FontStyle.Italic)) | |
"</em>", "</i>" -> pop() | |
} | |
} | |
if (currentIndex < newContent.length) { | |
withStyle(baseStyle) { | |
append(newContent.substring(currentIndex)) | |
} | |
} | |
} | |
@Composable | |
fun parseMyHtml() { | |
Text( | |
htmlToAnnotatedString(html), | |
modifier = Modifier.padding(LocalMarkazSpacing.current.l), | |
softWrap = true | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment