Skip to content

Commit 3fe88ce

Browse files
authored
Rewrite the XML parsing logic (#78)
1 parent 03a8cdf commit 3fe88ce

21 files changed

+1979
-963
lines changed

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
.idea/**/dictionaries
1414
.idea/**/shelf
1515
.idea/**/runConfigurations.xml
16+
.idea/**/other.xml
1617
.idea/kotlinc.xml
1718

1819
# Generated files

‎.idea/migrations.xml‎

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎CHANGELOG.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2424
### Added
2525
- No new features!
2626
### Changed
27-
- No changed features!
27+
- Refactor the whole parsing logic to better handle input and improve performance.
2828
### Deprecated
2929
- No deprecated features!
3030
### Removed

‎build.gradle.kts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,13 @@ dependencies {
5050

5151
compileOnly(libs.android.buildTools)
5252

53+
5354
implementation(libs.kotlin.stdlib)
5455
implementation(libs.bundles.moshi)
5556
implementation(libs.bundles.retrofit)
5657
implementation(libs.bundles.okhttp3)
5758
implementation(libs.dotenvKotlin)
59+
implementation(libs.xerces)
5860

5961
testImplementation(gradleTestKit())
6062
testImplementation(kotlin("test"))

‎gradle/libs.versions.toml‎

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,27 @@ android-buildTools = "com.android.tools.build:gradle:8.1.2"
1313
kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
1414
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
1515

16-
moshi = { group = "com.squareup.moshi", name = "moshi", version.ref = "moshi"}
17-
moshi-kotlin = { group = "com.squareup.moshi", name = "moshi-kotlin", version.ref = "moshi"}
18-
moshi-adapters = { group = "com.squareup.moshi", name = "moshi-adapters", version.ref = "moshi"}
16+
moshi = { group = "com.squareup.moshi", name = "moshi", version.ref = "moshi" }
17+
moshi-kotlin = { group = "com.squareup.moshi", name = "moshi-kotlin", version.ref = "moshi" }
18+
moshi-adapters = { group = "com.squareup.moshi", name = "moshi-adapters", version.ref = "moshi" }
1919

20-
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit"}
21-
retrofit-converterMoshi = { group = "com.squareup.retrofit2", name = "converter-moshi", version.ref = "retrofit"}
20+
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
21+
retrofit-converterMoshi = { group = "com.squareup.retrofit2", name = "converter-moshi", version.ref = "retrofit" }
2222

23-
okhttp3 = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp"}
24-
okhttp3-loggingInterceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp"}
23+
okhttp3 = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
24+
okhttp3-loggingInterceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
2525

2626
dotenvKotlin = "io.github.cdimascio:dotenv-kotlin:6.4.1"
2727

28+
xerces = { module = "xerces:xercesImpl", version = "2.12.2" }
29+
2830
junit = "junit:junit:4.13.2"
2931

30-
detekt-formatting = { group = "io.gitlab.arturbosch.detekt", name = "detekt-formatting", version.ref = "detekt"}
32+
detekt-formatting = { group = "io.gitlab.arturbosch.detekt", name = "detekt-formatting", version.ref = "detekt" }
3133

3234
[plugins]
3335
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
34-
gitVersionGradle = { id = "com.gladed.androidgitversion", version = "0.4.14"}
36+
gitVersionGradle = { id = "com.gladed.androidgitversion", version = "0.4.14" }
3537
versionsUpdate = { id = "com.github.ben-manes.versions", version = "0.49.0" }
3638

3739
[bundles]

‎src/main/kotlin/com/hyperdevs/poeditor/gradle/PoEditorPlugin.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import com.android.build.gradle.AppPlugin
2424
import com.android.build.gradle.LibraryExtension
2525
import com.android.build.gradle.LibraryPlugin
2626
import com.android.build.gradle.internal.dsl.BaseAppModuleExtension
27-
import com.hyperdevs.poeditor.gradle.ktx.registerNewTask
27+
import com.hyperdevs.poeditor.gradle.extensions.registerNewTask
2828
import com.hyperdevs.poeditor.gradle.tasks.ImportPoEditorStringsTask
2929
import com.hyperdevs.poeditor.gradle.utils.*
3030
import org.gradle.api.NamedDomainObjectContainer

‎src/main/kotlin/com/hyperdevs/poeditor/gradle/PoEditorStringsImporter.kt‎

Lines changed: 44 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
package com.hyperdevs.poeditor.gradle
2020

2121
import com.hyperdevs.poeditor.gradle.adapters.PoEditorDateJsonAdapter
22-
import com.hyperdevs.poeditor.gradle.ktx.downloadUrlToString
22+
import com.hyperdevs.poeditor.gradle.extensions.downloadUrlToString
2323
import com.hyperdevs.poeditor.gradle.network.PoEditorApiControllerImpl
2424
import com.hyperdevs.poeditor.gradle.network.api.ExportType
2525
import com.hyperdevs.poeditor.gradle.network.api.FilterType
@@ -28,8 +28,9 @@ import com.hyperdevs.poeditor.gradle.network.api.PoEditorApi
2828
import com.hyperdevs.poeditor.gradle.network.api.ProjectLanguage
2929
import com.hyperdevs.poeditor.gradle.utils.TABLET_REGEX_STRING
3030
import com.hyperdevs.poeditor.gradle.utils.logger
31-
import com.hyperdevs.poeditor.gradle.xml.AndroidXmlWriter
32-
import com.hyperdevs.poeditor.gradle.xml.XmlPostProcessor
31+
import com.hyperdevs.poeditor.gradle.xml.StringsXmlPostProcessor
32+
import com.hyperdevs.poeditor.gradle.xml.StringsXmlWriter
33+
import com.hyperdevs.poeditor.gradle.xml.parser.SaxStringsXmlDocumentParser
3334
import com.squareup.moshi.Moshi
3435
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
3536
import okhttp3.HttpUrl.Companion.toHttpUrl
@@ -59,12 +60,14 @@ object PoEditorStringsImporter {
5960
.connectTimeout(CONNECT_TIMEOUT_SECONDS, TimeUnit.SECONDS)
6061
.readTimeout(READ_TIMEOUT_SECONDS, TimeUnit.SECONDS)
6162
.writeTimeout(WRITE_TIMEOUT_SECONDS, TimeUnit.SECONDS)
62-
.addInterceptor(HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
63-
override fun log(message: String) {
64-
logger.debug(message)
65-
}
66-
})
67-
.setLevel(HttpLoggingInterceptor.Level.BODY))
63+
.addInterceptor(
64+
HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
65+
override fun log(message: String) {
66+
logger.debug(message)
67+
}
68+
})
69+
.setLevel(HttpLoggingInterceptor.Level.BODY)
70+
)
6871
.build()
6972

7073
private val retrofit = Retrofit.Builder()
@@ -75,27 +78,25 @@ object PoEditorStringsImporter {
7578

7679
private val poEditorApi: PoEditorApi = retrofit.create(PoEditorApi::class.java)
7780

78-
private val xmlPostProcessor = XmlPostProcessor()
79-
80-
private val xmlWriter = AndroidXmlWriter()
81-
8281
/**
8382
* Imports PoEditor strings.
8483
*/
85-
@Suppress("LongParameterList")
86-
fun importPoEditorStrings(apiToken: String,
87-
projectId: Int,
88-
defaultLang: String,
89-
resDirPath: String,
90-
filters: List<FilterType>,
91-
order: OrderType,
92-
tags: List<String>,
93-
languageValuesOverridePathMap: Map<String, String>,
94-
minimumTranslationPercentage: Int,
95-
resFileName: String,
96-
unquoted: Boolean,
97-
unescapeHtmlTags: Boolean,
98-
untranslatableStringsRegex: String?) {
84+
@Suppress("LongParameterList", "LongMethod")
85+
fun importPoEditorStrings(
86+
apiToken: String,
87+
projectId: Int,
88+
defaultLang: String,
89+
resDirPath: String,
90+
filters: List<FilterType>,
91+
order: OrderType,
92+
tags: List<String>,
93+
languageValuesOverridePathMap: Map<String, String>,
94+
minimumTranslationPercentage: Int,
95+
resFileName: String,
96+
unquoted: Boolean,
97+
unescapeHtmlTags: Boolean,
98+
untranslatableStringsRegex: String?
99+
) {
99100
try {
100101
val poEditorApiController = PoEditorApiControllerImpl(apiToken, moshi, poEditorApi)
101102

@@ -122,6 +123,8 @@ object PoEditorStringsImporter {
122123
logger.lifecycle("Using the following filters for all languages: $filters")
123124
}
124125

126+
val stringsXmlDocumentParser = SaxStringsXmlDocumentParser()
127+
125128
projectLanguages.minus(skippedLanguages).forEach { languageData ->
126129
val languageCode = languageData.code
127130

@@ -143,26 +146,31 @@ object PoEditorStringsImporter {
143146
val translationFile = okHttpClient.downloadUrlToString(translationFileUrl)
144147

145148
// Extract final files from downloaded translation XML
146-
val postProcessedXmlDocumentMap = xmlPostProcessor.postProcessTranslationXml(
147-
translationFile,
149+
val originalStringsXmlDocument = stringsXmlDocumentParser.deserialize(translationFile)
150+
151+
val postProcessedStringsXmlDocumentMap = StringsXmlPostProcessor.processTranslationXml(
152+
originalStringsXmlDocument,
148153
listOf(TABLET_REGEX_STRING),
149154
unescapeHtmlTags,
150-
untranslatableStringsRegex
155+
untranslatableStringsRegex?.toRegex()
151156
)
152157

153-
xmlWriter.saveXml(
158+
StringsXmlWriter.saveXml(
154159
resDirPath,
155160
resFileName,
156-
postProcessedXmlDocumentMap,
161+
postProcessedStringsXmlDocumentMap.mapValues { (_, document) ->
162+
stringsXmlDocumentParser.serialize(document)
163+
},
157164
defaultLang,
158165
languageCode,
159-
languageValuesOverridePathMap,
160-
unescapeHtmlTags
166+
languageValuesOverridePathMap
161167
)
162168
}
163169
} catch (e: Exception) {
164-
logger.error("An error happened when retrieving strings from project. " +
165-
"Please review the plug-in's input parameters and try again")
170+
logger.error(
171+
"An error happened when retrieving strings from project. " +
172+
"Please review the plug-in's input parameters and try again"
173+
)
166174
throw e
167175
}
168176
}

‎src/main/kotlin/com/hyperdevs/poeditor/gradle/ktx/OkHttpClientExtensions.kt‎ renamed to ‎src/main/kotlin/com/hyperdevs/poeditor/gradle/extensions/OkHttpClientExtensions.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
* limitations under the License.
1717
*/
1818

19-
package com.hyperdevs.poeditor.gradle.ktx
19+
package com.hyperdevs.poeditor.gradle.extensions
2020

2121
import okhttp3.OkHttpClient
2222
import okhttp3.Request

‎src/main/kotlin/com/hyperdevs/poeditor/gradle/ktx/ProjectExtensions.kt‎ renamed to ‎src/main/kotlin/com/hyperdevs/poeditor/gradle/extensions/ProjectExtensions.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
* limitations under the License.
1717
*/
1818

19-
package com.hyperdevs.poeditor.gradle.ktx
19+
package com.hyperdevs.poeditor.gradle.extensions
2020

2121
import org.gradle.api.Project
2222
import org.gradle.api.Task
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2021 HyperDevs
3+
*
4+
* Copyright 2020 BQ
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package com.hyperdevs.poeditor.gradle.extensions
20+
21+
private val cdataRegex = Regex("^<!\\[CDATA\\[(.*)]]>$", RegexOption.DOT_MATCHES_ALL)
22+
23+
/**
24+
* Unescapes HTML tags from string.
25+
*/
26+
fun String.unescapeHtmlTags() = this
27+
.replace("&lt;", "<")
28+
.replace("&gt;", ">")
29+
.replace("&amp;", "&")
30+
.replace("&apos;", "'")
31+
.replace("&quot;", "\"")
32+
33+
/**
34+
* Returns if the given string is a CDATA string.
35+
*/
36+
fun String.isCData() = cdataRegex.matches(this.trim())
37+
38+
/**
39+
* Returns the CDATA content from a given CDATA string.
40+
*
41+
* Returns null if the string is not a CDATA string.
42+
*/
43+
fun String.getCDataContent() = cdataRegex.matchEntire(this.trim())?.groups?.get(1)?.value
44+
45+
/**
46+
* Replaces the CDATA contents of a given string.
47+
*
48+
* Returns the same string if it's not a CDATA string.
49+
*/
50+
fun String.replaceCDataContent(newContent: String): String {
51+
return if (this.isCData()) {
52+
"<![CDATA[$newContent]]>"
53+
} else {
54+
this
55+
}
56+
}

0 commit comments

Comments
 (0)