This is an automated email from the ASF dual-hosted git repository. henrib pushed a commit to branch JEXL-455 in repository https://gitbox.apache.org/repos/asf/commons-jexl.git
commit 36dc64dfb9195a97bba67c99d7bf863140dc539f Author: Henrib <[email protected]> AuthorDate: Wed Feb 4 12:35:38 2026 +0100 JEXL-455: ignore whitespaces when creating embedded expressions (interpolations, templates); --- .../commons/jexl3/internal/TemplateEngine.java | 95 ++++++++++++---------- .../org/apache/commons/jexl3/Issues400Test.java | 33 ++++++++ 2 files changed, 83 insertions(+), 45 deletions(-) diff --git a/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java b/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java index 75b66e11..b4be891d 100644 --- a/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java +++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java @@ -1073,7 +1073,7 @@ public final class TemplateEngine extends JxltEngine { strb.delete(0, Integer.MAX_VALUE); state = ParseState.CONST; } - } else { + } else if (!isIgnorable(c)) { if (c == '{') { immediate1 += 1; } @@ -1086,54 +1086,54 @@ public final class TemplateEngine extends JxltEngine { // nested immediate in deferred; need to balance count of '{' & '}' // closing '}' switch (c) { - case '"': - case '\'': - strb.append(c); - column = StringParser.readString(strb, expr, column + 1, c); - continue; - case '{': - if (expr.charAt(column - 1) == immediateChar) { - inner1 += 1; - strb.deleteCharAt(strb.length() - 1); - nested = true; - } else { - deferred1 += 1; - strb.append(c); - } - continue; - case '}': - // balance nested immediate - if (deferred1 > 0) { - deferred1 -= 1; + case '"': + case '\'': strb.append(c); - } else if (inner1 > 0) { - inner1 -= 1; - } else { - // materialize the nested/deferred expr - final String src = escapeString(strb); - final JexlInfo srcInfo = info.at(lineno, column); - TemplateExpression dexpr; - if (nested) { - dexpr = new NestedExpression( - escapeString(expr.substring(inested, column + 1)), + column = StringParser.readString(strb, expr, column + 1, c); + continue; + case '{': + if (expr.charAt(column - 1) == immediateChar) { + inner1 += 1; + strb.deleteCharAt(strb.length() - 1); + nested = true; + } else { + deferred1 += 1; + strb.append(c); + } + continue; + case '}': + // balance nested immediate + if (deferred1 > 0) { + deferred1 -= 1; + strb.append(c); + } else if (inner1 > 0) { + inner1 -= 1; + } else if (!isIgnorable(c)) { + // materialize the nested/deferred expr + final String src = escapeString(strb); + final JexlInfo srcInfo = info.at(lineno, column); + TemplateExpression dexpr; + if (nested) { + dexpr = new NestedExpression( + escapeString(expr.substring(inested, column + 1)), + jexl.jxltParse(srcInfo, noscript, src, scope), + null); + } else { + dexpr = new DeferredExpression( + src, jexl.jxltParse(srcInfo, noscript, src, scope), null); - } else { - dexpr = new DeferredExpression( - src, - jexl.jxltParse(srcInfo, noscript, src, scope), - null); + } + builder.add(dexpr); + strb.delete(0, Integer.MAX_VALUE); + nested = false; + state = ParseState.CONST; } - builder.add(dexpr); - strb.delete(0, Integer.MAX_VALUE); - nested = false; - state = ParseState.CONST; - } - break; - default: - // do buildup expr - column = append(strb, expr, column, c); - break; + break; + default: + // do buildup expr + column = append(strb, expr, column, c); + break; } break; case ESCAPE: @@ -1184,6 +1184,11 @@ public final class TemplateEngine extends JxltEngine { return StringParser.escapeString(str, (char) 0); } + private static boolean isIgnorable(char c) { + return c == '\n' || c == '\r' || c == '\t' || c == '\f' || c == '\b'; + } + + /** * Reads lines of a template grouping them by typed blocks. * diff --git a/src/test/java/org/apache/commons/jexl3/Issues400Test.java b/src/test/java/org/apache/commons/jexl3/Issues400Test.java index 2deb4ab9..28b252e8 100644 --- a/src/test/java/org/apache/commons/jexl3/Issues400Test.java +++ b/src/test/java/org/apache/commons/jexl3/Issues400Test.java @@ -30,6 +30,7 @@ import static org.junit.jupiter.api.Assertions.fail; import java.io.Closeable; import java.io.File; +import java.io.StringWriter; import java.lang.reflect.Method; import java.math.BigDecimal; import java.util.Arrays; @@ -798,6 +799,38 @@ public class Issues400Test { Assertions.assertEquals(ctl, o); } + @Test + void testIssue455a() { + final JexlEngine jexl = new JexlBuilder().create(); + String code = "name -> `${name +\n name}`"; + JexlScript script = jexl.createScript(code); + Object o = script.execute(null, "Hello"); + String ctl = "HelloHello"; + Assertions.assertEquals(ctl, o); + } + + @Test + void testIssue455b() { + final JexlEngine jexl = new JexlBuilder().create(); + String code = "name -> `${name}\n${name}`;"; + JexlScript script = jexl.createScript(code); + Object o = script.execute(null, "Hello"); + String ctl = "Hello\nHello"; + Assertions.assertEquals(ctl, o); + } + + @Test + void testIssue455() { + final JexlEngine jexl = new JexlBuilder().create(); + final JexlContext context = new MapContext(); + context.set("name", "Hello"); + final JxltEngine jxlt = jexl.createJxltEngine(); + final JxltEngine.Template template = jxlt.createTemplate("<b>\n\t${name\n\t+ name}\n</b>"); + final StringWriter writer = new StringWriter(); + template.evaluate(context, writer); + assertEquals("<b>\n\tHelloHello\n</b>", writer.toString()); + } + @Test void testIssue442() { final JexlEngine jexl = new JexlBuilder().create();
