Skip to content

Commit 4e8aebc

Browse files
committed
Fix SpEL support for quotes within String literals
Prior to this commit, there were two bugs in the support for quotes within String literals in SpEL expressions. - Two double quotes ("") or two single quotes ('') were always replaced with one double quote or one single quote, respectively, regardless of which quote character was used to enclose the original String literal. This resulted in the loss of one of the double quotes when the String literal was enclosed in single quotes, and vice versa. For example, 'x "" y' became 'x " y'. - A single quote which was properly escaped in a String literal enclosed within single quotes was not escaped in the AST string representation of the expression. For example, 'x '' y' became 'x ' y'. This commit fixes both of these related issues in StringLiteral and overhauls the structure of ParsingTests. Closes gh-29604 Closes gh-28356
1 parent dff5b1f commit 4e8aebc

File tree

4 files changed

+544
-410
lines changed

4 files changed

+544
-410
lines changed

spring-expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
2626
*
2727
* @author Andy Clement
2828
* @author Juergen Hoeller
29+
* @author Sam Brannen
2930
* @since 3.0
3031
*/
3132
public class StringLiteral extends Literal {
@@ -36,9 +37,19 @@ public class StringLiteral extends Literal {
3637
public StringLiteral(String payload, int startPos, int endPos, String value) {
3738
super(payload, startPos, endPos);
3839

40+
// The original enclosing quote character for the string literal: ' or ".
41+
char quoteCharacter = value.charAt(0);
42+
43+
// Remove enclosing quotes
3944
String valueWithinQuotes = value.substring(1, value.length() - 1);
40-
valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "''", "'");
41-
valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "\"\"", "\"");
45+
46+
// Replace escaped internal quote characters
47+
if (quoteCharacter == '\'') {
48+
valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "''", "'");
49+
}
50+
else {
51+
valueWithinQuotes = StringUtils.replace(valueWithinQuotes, "\"\"", "\"");
52+
}
4253

4354
this.value = new TypedValue(valueWithinQuotes);
4455
this.exitTypeDescriptor = "Ljava/lang/String";
@@ -52,7 +63,9 @@ public TypedValue getLiteralValue() {
5263

5364
@Override
5465
public String toString() {
55-
return "'" + getLiteralValue().getValue() + "'";
66+
String ast = String.valueOf(getLiteralValue().getValue());
67+
ast = StringUtils.replace(ast, "'", "''");
68+
return "'" + ast + "'";
5669
}
5770

5871
@Override

spring-expression/src/test/java/org/springframework/expression/spel/EvaluationTests.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,53 @@ void limitCollectionGrowing() {
364364

365365
}
366366

367+
@Nested
368+
class StringLiterals {
369+
370+
@Test
371+
void insideSingleQuotes() {
372+
evaluate("'hello'", "hello", String.class);
373+
evaluate("'hello world'", "hello world", String.class);
374+
}
375+
376+
@Test
377+
void insideDoubleQuotes() {
378+
evaluate("\"hello\"", "hello", String.class);
379+
evaluate("\"hello world\"", "hello world", String.class);
380+
}
381+
382+
@Test
383+
void singleQuotesInsideSingleQuotes() {
384+
evaluate("'Tony''s Pizza'", "Tony's Pizza", String.class);
385+
evaluate("'big ''''pizza'''' parlor'", "big ''pizza'' parlor", String.class);
386+
}
387+
388+
@Test
389+
void doubleQuotesInsideDoubleQuotes() {
390+
evaluate("\"big \"\"pizza\"\" parlor\"", "big \"pizza\" parlor", String.class);
391+
evaluate("\"big \"\"\"\"pizza\"\"\"\" parlor\"", "big \"\"pizza\"\" parlor", String.class);
392+
}
393+
394+
@Test
395+
void singleQuotesInsideDoubleQuotes() {
396+
evaluate("\"Tony's Pizza\"", "Tony's Pizza", String.class);
397+
evaluate("\"big ''pizza'' parlor\"", "big ''pizza'' parlor", String.class);
398+
}
399+
400+
@Test
401+
void doubleQuotesInsideSingleQuotes() {
402+
evaluate("'big \"pizza\" parlor'", "big \"pizza\" parlor", String.class);
403+
evaluate("'two double \"\" quotes'", "two double \"\" quotes", String.class);
404+
}
405+
406+
@Test
407+
void inCompoundExpressions() {
408+
evaluate("'123''4' == '123''4'", true, Boolean.class);
409+
evaluate("\"123\"\"4\" == \"123\"\"4\"", true, Boolean.class);
410+
}
411+
412+
}
413+
367414
@Nested
368415
class RelationalOperatorTests {
369416

0 commit comments

Comments
 (0)