Skip to content

Commit 2a0f4f5

Browse files
authored
Merge pull request #935 from camunda/930-backport-1.16
[Backport 1.16] fix: Handle escape and regex characters
2 parents cbc340c + 3ac86a7 commit 2a0f4f5

File tree

2 files changed

+82
-63
lines changed

2 files changed

+82
-63
lines changed

src/main/scala/org/camunda/feel/impl/parser/FeelParser.scala

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -688,22 +688,16 @@ object FeelParser {
688688
}.getOrElse(ConstNull)
689689
}
690690

691-
// replace escaped character with the provided replacement
692691
private def translateEscapes(input: String): String = {
693-
val escapeMap = Map(
694-
"\\b" -> "\b",
695-
"\\t" -> "\t",
696-
"\\n" -> "\n",
697-
"\\f" -> "\f",
698-
"\\r" -> "\r",
699-
"\\\"" -> "\"",
700-
"\\'" -> "'",
701-
"\\s" -> " "
702-
// Add more escape sequences as needed
703-
)
704-
705-
escapeMap.foldLeft(input) { case (result, (escape, replacement)) =>
706-
result.replace(escape, replacement)
707-
}
692+
// replace all escape sequences
693+
input
694+
.replaceAll("(?<!\\\\)\\\\n", "\n") // new line
695+
.replaceAll("(?<!\\\\)\\\\r", "\r") // carriage return
696+
.replaceAll("(?<!\\\\)\\\\t", "\t") // tab
697+
.replaceAll("(?<!\\\\)\\\\b", "\b") // backspace
698+
.replaceAll("(?<!\\\\)\\\\f", "\f") // form feed
699+
.replaceAll("(?<!\\\\)\\\\'", "'") // single quote
700+
.replaceAll("(?<!\\\\)\\\\\"", "\"") // double quote
701+
.replaceAll("\\\\\\\\", "\\\\") // backslash (for regex characters)
708702
}
709703
}

src/test/scala/org/camunda/feel/impl/interpreter/InterpreterStringExpressionTest.scala

Lines changed: 72 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -16,100 +16,125 @@
1616
*/
1717
package org.camunda.feel.impl.interpreter
1818

19-
import org.camunda.feel.impl.FeelIntegrationTest
19+
import org.camunda.feel.impl.{EvaluationResultMatchers, FeelEngineTest, FeelIntegrationTest}
2020
import org.camunda.feel.syntaxtree._
2121
import org.scalatest.matchers.should.Matchers
2222
import org.scalatest.flatspec.AnyFlatSpec
23+
import org.scalatest.prop.TableDrivenPropertyChecks
24+
25+
import scala.collection.immutable.Map
2326

2427
/** @author
2528
* Philipp Ossler
2629
*/
27-
class InterpreterStringExpressionTest extends AnyFlatSpec with Matchers with FeelIntegrationTest {
30+
class InterpreterStringExpressionTest
31+
extends AnyFlatSpec
32+
with Matchers
33+
with FeelEngineTest
34+
with EvaluationResultMatchers
35+
with TableDrivenPropertyChecks {
2836

2937
"A string" should "concatenates to another String" in {
3038

31-
eval(""" "a" + "b" """) should be(ValString("ab"))
39+
evaluateExpression(""" "a" + "b" """) should returnResult("ab")
3240
}
3341

3442
it should "compare with '='" in {
3543

36-
eval(""" "a" = "a" """) should be(ValBoolean(true))
37-
eval(""" "a" = "b" """) should be(ValBoolean(false))
44+
evaluateExpression(""" "a" = "a" """) should returnResult(true)
45+
evaluateExpression(""" "a" = "b" """) should returnResult(false)
3846
}
3947

4048
it should "compare with '!='" in {
4149

42-
eval(""" "a" != "a" """) should be(ValBoolean(false))
43-
eval(""" "a" != "b" """) should be(ValBoolean(true))
50+
evaluateExpression(""" "a" != "a" """) should returnResult(false)
51+
evaluateExpression(""" "a" != "b" """) should returnResult(true)
4452
}
4553

4654
it should "compare with '<'" in {
4755

48-
eval(""" "a" < "b" """) should be(ValBoolean(true))
49-
eval(""" "b" < "a" """) should be(ValBoolean(false))
56+
evaluateExpression(""" "a" < "b" """) should returnResult(true)
57+
evaluateExpression(""" "b" < "a" """) should returnResult(false)
5058
}
5159

5260
it should "compare with '<='" in {
5361

54-
eval(""" "a" <= "a" """) should be(ValBoolean(true))
55-
eval(""" "b" <= "a" """) should be(ValBoolean(false))
62+
evaluateExpression(""" "a" <= "a" """) should returnResult(true)
63+
evaluateExpression(""" "b" <= "a" """) should returnResult(false)
5664
}
5765

5866
it should "compare with '>'" in {
5967

60-
eval(""" "b" > "a" """) should be(ValBoolean(true))
61-
eval(""" "a" > "b" """) should be(ValBoolean(false))
68+
evaluateExpression(""" "b" > "a" """) should returnResult(true)
69+
evaluateExpression(""" "a" > "b" """) should returnResult(false)
6270
}
6371

6472
it should "compare with '>='" in {
6573

66-
eval(""" "b" >= "b" """) should be(ValBoolean(true))
67-
eval(""" "a" >= "b" """) should be(ValBoolean(false))
74+
evaluateExpression(""" "b" >= "b" """) should returnResult(true)
75+
evaluateExpression(""" "a" >= "b" """) should returnResult(false)
6876
}
6977

7078
it should "compare with null" in {
7179

72-
eval(""" "a" = null """) should be(ValBoolean(false))
73-
eval(""" null = "a" """) should be(ValBoolean(false))
74-
eval(""" "a" != null """) should be(ValBoolean(true))
80+
evaluateExpression(""" "a" = null """) should returnResult(false)
81+
evaluateExpression(""" null = "a" """) should returnResult(false)
82+
evaluateExpression(""" "a" != null """) should returnResult(true)
7583
}
7684

77-
it should "return not escaped characters" in {
78-
79-
eval(""" "Hello\nWorld" """) should be(ValString("Hello\nWorld"))
80-
eval(" x ", Map("x" -> "Hello\nWorld")) should be(ValString("Hello\nWorld"))
85+
private val escapeSequences = Table(
86+
("Character", "Expected", "Display name"),
87+
('\n', '\n', "new line"),
88+
('\r', '\r', "carriage return"),
89+
('\t', '\t', "tab"),
90+
('\b', '\b', "backspace"),
91+
('\f', '\f', "form feed"),
92+
('\'', '\'', "single quote"),
93+
("\\\"", '"', "double quote"),
94+
("\\\\", '\\', "backslash")
95+
)
8196

82-
eval(""" "Hello\rWorld" """) should be(ValString("Hello\rWorld"))
83-
eval(" x ", Map("x" -> "Hello\rWorld")) should be(ValString("Hello\rWorld"))
97+
it should "contains an escape sequence" in {
98+
forEvery(escapeSequences) { (character, expected, _) =>
99+
val expectedString = s"a $expected b"
84100

85-
eval(""" "Hello\'World" """) should be(ValString("Hello\'World"))
86-
eval(" x ", Map("x" -> "Hello\'World")) should be(ValString("Hello\'World"))
101+
evaluateExpression(s" \"a $character b\" ") should returnResult(expectedString)
102+
evaluateExpression("char", Map("char" -> expectedString)) should returnResult(expectedString)
103+
}
104+
}
87105

88-
eval(""" "Hello\tWorld" """) should be(ValString("Hello\tWorld"))
89-
eval(" x ", Map("x" -> "Hello\tWorld")) should be(ValString("Hello\tWorld"))
106+
private val unicodeCharacters = Table(
107+
("Character", "Display name"),
108+
('\u269D', "\\u269D"),
109+
("\\U101EF", "\\U101EF")
110+
)
90111

91-
eval(""" "Hello\"World" """) should be(ValString("Hello\"World"))
92-
eval(" x ", Map("x" -> "Hello\"World")) should be(ValString("Hello\"World"))
112+
it should "contains unicode characters" in {
113+
forEvery(unicodeCharacters) { (character, _) =>
114+
evaluateExpression(s" \"a $character b\" ") should returnResult(s"a $character b")
115+
}
93116
}
94117

95-
List(
96-
" \' ",
97-
" \\ ",
98-
" \n ",
99-
" \r ",
100-
" \t ",
101-
""" \u269D """,
102-
""" \U101EF """
118+
private val regexCharacters = Table(
119+
("Character", "Display name"),
120+
("\\s", "\\s"),
121+
("\\S", "\\S"),
122+
("\\d", "\\d"),
123+
("\\w", "\\w"),
124+
("\\R", "\\R"),
125+
("\\h", "\\h"),
126+
("\\v", "\\v"),
127+
("\\\n", "\\n"),
128+
("\\\r", "\\r")
103129
)
104-
.foreach { notEscapeChar =>
105-
it should s"contains a not escape sequence ($notEscapeChar)" in {
106-
107-
eval(s""" "a $notEscapeChar b" """) should be(
108-
ValString(
109-
s"""a $notEscapeChar b"""
110-
)
111-
)
112-
}
130+
131+
it should "contains a regex character" in {
132+
forEvery(regexCharacters) { (character, _) =>
133+
val expectedString = s"a $character b"
134+
135+
evaluateExpression(s" \"a $character b\" ") should returnResult(expectedString)
136+
evaluateExpression("char", Map("char" -> expectedString)) should returnResult(expectedString)
113137
}
138+
}
114139

115140
}

0 commit comments

Comments
 (0)