mirror of
https://github.com/fcitx5-android/fcitx5-android.git
synced 2026-08-02 04:44:35 +08:00
Port new implementation of fcitx::stringutils::{,un}escapeForValue()
ffad9391ad
This commit is contained in:
parent
10f62c6936
commit
b1a030012d
@ -1,64 +1,136 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
* SPDX-FileCopyrightText: Copyright 2024 Fcitx5 for Android Contributors
|
||||
* SPDX-FileCopyrightText: Copyright 2024-2026 Fcitx5 for Android Contributors
|
||||
*/
|
||||
|
||||
package org.fcitx.fcitx5.android.core
|
||||
|
||||
object FcitxUtils {
|
||||
|
||||
// https://github.com/fcitx/fcitx5/blob/5.1.8/src/lib/fcitx-utils/stringutils.cpp#L323
|
||||
// https://github.com/fcitx/fcitx5/blob/5.1.8/src/lib/fcitx-utils/stringutils.cpp#L362
|
||||
// https://github.com/fcitx/fcitx5/blob/5.1.21/src/lib/fcitx-utils/stringutils.cpp#L100
|
||||
// https://en.cppreference.com/cpp/language/escape
|
||||
private val unescapeMap = mapOf(
|
||||
'\\' to '\\',
|
||||
'"' to '"',
|
||||
'n' to '\n',
|
||||
'f' to '\u000c',
|
||||
'r' to '\r',
|
||||
't' to '\t',
|
||||
'v' to '\u000b',
|
||||
)
|
||||
|
||||
// https://github.com/fcitx/fcitx5/blob/5.1.21/src/lib/fcitx-utils/stringutils.cpp#L390
|
||||
fun unescapeForValue(str: String): String {
|
||||
val quoted = str.length >= 2 && str.first() == '"' && str.last() == '"'
|
||||
val s = if (quoted) str.substring(1, str.length - 1) else str
|
||||
if (s.isEmpty()) return s
|
||||
var escape = false
|
||||
return buildString {
|
||||
s.forEach { c ->
|
||||
when (escape) {
|
||||
false -> {
|
||||
if (c == '\\') {
|
||||
escape = true
|
||||
} else {
|
||||
append(c)
|
||||
}
|
||||
if (str.length > 2 && str.startsWith('"') && str.endsWith('"')) {
|
||||
val (consumed, result) = consumeMaybeEscapedValue(str)
|
||||
return if (consumed.length == str.length) result else ""
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// https://en.cppreference.com/cpp/string/basic_string/find_first_of
|
||||
private fun String.findFirstOf(str: String, pos: Int = 0): Int {
|
||||
val set = str.toSet()
|
||||
for (i in pos..<length) {
|
||||
if (set.contains(get(i))) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
// https://en.cppreference.com/cpp/string/basic_string/find_first_not_of
|
||||
private fun String.findFirstNotOf(str: String, pos: Int = 0): Int {
|
||||
val set = str.toSet()
|
||||
for (i in pos..<length) {
|
||||
if (!set.contains(get(i))) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
enum class UnescapeState { NORMAL, ESCAPE }
|
||||
|
||||
// https://github.com/fcitx/fcitx5/blob/5.1.21/src/lib/fcitx-utils/stringutils.cpp#L435
|
||||
private fun consumeMaybeEscapedValue(str: String, skip: String = ""): Pair<String, String> {
|
||||
var input = str
|
||||
val start = input.findFirstNotOf(skip)
|
||||
if (start < 0) {
|
||||
return "" to ""
|
||||
}
|
||||
input = input.substring(start)
|
||||
val maybeQuoted = input.startsWith('"')
|
||||
if (maybeQuoted) {
|
||||
var end = 0
|
||||
val result = buildString {
|
||||
var state = UnescapeState.NORMAL
|
||||
input.forEachIndexed { i, c ->
|
||||
// skip first "
|
||||
if (i == 0) {
|
||||
return@forEachIndexed
|
||||
}
|
||||
true -> {
|
||||
if (c == '\\') {
|
||||
append('\\')
|
||||
} else if (c == 'n') {
|
||||
append('\n')
|
||||
} else if (c == '"' && quoted) {
|
||||
append('"')
|
||||
} else {
|
||||
throw IllegalStateException("Unexpected escape sequence '\\${c}' when unescaping string '${str}'.")
|
||||
when (state) {
|
||||
UnescapeState.NORMAL -> {
|
||||
when (c) {
|
||||
'\\' -> {
|
||||
state = UnescapeState.ESCAPE
|
||||
}
|
||||
'"' -> {
|
||||
end = i + 1
|
||||
return@buildString
|
||||
}
|
||||
else -> {
|
||||
append(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
UnescapeState.ESCAPE -> {
|
||||
// treat invalid escape sequence as normal character
|
||||
append(unescapeMap[c] ?: c)
|
||||
state = UnescapeState.NORMAL
|
||||
}
|
||||
escape = false
|
||||
}
|
||||
}
|
||||
}
|
||||
if (end > 0) {
|
||||
val consumed = input.substring(0, end)
|
||||
return consumed to result
|
||||
}
|
||||
}
|
||||
val end = input.findFirstOf(skip, 1)
|
||||
val consumed = if (end < 0) input else input.substring(0, end)
|
||||
return consumed to consumed
|
||||
}
|
||||
|
||||
private val QuotedChars = charArrayOf(' ', '"', '\t', '\r', '\u000b', '\u000c')
|
||||
// https://github.com/fcitx/fcitx5/blob/5.1.21/src/lib/fcitx-utils/stringutils.cpp#L86
|
||||
private val escapeMap = mapOf(
|
||||
'\\' to '\\',
|
||||
'"' to '"',
|
||||
'\n' to 'n',
|
||||
'\u000c' to 'f',
|
||||
'\r' to 'r',
|
||||
'\t' to 't',
|
||||
'\u000b' to 'v',
|
||||
)
|
||||
|
||||
// https://github.com/fcitx/fcitx5/blob/5.1.8/src/lib/fcitx-utils/stringutils.cpp#L380
|
||||
// "\f\r\t\v \"\\\n"
|
||||
private val EscapeChars = charArrayOf('\u000c', '\r', '\t', '\u000b', ' ', '"', '\\', '\n')
|
||||
|
||||
// https://github.com/fcitx/fcitx5/blob/5.1.21/src/lib/fcitx-utils/stringutils.cpp#L404
|
||||
fun escapeForValue(str: String): String {
|
||||
val needsQuote = str.lastIndexOfAny(QuotedChars) >= 0
|
||||
val needsEscape = str.lastIndexOfAny(EscapeChars) >= 0
|
||||
return buildString {
|
||||
if (needsQuote) append('"')
|
||||
if (needsEscape) append('"')
|
||||
str.forEach { c ->
|
||||
append(
|
||||
when (c) {
|
||||
'\\' -> "\\\\"
|
||||
'\n' -> "\\n"
|
||||
'"' -> "\\\""
|
||||
else -> c
|
||||
}
|
||||
)
|
||||
val escape = escapeMap[c]
|
||||
if (escape != null) {
|
||||
append('\\')
|
||||
append(escape)
|
||||
} else {
|
||||
append(c)
|
||||
}
|
||||
}
|
||||
if (needsQuote) append('"')
|
||||
if (needsEscape) append('"')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
* SPDX-FileCopyrightText: Copyright 2024 Fcitx5 for Android Contributors
|
||||
* SPDX-FileCopyrightText: Copyright 2024-2026 Fcitx5 for Android Contributors
|
||||
*/
|
||||
|
||||
package org.fcitx.fcitx5.android
|
||||
@ -11,13 +11,15 @@ import org.junit.Test
|
||||
|
||||
class StringEscapeTest {
|
||||
|
||||
// https://github.com/fcitx/fcitx5/blob/5.1.8/test/teststringutils.cpp#L118
|
||||
// https://github.com/fcitx/fcitx5/blob/5.1.21/test/teststringutils.cpp#L142
|
||||
private val data = listOf(
|
||||
"\"" to """"\""""",
|
||||
"\"\"\n" to """"\"\"\n"""",
|
||||
"abc" to """abc""",
|
||||
"ab\"c" to """"ab\"c"""",
|
||||
"a c" to """"a c""""
|
||||
"a c" to """"a c"""",
|
||||
"工 " to """"工 """",
|
||||
"\r\u000b\u000c\n\t\\\" " to """"\r\v\f\n\t\\\" """",
|
||||
)
|
||||
|
||||
@Test
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user