diff --git a/bundlebee-core/src/main/java/io/yupiik/bundlebee/core/lang/Substitutor.java b/bundlebee-core/src/main/java/io/yupiik/bundlebee/core/lang/Substitutor.java index d59e1a5c..bf920cb9 100644 --- a/bundlebee-core/src/main/java/io/yupiik/bundlebee/core/lang/Substitutor.java +++ b/bundlebee-core/src/main/java/io/yupiik/bundlebee/core/lang/Substitutor.java @@ -50,11 +50,27 @@ public String replace(final String source) { final var previous = current; current = substitute(current, 0); if (previous.equals(current)) { - return previous; + return unescape(previous); } } while (true); } + protected String unescape(final String previous) { + final var copy = new StringBuilder(previous.length()); + boolean forceAppend = false; + for (int i = 0; i < previous.length(); i++) { + final var c = previous.charAt(i); + if (!forceAppend && c == ESCAPE) { + forceAppend = true; + continue; + } else if (forceAppend) { + forceAppend = false; + } + copy.append(c); + } + return copy.toString(); + } + private String substitute(final String input, int iteration) { if (iteration > maxIterations) { return input; @@ -62,16 +78,17 @@ private String substitute(final String input, int iteration) { int from = 0; int start = -1; - while (start < 0 && from < input.length()) { + while (from < input.length()) { start = input.indexOf(PREFIX, from); if (start < 0) { return input; } - if (start != 0 && input.charAt(start - 1) != ESCAPE) { + if (start == 0 || input.charAt(start - 1) != ESCAPE) { break; } from = start + 1; } + final var keyStart = start + PREFIX.length(); final var end = input.indexOf(SUFFIX, keyStart); if (end < 0) { diff --git a/bundlebee-core/src/test/java/io/yupiik/bundlebee/core/lang/SubstitutorTest.java b/bundlebee-core/src/test/java/io/yupiik/bundlebee/core/lang/SubstitutorTest.java index d3c561b6..f329fad8 100644 --- a/bundlebee-core/src/test/java/io/yupiik/bundlebee/core/lang/SubstitutorTest.java +++ b/bundlebee-core/src/test/java/io/yupiik/bundlebee/core/lang/SubstitutorTest.java @@ -84,6 +84,21 @@ void fallback() { assertEquals("foo or dummy", new Substitutor(k -> null).replace("foo {{key:-or}} dummy")); } + @Test + void escaped() { + assertEquals("foo {{key:-or}} dummy", new Substitutor(k -> null).replace("foo \\{{key:-or}} dummy")); + assertEquals("foo before {{key:-or}} / {{key:-or2}} after dummy", new Substitutor(k -> { + switch (k) { + case "suffix": + return "after"; + case "prefix": + return "before"; + default: + return null; + } + }).replace("foo {{prefix}} \\{{key:-or}\\} / {{test:-\\{\\{key:-or2\\}\\}}} {{suffix}} dummy")); + } + @Test void nested() { assertEquals("foo replaced dummy", new Substitutor(k -> "key".equals(k) ? "replaced" : null).replace("foo {{k{{missing:-e}}y}} dummy"));