From 75944cc88fda827e84a13b15d3b501c50d58b5fd Mon Sep 17 00:00:00 2001 From: Andy Clement Date: Tue, 7 Aug 2012 09:55:20 -0700 Subject: [PATCH] Support nested double quotes in SpEL expressions The Spring Expression Language currently supports nested single quotes within expressions but not nested double quotes. The SpEL tokenizer has been modified to support nested double quotes in the same way it supports single quotes. A sequence of two double quotes will now be replaced by one when evaluated. Extra error handling has also been added to report when invalid escaping is encountered, since SpEL does not support escaping with backslash. Issue: SPR-9620 --- .../expression/spel/SpelMessage.java | 3 ++- .../expression/spel/ast/StringLiteral.java | 4 ++-- .../expression/spel/standard/Tokenizer.java | 9 ++++++++- .../spel/standard/SpelParserTests.java | 19 +++++++++++++++++++ src/dist/changelog.txt | 1 + 5 files changed, 32 insertions(+), 4 deletions(-) diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java index 14f0bfb9b9..23d616e1bc 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -104,6 +104,7 @@ public enum SpelMessage { MISSING_ARRAY_DIMENSION(Kind.ERROR, 1063, "A required array dimension has not been specified"), // INITIALIZER_LENGTH_INCORRECT( Kind.ERROR, 1064, "array initializer size does not match array dimensions"), // + UNEXPECTED_ESCAPE_CHAR(Kind.ERROR,1065,"unexpected escape character."); ; private Kind kind; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java b/spring-expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java index 2e3ff12dc4..efcd829726 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/ast/StringLiteral.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ public class StringLiteral extends Literal { super(payload,pos); // TODO should these have been skipped being created by the parser rules? or not? value = value.substring(1, value.length() - 1); - this.value = new TypedValue(value.replaceAll("''", "'")); + this.value = new TypedValue(value.replaceAll("''", "'").replaceAll("\"\"", "\"")); } @Override diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java index 5471e4c5fc..0654fbe3eb 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java @@ -199,6 +199,8 @@ class Tokenizer { // hit sentinel at end of value pos++; // will take us to the end break; + case '\\': + throw new InternalParseException(new SpelParseException(expressionString,pos,SpelMessage.UNEXPECTED_ESCAPE_CHAR)); default: throw new IllegalStateException("Cannot handle ("+Integer.valueOf(ch)+") '"+ch+"'"); } @@ -241,7 +243,12 @@ class Tokenizer { pos++; char ch = toProcess[pos]; if (ch=='"') { - terminated = true; + // may not be the end if the char after is also a " + if (toProcess[pos+1]=='"') { + pos++; // skip over that too, and continue + } else { + terminated = true; + } } if (ch==0) { throw new InternalParseException(new SpelParseException(expressionString,start,SpelMessage.NON_TERMINATING_DOUBLE_QUOTED_STRING)); diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java index 3ee78dbd81..66552f4ad4 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/standard/SpelParserTests.java @@ -261,6 +261,25 @@ public class SpelParserTests { assertEquals("ho", expr.getValue()); } + @Test + public void testStringLiterals_DoubleQuotes_spr9620() throws Exception { + SpelExpression expr = new SpelExpressionParser().parseRaw("\"double quote: \"\".\""); + assertEquals("double quote: \".", expr.getValue()); + expr = new SpelExpressionParser().parseRaw("\"hello \"\" world\""); + assertEquals("hello \" world", expr.getValue()); + } + + @Test + public void testStringLiterals_DoubleQuotes_spr9620_2() throws Exception { + try { + new SpelExpressionParser().parseRaw("\"double quote: \\\"\\\".\""); + fail("Should have failed"); + } catch (SpelParseException spe) { + assertEquals(17, spe.getPosition()); + assertEquals(SpelMessage.UNEXPECTED_ESCAPE_CHAR, spe.getMessageCode()); + } + } + @Test public void positionalInformation() throws EvaluationException, ParseException { SpelExpression expr = new SpelExpressionParser().parseRaw("true and true or false"); diff --git a/src/dist/changelog.txt b/src/dist/changelog.txt index 69c7f0decc..fcee310f59 100644 --- a/src/dist/changelog.txt +++ b/src/dist/changelog.txt @@ -10,6 +10,7 @@ Changes in version 3.2 M2 (2012-08-xx) * now inferring return type of generic factory methods (SPR-9493) * SpEL now supports method invocations on integers (SPR-9612) * SpEL now supports symbolic boolean operators for OR and AND (SPR-9614) +* SpEL now supports nested double quotes in expressions (SPR-9620) * introduced support for case-insensitive null literals in SpEL expressions (SPR-9613) * now using BufferedInputStream in SimpleMetaDataReader to double performance (SPR-9528) * introduced "repeatCount" property in Quartz SimpleTriggerFactoryBean (SPR-9521)