Browse Source

Improve handling of special characters in string resources (#4220)

Introduced a function to process and replace certain escaped symbols
like '\n', '\t', and '\uXXXX' in the strings extracted from compose
string resources.
release/1.6.0-beta02 v1.6.0-beta02
Konstantin 10 months ago committed by GitHub
parent
commit
870b2d3aec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 13
      components/resources/demo/shared/src/commonMain/composeResources/values/strings.xml
  2. 38
      components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringResources.kt
  3. 7
      components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ResourceTest.kt

13
components/resources/demo/shared/src/commonMain/composeResources/values/strings.xml

@ -1,13 +1,12 @@
<resources> <resources>
<string name="app_name">Compose Resources App</string> <string name="app_name">Compose Resources App</string>
<string name="hello">😊 Hello world!</string> <string name="hello">😊 Hello world!</string>
<string name="multi_line">Lorem ipsum dolor sit amet, <string name="multi_line">Lorem ipsum dolor sit amet,\nconsectetur adipiscing elit.
consectetur adipiscing elit. Donec eget turpis ac sem ultricies consequat.</string>
Donec eget turpis ac sem ultricies consequat.</string> <string name="str_template">Hello, %1$s!\nYou have %2$d new messages.</string>
<string name="str_template">Hello, %1$s! You have %2$d new messages.</string>
<string-array name="str_arr"> <string-array name="str_arr">
<item>item 1</item> <item>item \u2605</item>
<item>item 2</item> <item>item \u2318</item>
<item>item 3</item> <item>item \u00BD</item>
</string-array> </string-array>
</resources> </resources>

38
components/resources/library/src/commonMain/kotlin/org/jetbrains/compose/resources/StringResources.kt

@ -20,7 +20,7 @@ private val SimpleStringFormatRegex = Regex("""%(\d)\$[ds]""")
@ExperimentalResourceApi @ExperimentalResourceApi
@Immutable @Immutable
class StringResource class StringResource
@InternalResourceApi constructor(id: String, val key: String, items: Set<ResourceItem>): Resource(id, items) @InternalResourceApi constructor(id: String, val key: String, items: Set<ResourceItem>) : Resource(id, items)
private sealed interface StringItem { private sealed interface StringItem {
data class Value(val text: String) : StringItem data class Value(val text: String) : StringItem
@ -53,11 +53,13 @@ private suspend fun getParsedStrings(
private suspend fun parseStringXml(path: String, resourceReader: ResourceReader): Map<String, StringItem> { private suspend fun parseStringXml(path: String, resourceReader: ResourceReader): Map<String, StringItem> {
val nodes = resourceReader.read(path).toXmlElement().childNodes val nodes = resourceReader.read(path).toXmlElement().childNodes
val strings = nodes.getElementsWithName("string").associate { element -> val strings = nodes.getElementsWithName("string").associate { element ->
element.getAttribute("name") to StringItem.Value(element.textContent.orEmpty()) val rawString = element.textContent.orEmpty()
element.getAttribute("name") to StringItem.Value(handleSpecialCharacters(rawString))
} }
val arrays = nodes.getElementsWithName("string-array").associate { arrayElement -> val arrays = nodes.getElementsWithName("string-array").associate { arrayElement ->
val items = arrayElement.childNodes.getElementsWithName("item").map { element -> val items = arrayElement.childNodes.getElementsWithName("item").map { element ->
element.textContent.orEmpty() val rawString = element.textContent.orEmpty()
handleSpecialCharacters(rawString)
} }
arrayElement.getAttribute("name") to StringItem.Array(items) arrayElement.getAttribute("name") to StringItem.Array(items)
} }
@ -204,3 +206,33 @@ private fun NodeList.getElementsWithName(name: String): List<Element> =
List(length) { item(it) } List(length) { item(it) }
.filterIsInstance<Element>() .filterIsInstance<Element>()
.filter { it.localName == name } .filter { it.localName == name }
//https://developer.android.com/guide/topics/resources/string-resource#escaping_quotes
/**
* Replaces
*
* '\n' -> new line
*
* '\t' -> tab
*
* '\uXXXX' -> unicode symbol
*
* '\\' -> '\'
*
* @param string The input string to handle.
* @return The string with special characters replaced according to the logic.
*/
internal fun handleSpecialCharacters(string: String): String {
val unicodeNewLineTabRegex = Regex("""\\u[a-fA-F\d]{4}|\\n|\\t""")
val doubleSlashRegex = Regex("""\\\\""")
val doubleSlashIndexes = doubleSlashRegex.findAll(string).map { it.range.first }
val handledString = unicodeNewLineTabRegex.replace(string) { matchResult ->
if (doubleSlashIndexes.contains(matchResult.range.first - 1)) matchResult.value
else when (matchResult.value) {
"\\n" -> "\n"
"\\t" -> "\t"
else -> matchResult.value.substring(2).toInt(16).toChar().toString()
}
}.replace("""\\""", """\""")
return handledString
}

7
components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/ResourceTest.kt

@ -98,6 +98,13 @@ class ResourceTest {
}.message.let { msg -> }.message.let { msg ->
assertEquals("Resource with ID='ImageResource:test3' has more than one file: en1, en2", msg) assertEquals("Resource with ID='ImageResource:test3' has more than one file: en1, en2", msg)
} }
}
@Test
fun testEscapedSymbols() {
assertEquals(
"abc \n \\n \t \\t \u1234 \ua45f \\u1234 \\ \\u355g",
handleSpecialCharacters("""abc \n \\n \t \\t \u1234 \ua45f \\u1234 \\ \u355g""")
)
} }
} }

Loading…
Cancel
Save