Skip to content

Commit 15ee7a6

Browse files
committed
Fix HTML tags being escaped when parsing. Dotenv support for local development
1 parent 6ef22c8 commit 15ee7a6

File tree

9 files changed

+133
-56
lines changed

9 files changed

+133
-56
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,7 @@ gradle-app.setting
127127
# End of https://www.gitignore.io/api/java,gradle,intellij+iml
128128

129129
# Output directory for tests
130-
/output
130+
/output
131+
132+
# Donenv configuration file
133+
.env

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020
- No security issues fixed!
2121
-->
2222

23+
## [Unreleased]
24+
### Added
25+
- No new features!
26+
### Changed
27+
- No changed features!
28+
### Deprecated
29+
- No deprecated features!
30+
### Removed
31+
- No removed features!
32+
### Fixed
33+
- No fixed issues!
34+
### Security
35+
- No security issues fixed!
36+
37+
## [1.4.2] - 2020-12-29
38+
### Added
39+
- [Dotenv support](https://github.com/cdimascio/dotenv-kotlin) for the `Main.kt` file for easier local development.
40+
### Fixed
41+
- Fix HTML tags being escaped when parsing.
42+
2343
## [1.4.1] - 2020-12-28
2444
### Fixed
2545
- Fix percent symbols not being properly escaped (again) by processing the XML file line by line.

build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ dependencies {
6868
implementation("com.squareup.okhttp3:logging-interceptor:4.7.2")
6969
implementation("com.squareup.okhttp3:okhttp:4.7.2")
7070

71+
implementation("io.github.cdimascio:dotenv-kotlin:6.2.2")
72+
7173
testImplementation(gradleTestKit())
7274
testImplementation(kotlin("test"))
7375
testImplementation("junit:junit:4.13")

src/main/kotlin/com/bq/poeditor/gradle/Main.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,21 @@
1616

1717
package com.bq.poeditor.gradle
1818

19+
import io.github.cdimascio.dotenv.Dotenv
20+
1921
/**
2022
* Only for testing purposes.
23+
*
24+
* Declare the variables API_TOKEN, PROJECT_ID, RES_DIR_PATH and DEFAULT_LANGUAGE in /.env
2125
*/
2226
@Suppress("MagicNumber")
2327
fun main() {
24-
val apiToken = "your_api_token"
25-
val projectId = 1234567890
26-
val resDirPath = "output"
27-
val defaultLanguage = "en"
28+
val dotenv: Dotenv = Dotenv.load()
29+
30+
val apiToken = dotenv.get("API_TOKEN", "")
31+
val projectId = dotenv.get("PROJECT_ID", "-1").toInt()
32+
val resDirPath = dotenv.get("RES_DIR_PATH", "")
33+
val defaultLanguage = dotenv.get("DEFAULT_LANGUAGE", "")
2834

2935
PoEditorStringsImporter.importPoEditorStrings(apiToken, projectId, defaultLanguage, resDirPath)
3036
}

src/main/kotlin/com/bq/poeditor/gradle/ktx/DocumentExtensions.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ fun String.toDocument(): Document =
3434
.parse(this.byteInputStream(DEFAULT_ENCODING))
3535

3636
/**
37-
* Convers a [Document] into a formatted [String].
37+
* Converts a [Document] into a formatted [String].
3838
*/
39-
fun Document.dumpToString(): String {
39+
fun Document.toAndroidXmlString(): String {
4040
val registry = DOMImplementationRegistry.newInstance()
4141
val impl = registry.getDOMImplementation("LS") as DOMImplementationLS
4242
val output = impl.createLSOutput().apply { encoding = "UTF-8" }
@@ -51,5 +51,5 @@ fun Document.dumpToString(): String {
5151

5252
serializer.write(this, output)
5353

54-
return writer.toString()
54+
return writer.toString().unescapeHtmlTags()
5555
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*
2+
* Copyright 2020 BQ
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.bq.poeditor.gradle.ktx
18+
19+
private val UNESCAPED_HTML_TAGS_REGEX = Regex("""<([^.]*?)>""")
20+
21+
/**
22+
* Unescapes HTML tags from string.
23+
*/
24+
fun String.unescapeHtmlTags() = this.replace(UNESCAPED_HTML_TAGS_REGEX, "<$1>")

src/main/kotlin/com/bq/poeditor/gradle/xml/AndroidXmlWriter.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package com.bq.poeditor.gradle.xml
1818

19-
import com.bq.poeditor.gradle.ktx.dumpToString
19+
import com.bq.poeditor.gradle.ktx.toAndroidXmlString
2020
import com.bq.poeditor.gradle.utils.TABLET_REGEX_STRING
2121
import com.bq.poeditor.gradle.utils.TABLET_RES_FOLDER_SUFFIX
2222
import com.bq.poeditor.gradle.utils.logger
@@ -68,6 +68,6 @@ class AndroidXmlWriter {
6868
}
6969
}
7070

71-
File(stringsFolderFile, "strings.xml").writeText(document.dumpToString())
71+
File(stringsFolderFile, "strings.xml").writeText(document.toAndroidXmlString())
7272
}
7373
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616

1717
package com.bq.poeditor.gradle.xml
1818

19-
import com.bq.poeditor.gradle.ktx.dumpToString
19+
import com.bq.poeditor.gradle.ktx.toAndroidXmlString
2020
import com.bq.poeditor.gradle.ktx.toDocument
21+
import com.bq.poeditor.gradle.ktx.unescapeHtmlTags
2122
import com.bq.poeditor.gradle.utils.ALL_REGEX_STRING
2223
import org.w3c.dom.Document
2324
import org.w3c.dom.Element
@@ -62,7 +63,7 @@ class XmlPostProcessor {
6263

6364
formatTranslationXmlDocument(translationFileXmlDocument, translationFileXmlDocument.childNodes)
6465

65-
return translationFileXmlDocument.dumpToString()
66+
return translationFileXmlDocument.toAndroidXmlString()
6667
}
6768

6869
/**
@@ -88,8 +89,7 @@ class XmlPostProcessor {
8889
return translationString
8990
// Replace % with %% if variables are found
9091
.let { if (containsVariables) it.replace("%", "%%") else it }
91-
// Replace &lt; with < and &gt; with >
92-
.replace("&lt;", "<").replace("&gt;", ">")
92+
.unescapeHtmlTags()
9393
// Replace placeholders from {{variable}} to %1$s format.
9494
.replace(VARIABLE_REGEX, placeholderTransform)
9595
}

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

Lines changed: 64 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
package com.bq.poeditor.gradle.xml
1818

19-
import com.bq.poeditor.gradle.ktx.dumpToString
19+
import com.bq.poeditor.gradle.ktx.toAndroidXmlString
2020
import com.bq.poeditor.gradle.utils.ALL_REGEX_STRING
2121
import com.bq.poeditor.gradle.utils.TABLET_REGEX_STRING
2222
import org.junit.Assert
@@ -217,46 +217,6 @@ class XmlPostProcessorTest {
217217
Assert.assertEquals(expectedResult, xmlPostProcessor.formatTranslationXml(inputXmlString))
218218
}
219219

220-
@Test
221-
fun `Splitting tablet translation strings works`() {
222-
// Test complete Xml
223-
val expectedKey = "general_button_goTop"
224-
val inputXmlString = """
225-
<resources>
226-
<string name="$expectedKey">
227-
"$expectedKey"
228-
</string>
229-
<string name="${expectedKey}_tablet">
230-
"${expectedKey}_tablet"
231-
</string>
232-
</resources>
233-
"""
234-
235-
val allRegexString = ALL_REGEX_STRING
236-
val tabletRegexString = TABLET_REGEX_STRING
237-
238-
val splitTranslationXmlMap = xmlPostProcessor.splitTranslationXml(inputXmlString, listOf(tabletRegexString))
239-
240-
// Check XML documents and see if the first string node has the proper name and the proper text with XPath
241-
val xpNamePath = "//resources/string[position()=1]/@name"
242-
val xpTextPath = "//resources/string[position()=1]/text()"
243-
244-
Assert.assertEquals(
245-
expectedKey,
246-
xp.evaluate(xpNamePath, splitTranslationXmlMap.getValue(allRegexString)).trim())
247-
Assert.assertEquals(
248-
expectedKey,
249-
xp.evaluate(xpNamePath, splitTranslationXmlMap.getValue(tabletRegexString)).trim())
250-
251-
Assert.assertEquals(
252-
"\"$expectedKey\"",
253-
xp.evaluate(xpTextPath, splitTranslationXmlMap.getValue(allRegexString)).trim())
254-
Assert.assertEquals(
255-
"\"${expectedKey}_tablet\"",
256-
xp.evaluate(xpTextPath, splitTranslationXmlMap.getValue(tabletRegexString)).trim())
257-
258-
}
259-
260220
@Test
261221
fun `Postprocessing XML with plurals works`() {
262222
// Test complete Xml
@@ -333,6 +293,68 @@ class XmlPostProcessorTest {
333293
Assert.assertEquals(expectedResult, xmlPostProcessor.formatTranslationXml(inputXmlString))
334294
}
335295

296+
@Test
297+
fun `Postprocessing XML with string HTML symbols works`() {
298+
// Test complete Xml
299+
val inputXmlString = """
300+
<resources>
301+
<string name="hello_friend_bold">
302+
"Hello &lt;b&gt;{{name}}&lt;/b&gt;"
303+
</string>
304+
</resources>
305+
"""
306+
307+
val expectedResult = """
308+
<resources>
309+
<string name="hello_friend_bold">
310+
"Hello <b>%1${'$'}s</b>"
311+
</string>
312+
</resources>
313+
""".formatXml()
314+
315+
Assert.assertEquals(expectedResult, xmlPostProcessor.formatTranslationXml(inputXmlString))
316+
}
317+
318+
@Test
319+
fun `Splitting tablet translation strings works`() {
320+
// Test complete Xml
321+
val expectedKey = "general_button_goTop"
322+
val inputXmlString = """
323+
<resources>
324+
<string name="$expectedKey">
325+
"$expectedKey"
326+
</string>
327+
<string name="${expectedKey}_tablet">
328+
"${expectedKey}_tablet"
329+
</string>
330+
</resources>
331+
"""
332+
333+
val allRegexString = ALL_REGEX_STRING
334+
val tabletRegexString = TABLET_REGEX_STRING
335+
336+
val splitTranslationXmlMap = xmlPostProcessor.splitTranslationXml(inputXmlString, listOf(tabletRegexString))
337+
338+
// Check XML documents and see if the first string node has the proper name and the proper text with XPath
339+
val xpNamePath = "//resources/string[position()=1]/@name"
340+
val xpTextPath = "//resources/string[position()=1]/text()"
341+
342+
Assert.assertEquals(
343+
expectedKey,
344+
xp.evaluate(xpNamePath, splitTranslationXmlMap.getValue(allRegexString)).trim())
345+
Assert.assertEquals(
346+
expectedKey,
347+
xp.evaluate(xpNamePath, splitTranslationXmlMap.getValue(tabletRegexString)).trim())
348+
349+
Assert.assertEquals(
350+
"\"$expectedKey\"",
351+
xp.evaluate(xpTextPath, splitTranslationXmlMap.getValue(allRegexString)).trim())
352+
Assert.assertEquals(
353+
"\"${expectedKey}_tablet\"",
354+
xp.evaluate(xpTextPath, splitTranslationXmlMap.getValue(tabletRegexString)).trim())
355+
356+
}
357+
336358
@Test
337359
fun `Splitting tablet translation strings with plurals works`() {
338360
// Test complete Xml
@@ -370,5 +392,5 @@ class XmlPostProcessorTest {
370392
DocumentBuilderFactory.newInstance()
371393
.newDocumentBuilder()
372394
.parse(this.byteInputStream(Charsets.UTF_8))
373-
.dumpToString()
395+
.toAndroidXmlString()
374396
}

0 commit comments

Comments
 (0)