Skip to content

Instantly share code, notes, and snippets.

@Kashif-E
Created January 17, 2024 17:18
Show Gist options
  • Save Kashif-E/5c06b38a561423f4096e68f0e166dafe to your computer and use it in GitHub Desktop.
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
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