Skip to content

Commit 3f1d0ab

Browse files
authored
Fix CDATA strings losing CDATA string. Properly unescape HTML tags' symbols (#46)
1 parent f9c4b60 commit 3f1d0ab

File tree

5 files changed

+86
-12
lines changed

5 files changed

+86
-12
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
.idea/**/usage.statistics.xml
1313
.idea/**/dictionaries
1414
.idea/**/shelf
15+
.idea/**/runConfigurations.xml
1516

1617
# Generated files
1718
.idea/**/contentModel.xml

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3030
### Removed
3131
- No removed features!
3232
### Fixed
33-
- No fixed issues!
33+
- Fix CDATA strings not parsed correctly
34+
- Fix `<` and `>` not getting properly unescaped in CDATA strings
3435
### Security
3536
- No security issues fixed!
3637

src/main/kotlin/com/hyperdevs/poeditor/gradle/ktx/StringExtensions.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@
1818

1919
package com.hyperdevs.poeditor.gradle.ktx
2020

21-
private val UNESCAPED_HTML_TAGS_REGEX = Regex("""&lt;([^.]*?)&gt;""")
22-
2321
/**
2422
* Unescapes HTML tags from string.
2523
*/
26-
fun String.unescapeHtmlTags() = this.replace(UNESCAPED_HTML_TAGS_REGEX, "<$1>")
24+
fun String.unescapeHtmlTags() = this.replace("&lt;", "<").replace("&gt;", ">")

src/main/kotlin/com/hyperdevs/poeditor/gradle/xml/XmlPostProcessor.kt

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,7 @@ import com.hyperdevs.poeditor.gradle.ktx.toAndroidXmlString
2222
import com.hyperdevs.poeditor.gradle.ktx.toStringsXmlDocument
2323
import com.hyperdevs.poeditor.gradle.ktx.unescapeHtmlTags
2424
import com.hyperdevs.poeditor.gradle.utils.ALL_REGEX_STRING
25-
import org.w3c.dom.Document
26-
import org.w3c.dom.Element
27-
import org.w3c.dom.Node
28-
import org.w3c.dom.NodeList
25+
import org.w3c.dom.*
2926

3027
/**
3128
* Class that handles XML transformation.
@@ -158,15 +155,42 @@ class XmlPostProcessor {
158155
}
159156

160157
private fun processTextAndReplaceNodeContent(document: Document, nodeElement: Element, rootNode: Node?) {
161-
val content = nodeElement.textContent
162-
val processedContent = formatTranslationString(content)
163-
val copiedNodeElement = (nodeElement.cloneNode(true) as Element).apply {
164-
textContent = processedContent
158+
// First check if we have a CDATA node as the a child of the element. If we have it, we have to
159+
// preserve the CDATA node but process the text. Else, we handle the node as a usual text node
160+
val copiedNodeElement: Element
161+
val (cDataNode, cDataPosition) = getCDataChildForNode(nodeElement)
162+
if (cDataNode != null) {
163+
val cDataContent = cDataNode.textContent
164+
val processedCDataContent = formatTranslationString(cDataContent)
165+
val copiedCDataNode = (cDataNode.cloneNode(true) as CDATASection).apply {
166+
this.data = processedCDataContent
167+
}
168+
copiedNodeElement = (nodeElement.cloneNode(true) as Element).apply {
169+
replaceChild(copiedCDataNode, this.childNodes.item(cDataPosition))
170+
}
171+
} else {
172+
val content = nodeElement.textContent
173+
val processedContent = formatTranslationString(content)
174+
copiedNodeElement = (nodeElement.cloneNode(true) as Element).apply {
175+
textContent = processedContent
176+
}
165177
}
178+
166179
document.adoptNode(copiedNodeElement)
167180
rootNode?.replaceChild(copiedNodeElement, nodeElement)
168181
}
169182

183+
private fun getCDataChildForNode(nodeElement: Element): Pair<Node?, Int> {
184+
val childrenList = nodeElement.childNodes
185+
for (i in 0..childrenList.length) {
186+
val childNode = childrenList.item(i)
187+
if (childNode is CDATASection) {
188+
return Pair(childNode, i)
189+
}
190+
}
191+
return Pair(null, -1)
192+
}
193+
170194
@Suppress("NestedBlockDepth")
171195
private fun extractMatchingNodes(nodeList: NodeList, regexString: String): List<Node> {
172196
val matchedNodes = mutableListOf<Node>()

src/test/kotlin/com/hyperdevs/poeditor/gradle/xml/XmlPostProcessorTest.kt

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,56 @@ class XmlPostProcessorTest {
317317
Assert.assertEquals(expectedResult, xmlPostProcessor.formatTranslationXml(inputXmlString))
318318
}
319319

320+
@Test
321+
fun `Postprocessing XML with CDATA works`() {
322+
// Test complete Xml
323+
val inputXmlString = """
324+
<resources>
325+
<string name="cdata">
326+
<![CDATA[Some text<a href="{{link}}">Link</a> text text]]>
327+
</string>
328+
</resources>
329+
"""
330+
331+
val expectedResult = """
332+
<resources>
333+
<string name="cdata">
334+
<![CDATA[Some text<a href="%1${'$'}s">Link</a> text text]]>
335+
</string>
336+
</resources>
337+
""".formatXml()
338+
339+
Assert.assertEquals(expectedResult, xmlPostProcessor.formatTranslationXml(inputXmlString))
340+
}
341+
342+
@Test
343+
fun `Postprocessing XML with multiline CDATA works`() {
344+
// Test complete Xml
345+
val inputXmlString = """
346+
<resources>
347+
<string name="cdata">
348+
<![CDATA[
349+
<br />
350+
<p><a href="mailto:{{email}}">ABC Email</a></p>
351+
]]>
352+
</string>
353+
</resources>
354+
"""
355+
356+
val expectedResult = """
357+
<resources>
358+
<string name="cdata">
359+
<![CDATA[
360+
<br />
361+
<p><a href="mailto:%1${'$'}s">ABC Email</a></p>
362+
]]>
363+
</string>
364+
</resources>
365+
""".formatXml()
366+
367+
Assert.assertEquals(expectedResult, xmlPostProcessor.formatTranslationXml(inputXmlString))
368+
}
369+
320370
@Test
321371
fun `Splitting tablet translation strings works`() {
322372
// Test complete Xml

0 commit comments

Comments
 (0)