diff --git a/.gitignore b/.gitignore index d926477..4b1554c 100644 --- a/.gitignore +++ b/.gitignore @@ -127,4 +127,7 @@ gradle-app.setting # End of https://www.gitignore.io/api/java,gradle,intellij+iml # Output directory for tests -/output \ No newline at end of file +/output + +# Donenv configuration file +.env \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 705f48a..1b5df93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - No security issues fixed! --> +## [Unreleased] +### Added +- No new features! +### Changed +- No changed features! +### Deprecated +- No deprecated features! +### Removed +- No removed features! +### Fixed +- No fixed issues! +### Security +- No security issues fixed! + +## [1.4.2] - 2020-12-29 +### Added +- [Dotenv support](https://github.com/cdimascio/dotenv-kotlin) for the `Main.kt` file for easier local development. +### Fixed +- Fix HTML tags being escaped when parsing. + ## [1.4.1] - 2020-12-28 ### Fixed - Fix percent symbols not being properly escaped (again) by processing the XML file line by line. diff --git a/build.gradle.kts b/build.gradle.kts index dbdc9d8..4b8b331 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -68,6 +68,8 @@ dependencies { implementation("com.squareup.okhttp3:logging-interceptor:4.7.2") implementation("com.squareup.okhttp3:okhttp:4.7.2") + implementation("io.github.cdimascio:dotenv-kotlin:6.2.2") + testImplementation(gradleTestKit()) testImplementation(kotlin("test")) testImplementation("junit:junit:4.13") diff --git a/src/main/kotlin/com/bq/poeditor/gradle/Main.kt b/src/main/kotlin/com/bq/poeditor/gradle/Main.kt index 3b75ee9..6a4d80f 100644 --- a/src/main/kotlin/com/bq/poeditor/gradle/Main.kt +++ b/src/main/kotlin/com/bq/poeditor/gradle/Main.kt @@ -16,15 +16,21 @@ package com.bq.poeditor.gradle +import io.github.cdimascio.dotenv.Dotenv + /** * Only for testing purposes. + * + * Declare the variables API_TOKEN, PROJECT_ID, RES_DIR_PATH and DEFAULT_LANGUAGE in /.env */ @Suppress("MagicNumber") fun main() { - val apiToken = "your_api_token" - val projectId = 1234567890 - val resDirPath = "output" - val defaultLanguage = "en" + val dotenv: Dotenv = Dotenv.load() + + val apiToken = dotenv.get("API_TOKEN", "") + val projectId = dotenv.get("PROJECT_ID", "-1").toInt() + val resDirPath = dotenv.get("RES_DIR_PATH", "") + val defaultLanguage = dotenv.get("DEFAULT_LANGUAGE", "") PoEditorStringsImporter.importPoEditorStrings(apiToken, projectId, defaultLanguage, resDirPath) } \ No newline at end of file diff --git a/src/main/kotlin/com/bq/poeditor/gradle/ktx/DocumentExtensions.kt b/src/main/kotlin/com/bq/poeditor/gradle/ktx/DocumentExtensions.kt index 8a6632d..d15e30b 100644 --- a/src/main/kotlin/com/bq/poeditor/gradle/ktx/DocumentExtensions.kt +++ b/src/main/kotlin/com/bq/poeditor/gradle/ktx/DocumentExtensions.kt @@ -34,9 +34,9 @@ fun String.toDocument(): Document = .parse(this.byteInputStream(DEFAULT_ENCODING)) /** - * Convers a [Document] into a formatted [String]. + * Converts a [Document] into a formatted [String]. */ -fun Document.dumpToString(): String { +fun Document.toAndroidXmlString(): String { val registry = DOMImplementationRegistry.newInstance() val impl = registry.getDOMImplementation("LS") as DOMImplementationLS val output = impl.createLSOutput().apply { encoding = "UTF-8" } @@ -51,5 +51,5 @@ fun Document.dumpToString(): String { serializer.write(this, output) - return writer.toString() + return writer.toString().unescapeHtmlTags() } \ No newline at end of file diff --git a/src/main/kotlin/com/bq/poeditor/gradle/ktx/StringExtensions.kt b/src/main/kotlin/com/bq/poeditor/gradle/ktx/StringExtensions.kt new file mode 100644 index 0000000..53f07ed --- /dev/null +++ b/src/main/kotlin/com/bq/poeditor/gradle/ktx/StringExtensions.kt @@ -0,0 +1,24 @@ +/* + * Copyright 2020 BQ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.bq.poeditor.gradle.ktx + +private val UNESCAPED_HTML_TAGS_REGEX = Regex("""<([^.]*?)>""") + +/** + * Unescapes HTML tags from string. + */ +fun String.unescapeHtmlTags() = this.replace(UNESCAPED_HTML_TAGS_REGEX, "<$1>") diff --git a/src/main/kotlin/com/bq/poeditor/gradle/xml/AndroidXmlWriter.kt b/src/main/kotlin/com/bq/poeditor/gradle/xml/AndroidXmlWriter.kt index db8275f..5ac73db 100644 --- a/src/main/kotlin/com/bq/poeditor/gradle/xml/AndroidXmlWriter.kt +++ b/src/main/kotlin/com/bq/poeditor/gradle/xml/AndroidXmlWriter.kt @@ -16,7 +16,7 @@ package com.bq.poeditor.gradle.xml -import com.bq.poeditor.gradle.ktx.dumpToString +import com.bq.poeditor.gradle.ktx.toAndroidXmlString import com.bq.poeditor.gradle.utils.TABLET_REGEX_STRING import com.bq.poeditor.gradle.utils.TABLET_RES_FOLDER_SUFFIX import com.bq.poeditor.gradle.utils.logger @@ -68,6 +68,6 @@ class AndroidXmlWriter { } } - File(stringsFolderFile, "strings.xml").writeText(document.dumpToString()) + File(stringsFolderFile, "strings.xml").writeText(document.toAndroidXmlString()) } } \ No newline at end of file diff --git a/src/main/kotlin/com/bq/poeditor/gradle/xml/XmlPostProcessor.kt b/src/main/kotlin/com/bq/poeditor/gradle/xml/XmlPostProcessor.kt index 260ed88..1d59532 100644 --- a/src/main/kotlin/com/bq/poeditor/gradle/xml/XmlPostProcessor.kt +++ b/src/main/kotlin/com/bq/poeditor/gradle/xml/XmlPostProcessor.kt @@ -16,8 +16,9 @@ package com.bq.poeditor.gradle.xml -import com.bq.poeditor.gradle.ktx.dumpToString +import com.bq.poeditor.gradle.ktx.toAndroidXmlString import com.bq.poeditor.gradle.ktx.toDocument +import com.bq.poeditor.gradle.ktx.unescapeHtmlTags import com.bq.poeditor.gradle.utils.ALL_REGEX_STRING import org.w3c.dom.Document import org.w3c.dom.Element @@ -62,7 +63,7 @@ class XmlPostProcessor { formatTranslationXmlDocument(translationFileXmlDocument, translationFileXmlDocument.childNodes) - return translationFileXmlDocument.dumpToString() + return translationFileXmlDocument.toAndroidXmlString() } /** @@ -88,8 +89,7 @@ class XmlPostProcessor { return translationString // Replace % with %% if variables are found .let { if (containsVariables) it.replace("%", "%%") else it } - // Replace < with < and > with > - .replace("<", "<").replace(">", ">") + .unescapeHtmlTags() // Replace placeholders from {{variable}} to %1$s format. .replace(VARIABLE_REGEX, placeholderTransform) } diff --git a/src/test/kotlin/com/bq/poeditor/gradle/xml/XmlPostProcessorTest.kt b/src/test/kotlin/com/bq/poeditor/gradle/xml/XmlPostProcessorTest.kt index cec3394..722f1b6 100644 --- a/src/test/kotlin/com/bq/poeditor/gradle/xml/XmlPostProcessorTest.kt +++ b/src/test/kotlin/com/bq/poeditor/gradle/xml/XmlPostProcessorTest.kt @@ -16,7 +16,7 @@ package com.bq.poeditor.gradle.xml -import com.bq.poeditor.gradle.ktx.dumpToString +import com.bq.poeditor.gradle.ktx.toAndroidXmlString import com.bq.poeditor.gradle.utils.ALL_REGEX_STRING import com.bq.poeditor.gradle.utils.TABLET_REGEX_STRING import org.junit.Assert @@ -217,46 +217,6 @@ class XmlPostProcessorTest { Assert.assertEquals(expectedResult, xmlPostProcessor.formatTranslationXml(inputXmlString)) } - @Test - fun `Splitting tablet translation strings works`() { - // Test complete Xml - val expectedKey = "general_button_goTop" - val inputXmlString = """ - - - "$expectedKey" - - - "${expectedKey}_tablet" - - - """ - - val allRegexString = ALL_REGEX_STRING - val tabletRegexString = TABLET_REGEX_STRING - - val splitTranslationXmlMap = xmlPostProcessor.splitTranslationXml(inputXmlString, listOf(tabletRegexString)) - - // Check XML documents and see if the first string node has the proper name and the proper text with XPath - val xpNamePath = "//resources/string[position()=1]/@name" - val xpTextPath = "//resources/string[position()=1]/text()" - - Assert.assertEquals( - expectedKey, - xp.evaluate(xpNamePath, splitTranslationXmlMap.getValue(allRegexString)).trim()) - Assert.assertEquals( - expectedKey, - xp.evaluate(xpNamePath, splitTranslationXmlMap.getValue(tabletRegexString)).trim()) - - Assert.assertEquals( - "\"$expectedKey\"", - xp.evaluate(xpTextPath, splitTranslationXmlMap.getValue(allRegexString)).trim()) - Assert.assertEquals( - "\"${expectedKey}_tablet\"", - xp.evaluate(xpTextPath, splitTranslationXmlMap.getValue(tabletRegexString)).trim()) - - } - @Test fun `Postprocessing XML with plurals works`() { // Test complete Xml @@ -333,6 +293,68 @@ class XmlPostProcessorTest { Assert.assertEquals(expectedResult, xmlPostProcessor.formatTranslationXml(inputXmlString)) } + @Test + fun `Postprocessing XML with string HTML symbols works`() { + // Test complete Xml + val inputXmlString = """ + + + "Hello <b>{{name}}</b>" + + + """ + + val expectedResult = """ + + + "Hello %1${'$'}s" + + + """.formatXml() + + Assert.assertEquals(expectedResult, xmlPostProcessor.formatTranslationXml(inputXmlString)) + } + + @Test + fun `Splitting tablet translation strings works`() { + // Test complete Xml + val expectedKey = "general_button_goTop" + val inputXmlString = """ + + + "$expectedKey" + + + "${expectedKey}_tablet" + + + """ + + val allRegexString = ALL_REGEX_STRING + val tabletRegexString = TABLET_REGEX_STRING + + val splitTranslationXmlMap = xmlPostProcessor.splitTranslationXml(inputXmlString, listOf(tabletRegexString)) + + // Check XML documents and see if the first string node has the proper name and the proper text with XPath + val xpNamePath = "//resources/string[position()=1]/@name" + val xpTextPath = "//resources/string[position()=1]/text()" + + Assert.assertEquals( + expectedKey, + xp.evaluate(xpNamePath, splitTranslationXmlMap.getValue(allRegexString)).trim()) + Assert.assertEquals( + expectedKey, + xp.evaluate(xpNamePath, splitTranslationXmlMap.getValue(tabletRegexString)).trim()) + + Assert.assertEquals( + "\"$expectedKey\"", + xp.evaluate(xpTextPath, splitTranslationXmlMap.getValue(allRegexString)).trim()) + Assert.assertEquals( + "\"${expectedKey}_tablet\"", + xp.evaluate(xpTextPath, splitTranslationXmlMap.getValue(tabletRegexString)).trim()) + + } + @Test fun `Splitting tablet translation strings with plurals works`() { // Test complete Xml @@ -370,5 +392,5 @@ class XmlPostProcessorTest { DocumentBuilderFactory.newInstance() .newDocumentBuilder() .parse(this.byteInputStream(Charsets.UTF_8)) - .dumpToString() + .toAndroidXmlString() } \ No newline at end of file