1616
1717package com.bq.poeditor.gradle.xml
1818
19+ import com.bq.poeditor.gradle.ktx.dumpToString
20+ import com.bq.poeditor.gradle.ktx.toDocument
1921import com.bq.poeditor.gradle.utils.ALL_REGEX_STRING
2022import org.w3c.dom.Document
2123import org.w3c.dom.Element
@@ -31,6 +33,13 @@ class XmlPostProcessor {
3133 companion object {
3234 private val DEFAULT_ENCODING = Charsets .UTF_8
3335 private val VARIABLE_REGEX = Regex (""" \{\d?\{(.*?)\}\}""" )
36+
37+ private const val TAG_RESOURCES = " resources"
38+ private const val TAG_STRING = " string"
39+ private const val TAG_PLURALS = " plurals"
40+ private const val TAG_ITEM = " item"
41+
42+ private const val ATTR_NAME = " name"
3443 }
3544
3645 /* *
@@ -40,17 +49,29 @@ class XmlPostProcessor {
4049 * - Format variables and texts to conform to Android strings.xml format
4150 * - Split to multiple XML files depending on regex matching
4251 */
43- fun postProcessTranslationXml (translationXmlString : String ,
52+ fun postProcessTranslationXml (translationFileXmlString : String ,
4453 fileSplitRegexStringList : List <String >): Map <String , Document > =
45- splitTranslationXml(formatTranslationXml(translationXmlString), fileSplitRegexStringList)
54+ splitTranslationXml(formatTranslationXml(translationFileXmlString), fileSplitRegexStringList)
55+
56+ /* *
57+ * Formats a given translations XML string to conform to Android strings.xml format.
58+ */
59+ fun formatTranslationXml (translationFileXmlString : String ): String {
60+ // Parse line by line by traversing the original file using DOM
61+ val translationFileXmlDocument = translationFileXmlString.toDocument()
62+
63+ formatTranslationXmlDocument(translationFileXmlDocument, translationFileXmlDocument.childNodes)
64+
65+ return translationFileXmlDocument.dumpToString()
66+ }
4667
4768 /* *
48- * Format variables and texts to conform to Android strings.xml format.
69+ * Formats a given string to conform to Android strings.xml format.
4970 */
50- fun formatTranslationXml ( translationXmlString : String ): String {
51- // We need to check for variables to see if we have to escape percent symbols: if we find variables, we have to
52- // escape them
53- val containsVariables = translationXmlString .contains(VARIABLE_REGEX )
71+ fun formatTranslationString ( translationString : String ): String {
72+ // We need to check for variables to see if we have to escape percent symbols: if we find variables, we have
73+ // to escape them
74+ val containsVariables = translationString .contains(VARIABLE_REGEX )
5475
5576 val placeholderTransform: (MatchResult ) -> CharSequence = { matchResult ->
5677 // TODO: if the string has multiple variables but any of them has no order number,
@@ -64,7 +85,7 @@ class XmlPostProcessor {
6485 }
6586 }
6687
67- return translationXmlString
88+ return translationString
6889 // Replace % with %% if variables are found
6990 .let { if (containsVariables) it.replace(" %" , " %%" ) else it }
7091 // Replace < with < and > with >
@@ -98,9 +119,9 @@ class XmlPostProcessor {
98119 nodes.forEach { node ->
99120 node.parentNode.removeChild(node)
100121 val copiedNode = (node.cloneNode(true ) as Element ).apply {
101- val name = getAttribute(" name " )
122+ val name = getAttribute(ATTR_NAME )
102123 val nameWithoutRegex = regex.find(name)?.groups?.get(1 )?.value ? : " "
103- setAttribute(" name " , nameWithoutRegex)
124+ setAttribute(ATTR_NAME , nameWithoutRegex)
104125 }
105126 xmlRecords.adoptNode(copiedNode)
106127 xmlRecords.firstChild.appendChild(copiedNode)
@@ -113,25 +134,65 @@ class XmlPostProcessor {
113134 .plus(ALL_REGEX_STRING to translationFileRecords)
114135 }
115136
137+ private fun formatTranslationXmlDocument (document : Document , nodeList : NodeList , rootNode : Node ? = null) {
138+ for (i in 0 until nodeList.length) {
139+ if (nodeList.item(i).nodeType == Node .ELEMENT_NODE ) {
140+ val nodeElement = nodeList.item(i) as Element
141+ when (nodeElement.tagName) {
142+ TAG_RESOURCES -> {
143+ // Main node, traverse its children
144+ formatTranslationXmlDocument(document, nodeElement.childNodes, nodeElement)
145+ }
146+ TAG_PLURALS -> {
147+ // Plurals node, process its children
148+ formatTranslationXmlDocument(document, nodeElement.childNodes, nodeElement)
149+ }
150+ TAG_STRING -> {
151+ // String node, apply transformation to the content
152+ processTextAndReplaceNodeContent(document, nodeElement, rootNode)
153+ }
154+ TAG_ITEM -> {
155+ // Plurals item node, apply transformation to the content
156+ processTextAndReplaceNodeContent(document, nodeElement, rootNode)
157+ }
158+ }
159+ }
160+ }
161+ }
162+
163+ private fun processTextAndReplaceNodeContent (document : Document , nodeElement : Element , rootNode : Node ? ) {
164+ val content = nodeElement.textContent
165+ val processedContent = formatTranslationString(content)
166+ val copiedNodeElement = (nodeElement.cloneNode(true ) as Element ).apply {
167+ textContent = processedContent
168+ }
169+ document.adoptNode(copiedNodeElement)
170+ rootNode?.replaceChild(copiedNodeElement, nodeElement)
171+ }
172+
116173 private fun extractMatchingNodes (nodeList : NodeList , regexString : String ): List <Node > {
117174 val matchedNodes = mutableListOf<Node >()
118175 val regex = Regex (regexString)
119176
120177 for (i in 0 until nodeList.length) {
121178 if (nodeList.item(i).nodeType == Node .ELEMENT_NODE ) {
122179 val nodeElement = nodeList.item(i) as Element
123- when {
124- // Main XML node, process children
125- nodeElement.tagName == " resources " -> {
180+ when (nodeElement.tagName) {
181+ TAG_RESOURCES -> {
182+ // Main XML node, process children
126183 matchedNodes.addAll(extractMatchingNodes(nodeElement.childNodes, regexString))
127184 }
128- // String node, add if name matches regex
129- nodeElement.tagName == " string" && nodeElement.getAttribute(" name" ).matches(regex) -> {
130- matchedNodes.add(nodeElement)
185+ TAG_STRING -> {
186+ // String node, add node if name matches regex
187+ if (nodeElement.getAttribute(ATTR_NAME ).matches(regex)) {
188+ matchedNodes.add(nodeElement)
189+ }
131190 }
132- // Plurals node, add node and children if name matches regex
133- nodeElement.tagName == " plurals" && nodeElement.getAttribute(" name" ).matches(regex) -> {
134- matchedNodes.add(nodeElement)
191+ TAG_PLURALS -> {
192+ // Plurals node, add node and children if name matches regex
193+ if (nodeElement.getAttribute(ATTR_NAME ).matches(regex)) {
194+ matchedNodes.add(nodeElement)
195+ }
135196 }
136197 }
137198 }
0 commit comments