Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ import kotlin.text.Typography.nbsp
import kotlin.text.Typography.ndash

private const val MAGIC_SEP = "{{!}}"
private const val MAX_WIKITEXT_RECURSION_DEPTH = 64

private object WikitextParserState {
val recursionDepth: ThreadLocal<Int> = ThreadLocal.withInitial { 0 }
}

/**
* Converts Wikitext source code into an [AnnotatedString] that can be rendered by [androidx.compose.material3.Text]
Expand All @@ -57,39 +62,47 @@ fun String.toWikitextAnnotatedString(
inIndentCode: Boolean = false,
showRef: (String) -> Unit,
): AnnotatedString {
val hrChar = '─'
val input = this
var i = 0
var number = 1 // Count for numbered lists

var italic = false
var bold = false

val twas: String.() -> AnnotatedString = {
this.toWikitextAnnotatedString(
colorScheme,
typography,
loadPage,
fontSize,
showRef = showRef
)
val currentDepth = WikitextParserState.recursionDepth.get()
if (currentDepth >= MAX_WIKITEXT_RECURSION_DEPTH) {
// Fallback: avoid runaway recursion on pathological or malformed wikitext
return AnnotatedString(this)
}

val twasNoNewline: String.() -> AnnotatedString = {
this.toWikitextAnnotatedString(
colorScheme,
typography,
loadPage,
fontSize,
newLine = false,
showRef = showRef
)
}
WikitextParserState.recursionDepth.set(currentDepth + 1)
try {
val hrChar = '─'
val input = this
var i = 0
var number = 1 // Count for numbered lists

var italic = false
var bold = false

val twas: String.() -> AnnotatedString = {
this.toWikitextAnnotatedString(
colorScheme,
typography,
loadPage,
fontSize,
showRef = showRef
)
}

val twasNoNewline: String.() -> AnnotatedString = {
this.toWikitextAnnotatedString(
colorScheme,
typography,
loadPage,
fontSize,
newLine = false,
showRef = showRef
)
}

return buildAnnotatedString {
while (i < input.length) {
if (input[i] != '#') number = 1
when (input[i]) {
return buildAnnotatedString {
while (i < input.length) {
if (input[i] != '#') number = 1
when (input[i]) {
' ' ->
if ((getOrNull(i - 1) == '\n' || i == 0) && !inIndentCode) {
val curr = substring(i + 1).substringBefore('\n')
Expand Down Expand Up @@ -530,7 +543,7 @@ fun String.toWikitextAnnotatedString(
}

currSubstring.startsWith("{{abbr", ignoreCase = true) -> {
val curr = currSubstring.substringAfter('|').substringBefore('|')
val curr = currSubstring.substringAfter('|', "").substringBefore('|')
append(curr.twas())
}

Expand Down Expand Up @@ -575,19 +588,19 @@ fun String.toWikitextAnnotatedString(
}

currSubstring.startsWith("{{val", ignoreCase = true) -> {
val curr = currSubstring.substringAfter('|').substringBefore('|')
val curr = currSubstring.substringAfter('|', "").substringBefore('|')
append(curr.twas())
}

currSubstring.startsWith("{{var", ignoreCase = true) -> {
val curr = currSubstring.substringAfter('|')
val curr = currSubstring.substringAfter('|', "")
append("''$curr''".twas())
}

arrayOf("{{small", "{{smaller", "{{petit", "{{hw-small", "{{sma").any {
currSubstring.startsWith(it, ignoreCase = true)
} -> {
val curr = currSubstring.substringAfter('|')
val curr = currSubstring.substringAfter('|', "")
withStyle(SpanStyle(fontSize = (fontSize - 2).sp)) {
append(curr.twas())
}
Expand Down Expand Up @@ -652,7 +665,7 @@ fun String.toWikitextAnnotatedString(
}

currSubstring.startsWith("{{dfn", true) -> {
val curr = currSubstring.substringAfter('|')
val curr = currSubstring.substringAfter('|', "")
append("'''$curr'''".twas())
}

Expand Down Expand Up @@ -706,7 +719,8 @@ fun String.toWikitextAnnotatedString(
}

currSubstring.startsWith("{{format price", true) -> {
val curr = currSubstring.substringAfter('|').substringBefore('|')
val curr =
currSubstring.substringAfter('|', "").substringBefore('|')
append(
if (curr.contains('.')) curr.toDoubleOrNull()
?.formatToHumanReadable() ?: curr.twas()
Expand Down Expand Up @@ -838,13 +852,13 @@ fun String.toWikitextAnnotatedString(

currSubstring.startsWith("{{US$", true) -> {
if (currSubstring.contains('|')) {
val curr = currSubstring.substringAfter('|')
val curr = currSubstring.substringAfter('|', "")
append("US$${curr.twas()}")
} else append("US$")
}

currSubstring.startsWith("{{hatnote", ignoreCase = true) -> {
val curr = currSubstring.substringAfter('|').replace('\n', ' ')
val curr = currSubstring.substringAfter('|', "").replace('\n', ' ')
append("''$curr''".twas())
}

Expand Down Expand Up @@ -957,18 +971,18 @@ fun String.toWikitextAnnotatedString(
}

currSubstring.startsWith("{{rp", true) -> {
val curr = currSubstring.substringAfter('|')
val curr = currSubstring.substringAfter('|', "")
append("<sup>:$curr </sup>".twas())
}

currSubstring.startsWith("{{isbn", true) -> {
val curr = currSubstring.substringAfter('|').split('|')
val curr = currSubstring.substringAfter('|', "").split('|')
.filterNot { it.contains('=') }.joinToString()
append("[[ISBN]] $curr".twas())
}

currSubstring.startsWith("{{sfrac") -> {
val curr = currSubstring.substringAfter('|')
val curr = currSubstring.substringAfter('|', "")
val splitList = curr.split('|')
when (splitList.size) {
3 -> append("${splitList[0]}<sup>${splitList[1]}</sup>/<sub>${splitList[2]}</sub>".twas())
Expand All @@ -992,7 +1006,8 @@ fun String.toWikitextAnnotatedString(
}

currSubstring.startsWith("{{unichar", ignoreCase = true) -> {
val curr = currSubstring.substringAfter('|').substringBefore('|')
val curr =
currSubstring.substringAfter('|', "").substringBefore('|')
append("<code>U+$curr</code> ".twas())
try {
append(Character.toString(curr.toInt(16)))
Expand All @@ -1001,7 +1016,7 @@ fun String.toWikitextAnnotatedString(
}

currSubstring.startsWith("{{char", ignoreCase = true) -> {
append(currSubstring.substringAfter('|').twas())
append(currSubstring.substringAfter('|', "").twas())
}

currSubstring.startsWith("{{Nihongo", ignoreCase = true) -> {
Expand Down Expand Up @@ -1029,7 +1044,7 @@ fun String.toWikitextAnnotatedString(
}

currSubstring.startsWith("{{noflag", ignoreCase = true) -> {
val curr = currSubstring.substringAfter('|')
val curr = currSubstring.substringAfter('|', "")
append(curr.twas())
}

Expand Down Expand Up @@ -1380,6 +1395,9 @@ fun String.toWikitextAnnotatedString(
i++
}
}
} finally {
WikitextParserState.recursionDepth.set(currentDepth)
}
}

fun String.substringMatchingParen(
Expand Down