Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inline XML plain string resource references #24

Merged
merged 3 commits into from
Oct 21, 2023
Merged
Show file tree
Hide file tree
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 @@ -19,38 +19,93 @@ private const val TAG_STRING_ARRAY = "string-array"
private const val TAG_PLURALS = "plurals"
private const val TAG_ITEM = "item"

private val ReplacementExpr = Regex("@string/(\\S+)")
private var replacements = mutableListOf<String>()

internal fun FileTreeWalk.filterXmlStringFiles(): Sequence<File> =
filter {
it.isFile &&
it.nameWithoutExtension.contains(STRINGS_FILE_NAME, ignoreCase = true) &&
it.parentFile.name.startsWith(VALUES_FOLDER_NAME, ignoreCase = true)
}

internal fun File.getXmlStrings(): List<Pair<ResourceName, StringResource>> =
konsumeXml()
private typealias ResourceNodes = List<Pair<ResourceName, StringResource>>

internal fun File.getXmlStrings(): ResourceNodes {
replacements = mutableListOf()
return konsumeXml()
.child(TAG_RESOURCES) {
getPlainStrings() + getStringArrays() + getPlurals()
}
.applyReplacements()
}

private fun ResourceNodes.applyReplacements(): ResourceNodes {
val values = toMutableList()

// We need to do late replacements because Konsume XML resolves everything on-demand,
// there's no get("something") so trying to replace value of "A" node in "B" while "B" is
// being resolved is impossible
for (replacement in replacements) {
val index = values.indexOfFirst { (key, _) -> key == replacement }
val (key, resource) = values[index]
val replaced: StringResource = when (resource) {
is StringResource.PlainString -> resource.copy(
value = replaceResourceValueExpr(resource.value, values)
)
is StringResource.Plurals -> resource.copy(
value = resource.value.mapValues { (_, nodeText) ->
replaceResourceValueExpr(nodeText, values)
}
)
is StringResource.StringArray -> resource.copy(
value = resource.value.map { nodeText -> replaceResourceValueExpr(nodeText, values) }
)
}

values[index] = key to replaced
}

return values
}

private fun replaceResourceValueExpr(currentValue: String, values: ResourceNodes) =
ReplacementExpr.replace(currentValue) { match ->
val target = match.groupValues.last()
// Searches for `<string name="...">`
val (_, resource) = values.first { (name, _) -> name == target }
(resource as StringResource.PlainString).value
}

private fun Konsumer.getPlainStrings(): List<Pair<String, StringResource>> =
private fun Konsumer.getPlainStrings(): ResourceNodes =
children(TAG_STRING) {
attributes[ATTRIBUTE_NAME] to StringResource.PlainString(
value = text()
val attr = attributes[ATTRIBUTE_NAME]
attr to StringResource.PlainString(
value = textWithReplacements(attr)
)
}

private fun Konsumer.getStringArrays(): List<Pair<String, StringResource>> =
private fun Konsumer.getStringArrays(): ResourceNodes =
children(TAG_STRING_ARRAY) {
attributes[ATTRIBUTE_NAME] to StringResource.StringArray(
value = children(TAG_ITEM) { text() }
val attr = attributes[ATTRIBUTE_NAME]
attr to StringResource.StringArray(
value = children(TAG_ITEM) { textWithReplacements(attr) }
)
}

private fun Konsumer.getPlurals(): List<Pair<String, StringResource>> =
private fun Konsumer.getPlurals(): ResourceNodes =
children(TAG_PLURALS) {
attributes[ATTRIBUTE_NAME] to StringResource.Plurals(
val attr = attributes[ATTRIBUTE_NAME]
attr to StringResource.Plurals(
value = children(TAG_ITEM) {
Quantity.valueOf(attributes[ATTRIBUTE_QUANTITY].uppercase()) to text()
Quantity.valueOf(attributes[ATTRIBUTE_QUANTITY].uppercase()) to
textWithReplacements(attr)
}.toMap()
)
}

private fun Konsumer.textWithReplacements(attr: String): String = text()
.also { text ->
if (ReplacementExpr.containsMatchIn(text))
replacements.add(attr)
}
1 change: 1 addition & 0 deletions sample-xml/src/main/res/values-pt/strings.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<resources>
<string name="simple">Olá "mundo"</string>
<string name="params">Parâmetros: %1$s, %2$d, %s, %d</string>
<string name="replacement">@string/simple denovo</string>

<string-array name="array">
<item>Abacate</item>
Expand Down
1 change: 1 addition & 0 deletions sample-xml/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<resources>
<string name="simple">Hello "world"</string>
<string name="params">Parameters: %1$s, %2$d, %s, %d</string>
<string name="replacement">@string/simple again</string>

<string-array name="array">
<item>Avocado</item>
Expand Down