Improve escaping in i18n-maven-plugin (#2968)

* Also escape special characters in state option values

And escaped space, = and : symbols (all legal according to properties documentation).

Signed-off-by: Hilbrand Bouwkamp <hilbrand@h72.nl>

* Properly escape : and \ in option

Signed-off-by: Hilbrand Bouwkamp <hilbrand@h72.nl>
pull/2973/head
Hilbrand Bouwkamp 2022-05-19 18:31:25 +02:00 committed by GitHub
parent b52d9f2e4f
commit 3da9570fe7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 24 additions and 6 deletions

View File

@ -98,7 +98,7 @@ public class PropertiesToTranslationsConverter {
int index = line.indexOf("=");
if (index == -1) {
log.warn("Ignoring invalid translation key/value pair: " + line);
} else {
} else if (!(index > 0 && line.charAt(index - 1) == '\\')) { // ignore escaped =
if (entriesBuilder == null) {
entriesBuilder = Stream.builder();
}

View File

@ -20,7 +20,7 @@ import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import java.util.stream.Stream.Builder;
@ -51,6 +51,8 @@ import org.openhab.core.types.StateDescription;
@NonNullByDefault
public class XmlToTranslationsConverter {
private static final Pattern OPTION_ESCAPE_PATTERN = Pattern.compile("([ :=])");
public Translations convert(BundleInfo bundleInfo) {
String bindingId = bundleInfo.getBindingId();
return bindingId.isBlank() ? configTranslations(bundleInfo) : bindingTranslations(bundleInfo);
@ -271,7 +273,8 @@ public class XmlToTranslationsConverter {
if (stateDescription != null) {
stateDescription.getOptions().stream()
.map(option -> entry(
String.format("%s.%s.state.option.%s", keyPrefix, channelId, option.getValue()),
String.format("%s.%s.state.option.%s", keyPrefix, channelId,
OPTION_ESCAPE_PATTERN.matcher(option.getValue()).replaceAll("\\\\$1")),
option.getLabel()))
.forEach(entriesBuilder::add);
@ -354,7 +357,7 @@ public class XmlToTranslationsConverter {
parameter.getOptions().stream()
.map(option -> entry(
String.format("%s.option.%s", parameterKeyPrefix,
option.getValue().replaceAll(" ", Matcher.quoteReplacement("\\ "))),
OPTION_ESCAPE_PATTERN.matcher(option.getValue()).replaceAll("\\\\$1")),
option.getLabel()))
.forEach(entriesBuilder::add);

View File

@ -39,7 +39,7 @@ public class PropertiesToTranslationsConverterTest {
assertThat(translations.hasTranslations(), is(true));
assertThat(translations.sections.size(), is(6));
assertThat(translations.keysStream().count(), is(32L));
assertThat(translations.keysStream().count(), is(34L));
String lines = translations.linesStream().collect(Collectors.joining(System.lineSeparator()));
assertThat(lines, containsString("# binding"));

View File

@ -42,7 +42,7 @@ public class XmlToTranslationsConverterTest {
assertThat(translations.hasTranslations(), is(true));
assertThat(translations.sections.size(), is(7));
assertThat(translations.keysStream().count(), is(31L));
assertThat(translations.keysStream().count(), is(34L));
String lines = translations.linesStream().collect(Collectors.joining(System.lineSeparator()));
assertThat(lines, containsString("# binding"));
@ -55,6 +55,9 @@ public class XmlToTranslationsConverterTest {
"channel-type.acmeweather.time-stamp.state.pattern = %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS"));
assertThat(lines,
containsString("thing-type.config.acmeweather.weather.language.option.de\\ DE = German (Germany)"));
assertThat(lines, containsString("channel-type.acmeweather.temperature.state.option.VALUE\\ 1 = My label 1"));
assertThat(lines, containsString("channel-type.acmeweather.temperature.state.option.VALUE\\:2 = My label 2"));
assertThat(lines, containsString("channel-type.acmeweather.temperature.state.option.VALUE\\=3 = My label 3"));
}
@Test

View File

@ -40,6 +40,9 @@ channel-group-type.acmeweather.forecast.channel.minTemperature.description = Min
channel-type.acmeweather.temperature.label = Temperature
channel-type.acmeweather.temperature.description = Temperature in degrees Celsius (metric) or Fahrenheit (imperial).
channel-type.acmeweather.temperature.state.option.VALUE = My label
channel-type.acmeweather.temperature.state.option.VALUE\ 1 = My label 1
channel-type.acmeweather.temperature.state.option.VALUE\:2 = My label 2
channel-type.acmeweather.temperature.state.option.VALUE\=3 = My label 3
channel-type.acmeweather.time-stamp.label = Observation Time
channel-type.acmeweather.time-stamp.description = Time of data observation.
channel-type.acmeweather.time-stamp.state.pattern = %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS

View File

@ -40,6 +40,9 @@ channel-group-type.acmeweather.forecast.channel.minTemperature.description = Min
channel-type.acmeweather.temperature.label = Temperature
channel-type.acmeweather.temperature.description = Temperature in degrees Celsius (metric) or Fahrenheit (imperial).
channel-type.acmeweather.temperature.state.option.VALUE = My label
channel-type.acmeweather.temperature.state.option.VALUE\ 1 = My label 1
channel-type.acmeweather.temperature.state.option.VALUE\:2 = My label 2
channel-type.acmeweather.temperature.state.option.VALUE\=3 = My label 3
channel-type.acmeweather.time-stamp.label = Observation Time
channel-type.acmeweather.time-stamp.description = Time of data observation.
channel-type.acmeweather.time-stamp.state.pattern = %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS

View File

@ -40,6 +40,9 @@ channel-group-type.acmeweather.forecast.channel.minTemperature.description = Min
channel-type.acmeweather.temperature.label = Temperatur
channel-type.acmeweather.temperature.description = Temperatur in Grad Celsius (Metrisch) oder Fahrenheit (Imperial).
channel-type.acmeweather.temperature.state.option.VALUE = Mein String
channel-type.acmeweather.temperature.state.option.VALUE\ 1 = Mein String 1
channel-type.acmeweather.temperature.state.option.VALUE\:2 = Mein String 2
channel-type.acmeweather.temperature.state.option.VALUE\=3 = Mein String 3
channel-type.acmeweather.time-stamp.label = Letzte Messung
channel-type.acmeweather.time-stamp.description = Zeigt den Zeitpunkt der letzten Messung an.
channel-type.acmeweather.time-stamp.state.pattern = %1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS

View File

@ -29,6 +29,9 @@
<state pattern="%d degree Celsius">
<options>
<option value="VALUE">My label</option>
<option value="VALUE 1">My label 1</option>
<option value="VALUE:2">My label 2</option>
<option value="VALUE=3">My label 3</option>
</options>
</state>
</channel-type>