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-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
|
package org.fcitx.fcitx5.android.core
|
||||||
|
|
||||||
object FcitxUtils {
|
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.21/src/lib/fcitx-utils/stringutils.cpp#L100
|
||||||
// https://github.com/fcitx/fcitx5/blob/5.1.8/src/lib/fcitx-utils/stringutils.cpp#L362
|
// 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 {
|
fun unescapeForValue(str: String): String {
|
||||||
val quoted = str.length >= 2 && str.first() == '"' && str.last() == '"'
|
if (str.length > 2 && str.startsWith('"') && str.endsWith('"')) {
|
||||||
val s = if (quoted) str.substring(1, str.length - 1) else str
|
val (consumed, result) = consumeMaybeEscapedValue(str)
|
||||||
if (s.isEmpty()) return s
|
return if (consumed.length == str.length) result else ""
|
||||||
var escape = false
|
}
|
||||||
return buildString {
|
return str
|
||||||
s.forEach { c ->
|
}
|
||||||
when (escape) {
|
|
||||||
false -> {
|
// https://en.cppreference.com/cpp/string/basic_string/find_first_of
|
||||||
if (c == '\\') {
|
private fun String.findFirstOf(str: String, pos: Int = 0): Int {
|
||||||
escape = true
|
val set = str.toSet()
|
||||||
} else {
|
for (i in pos..<length) {
|
||||||
append(c)
|
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 -> {
|
when (state) {
|
||||||
if (c == '\\') {
|
UnescapeState.NORMAL -> {
|
||||||
append('\\')
|
when (c) {
|
||||||
} else if (c == 'n') {
|
'\\' -> {
|
||||||
append('\n')
|
state = UnescapeState.ESCAPE
|
||||||
} else if (c == '"' && quoted) {
|
}
|
||||||
append('"')
|
'"' -> {
|
||||||
} else {
|
end = i + 1
|
||||||
throw IllegalStateException("Unexpected escape sequence '\\${c}' when unescaping string '${str}'.")
|
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 {
|
fun escapeForValue(str: String): String {
|
||||||
val needsQuote = str.lastIndexOfAny(QuotedChars) >= 0
|
val needsEscape = str.lastIndexOfAny(EscapeChars) >= 0
|
||||||
return buildString {
|
return buildString {
|
||||||
if (needsQuote) append('"')
|
if (needsEscape) append('"')
|
||||||
str.forEach { c ->
|
str.forEach { c ->
|
||||||
append(
|
val escape = escapeMap[c]
|
||||||
when (c) {
|
if (escape != null) {
|
||||||
'\\' -> "\\\\"
|
append('\\')
|
||||||
'\n' -> "\\n"
|
append(escape)
|
||||||
'"' -> "\\\""
|
} else {
|
||||||
else -> c
|
append(c)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
}
|
||||||
if (needsQuote) append('"')
|
if (needsEscape) append('"')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-License-Identifier: LGPL-2.1-or-later
|
* 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
|
package org.fcitx.fcitx5.android
|
||||||
@ -11,13 +11,15 @@ import org.junit.Test
|
|||||||
|
|
||||||
class StringEscapeTest {
|
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(
|
private val data = listOf(
|
||||||
"\"" to """"\""""",
|
"\"" to """"\""""",
|
||||||
"\"\"\n" to """"\"\"\n"""",
|
"\"\"\n" to """"\"\"\n"""",
|
||||||
"abc" to """abc""",
|
"abc" to """abc""",
|
||||||
"ab\"c" to """"ab\"c"""",
|
"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
|
@Test
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user