You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by di...@apache.org on 2022/10/25 14:15:18 UTC
[sling-org-apache-sling-scripting-sightly] branch master updated: SLING-10654: add support for ICU MessageFormat (#12)
This is an automated email from the ASF dual-hosted git repository.
diru pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-scripting-sightly.git
The following commit(s) were added to refs/heads/master by this push:
new 6cc7704 SLING-10654: add support for ICU MessageFormat (#12)
6cc7704 is described below
commit 6cc7704f0a2c361708a052df082629cb301e6d9a
Author: Dirk Rudolph <di...@apache.org>
AuthorDate: Tue Oct 25 16:15:11 2022 +0200
SLING-10654: add support for ICU MessageFormat (#12)
This adds an optional dependency to icu4j. If they do not exist in the runtime the logic falls back to the existing behaviour.
---
bnd.bnd | 2 +
pom.xml | 8 +++
.../engine/extension/FormatFilterExtension.java | 62 ++++++++++++-------
.../extension/FormatFilterExtensionTest.java | 70 ++++++++++++++++++++++
4 files changed, 122 insertions(+), 20 deletions(-)
diff --git a/bnd.bnd b/bnd.bnd
index 768c55b..c4c672d 100644
--- a/bnd.bnd
+++ b/bnd.bnd
@@ -12,4 +12,6 @@ Import-Package: org.apache.sling.scripting.sightly.compiler.*;resolution:=op
org.apache.sling.commons.compiler.*;resolution:=optional, \\
org.apache.sling.commons.classloader.*;resolution:=optional, \\
org.apache.sling.models.*;resolution:=optional, \\
+ org.apache.sling.models.*;resolution:=optional, \\
+ com.ibm.icu.*;resolution:=optional, \\
*
diff --git a/pom.xml b/pom.xml
index 68b3366..e7b1fb4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -304,6 +304,14 @@
<scope>provided</scope>
</dependency>
+ <!-- icu -->
+ <dependency>
+ <groupId>com.ibm.icu</groupId>
+ <artifactId>icu4j</artifactId>
+ <version>69.1</version>
+ <scope>provided</scope>
+ </dependency>
+
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtension.java b/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtension.java
index ff438de..c7bd69c 100644
--- a/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtension.java
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtension.java
@@ -41,6 +41,8 @@ import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.ibm.icu.text.MessageFormat;
+
@Component(
service = RuntimeExtension.class,
property = {
@@ -60,6 +62,9 @@ public class FormatFilterExtension implements RuntimeExtension {
private static final Logger LOG = LoggerFactory.getLogger(FormatFilterExtension.class);
private static final Pattern PLACEHOLDER_REGEX = Pattern.compile("\\{\\d+}");
+ private static final Pattern COMPLEX_PLACEHOLDER_REGEX = Pattern.compile("\\{\\d+,[^}]+}");
+
+ protected boolean hasIcuSupport = true;
@Override
public Object call(final RenderContext renderContext, Object... arguments) {
@@ -70,18 +75,17 @@ public class FormatFilterExtension implements RuntimeExtension {
String formattingType = runtimeObjectModel.toString(options.get(TYPE_OPTION));
Object formatObject = options.get(FORMAT_OPTION);
- boolean hasPlaceHolders = PLACEHOLDER_REGEX.matcher(source).find();
+ boolean hasPlaceHolders = PLACEHOLDER_REGEX.matcher(source).find() || COMPLEX_PLACEHOLDER_REGEX.matcher(source).find();
if (STRING_FORMAT_TYPE.equals(formattingType)) {
- return getFormattedString(runtimeObjectModel, source, formatObject);
+ return getFormattedString(runtimeObjectModel, source, options, formatObject);
} else if (DATE_FORMAT_TYPE.equals(formattingType) || (!hasPlaceHolders && runtimeObjectModel.isDate(formatObject))) {
return getDateFormattedString(runtimeObjectModel, source, options, formatObject);
} else if (NUMBER_FORMAT_TYPE.equals(formattingType) || (!hasPlaceHolders && runtimeObjectModel.isNumber(formatObject))) {
return getNumberFormattedString(runtimeObjectModel, source, options, formatObject);
}
if (hasPlaceHolders) {
- return getFormattedString(runtimeObjectModel, source, formatObject);
+ return getFormattedString(runtimeObjectModel, source, options, formatObject);
}
-
try {
// try to parse as DateTimeFormatter
DateTimeFormatter.ofPattern(source);
@@ -96,12 +100,22 @@ public class FormatFilterExtension implements RuntimeExtension {
} catch (IllegalArgumentException e) {
// ignore
}
- return getFormattedString(runtimeObjectModel, source, formatObject);
+ return getFormattedString(runtimeObjectModel, source, options, formatObject);
}
- private Object getFormattedString(RuntimeObjectModel runtimeObjectModel, String source, Object formatObject) {
+ private Object getFormattedString(RuntimeObjectModel runtimeObjectModel, String source, Map<String, Object> options,
+ Object formatObject) {
Object[] params = decodeParams(runtimeObjectModel, formatObject);
- return formatString(runtimeObjectModel, source, params);
+ if (COMPLEX_PLACEHOLDER_REGEX.matcher(source).find()) {
+ if (hasIcuSupport) {
+ Locale locale = getLocale(runtimeObjectModel, options);
+ return formatStringIcu(source, locale, params);
+ } else {
+ return null;
+ }
+ } else {
+ return formatString(runtimeObjectModel, source, params);
+ }
}
private String getNumberFormattedString(RuntimeObjectModel runtimeObjectModel, String source, Map<String, Object> options,
@@ -157,25 +171,33 @@ public class FormatFilterExtension implements RuntimeExtension {
Matcher matcher = PLACEHOLDER_REGEX.matcher(source);
StringBuilder builder = new StringBuilder();
int lastPos = 0;
- boolean matched = true;
- while (matched) {
- matched = matcher.find();
- if (matched) {
- String group = matcher.group();
- int paramIndex = Integer.parseInt(group.substring(1, group.length() - 1));
- String replacement = toString(runtimeObjectModel, params, paramIndex);
- int matchStart = matcher.start();
- int matchEnd = matcher.end();
- builder.append(source, lastPos, matchStart).append(replacement);
- lastPos = matchEnd;
- }
+ while (matcher.find()) {
+ String group = matcher.group();
+ int paramIndex = Integer.parseInt(group.substring(1, group.length() - 1));
+ String replacement = toString(runtimeObjectModel, params, paramIndex);
+ int matchStart = matcher.start();
+ int matchEnd = matcher.end();
+ builder.append(source, lastPos, matchStart).append(replacement);
+ lastPos = matchEnd;
}
builder.append(source, lastPos, source.length());
return builder.toString();
}
+ private String formatStringIcu(String source, Locale locale, Object[] params) {
+ try {
+ MessageFormat messageFormat = locale != null ? new MessageFormat(source, locale) : new MessageFormat(source);
+ return messageFormat.format(params);
+ } catch (NoClassDefFoundError ex) {
+ LOG.trace("ICU4J not found", ex);
+ hasIcuSupport = false;
+ return null;
+ }
+ }
+
private String toString(RuntimeObjectModel runtimeObjectModel, Object[] params, int index) {
- if (index >= 0 && index < params.length) {
+ // index can only be a signed integer according to FormatFilterExtension#PLACEHOLDER_REGEX
+ if (index < params.length) {
return runtimeObjectModel.toString(params[index]);
}
return "";
diff --git a/src/test/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtensionTest.java b/src/test/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtensionTest.java
index 9c33bc6..82c3a19 100644
--- a/src/test/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtensionTest.java
+++ b/src/test/java/org/apache/sling/scripting/sightly/impl/engine/extension/FormatFilterExtensionTest.java
@@ -20,6 +20,7 @@ package org.apache.sling.scripting.sightly.impl.engine.extension;
import java.time.LocalDateTime;
import java.time.ZoneId;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
@@ -61,6 +62,23 @@ public class FormatFilterExtensionTest {
.atZone(ZoneId.of("UTC"))
.toInstant());
+ @Test
+ public void testNoop() {
+ // constructed case, it is actually difficult to find a pattern that is not a date-time or decimal number format
+ assertEquals("0-#",
+ subject.call(renderContext, "0-#", Collections.singletonMap(FormatFilterExtension.FORMAT, "ignored")));
+ }
+
+ @Test
+ public void testNoopNoParameters() {
+ assertNull("0-#", subject.call(renderContext, "0-#", Collections.emptyMap()));
+ }
+
+ @Test(expected = SightlyException.class)
+ public void testMissingOptions() {
+ subject.call(renderContext, "fails");
+ }
+
@Test
public void testDateFormatNull() {
assertNull(subject.call(renderContext, "default", new HashMap<String, Object>() {{
@@ -187,4 +205,56 @@ public class FormatFilterExtensionTest {
}
assertEquals(expected, subject.call(renderContext, format, options));
}
+
+ @Test
+ public void testSimpleStringFormat() {
+ Object result = subject.call(renderContext,
+ "This {0} a {1} format", Collections.singletonMap("format", Arrays.asList("is", "simple")));
+ assertEquals("This is a simple format", result);
+ }
+
+ @Test
+ public void testStringFormat() {
+ Object result = subject.call(renderContext,
+ "This {0} a {1} format", new HashMap<String, Object>() {{
+ put(FormatFilterExtension.FORMAT, Arrays.asList("is", "simple"));
+ put(FormatFilterExtension.TYPE_OPTION, FormatFilterExtension.STRING_FORMAT_TYPE);
+ }});
+ assertEquals("This is a simple format", result);
+ }
+
+ @Test
+ public void testSimpleStringFormatWithSingleParameter() {
+ Object result = subject.call(renderContext,
+ "Hello {0}", Collections.singletonMap(FormatFilterExtension.FORMAT, "world"));
+ assertEquals("Hello world", result);
+ }
+
+ @Test
+ public void testComplexStringFormatNoSimplePlaceholderWithLocale() {
+ Object result = subject.call(renderContext,
+ "This query has {0,plural,zero {# results} one {# result} other {# results}}",
+ new HashMap<String, Object>() {{
+ put(FormatFilterExtension.FORMAT, Collections.singletonList(7));
+ put(FormatFilterExtension.LOCALE_OPTION, "en_US");
+ }});
+ assertEquals("This query has 7 results", result);
+ }
+
+ @Test
+ public void testComplexStringFormatWithSimplePlaceholderNoLocale() {
+ Object result = subject.call(renderContext,
+ "This {0} has {1,plural,zero {# results} one {# result} other {# results}}",
+ Collections.singletonMap(FormatFilterExtension.FORMAT, Arrays.asList("query", 7)));
+ assertEquals("This query has 7 results", result);
+ }
+
+ @Test
+ public void testComplexStringFormatWithSimplePlaceholderNoIcuSupport() {
+ subject.hasIcuSupport = false;
+ Object result = subject.call(renderContext,
+ "This {0} has {1,plural,zero {{1} results} one {{1} result} other {{1} results}}",
+ Collections.singletonMap(FormatFilterExtension.FORMAT, Arrays.asList("query", 7)));
+ assertNull(result);
+ }
}