mirror of
https://github.com/jlengrand/quarkus.git
synced 2026-03-10 08:41:22 +00:00
Merge pull request #7274 from mkouba/issue-6621
Qute - ignore expressions/tags starting with invalid identifiers
This commit is contained in:
@@ -94,6 +94,32 @@ The dynamic parts of a template include:
|
||||
** can be empty: `{#myTag image=true /}`,
|
||||
** may declare nested section blocks: `{#if item.valid} Valid. {#else} Invalid. {/if}` and decide which block to render.
|
||||
|
||||
=== Identifiers
|
||||
|
||||
Expressions/tags must start with a curly bracket (`{`) followed by a valid identifier.
|
||||
A valid identifier is a digit, an alphabet character, underscore (`_`), or a section command (`#`).
|
||||
Expressions/tags starting with an invalid identifier are ignored.
|
||||
A closing curly bracket (`}`) is ignored if not inside an expression/tag.
|
||||
|
||||
.hello.html
|
||||
[source,html]
|
||||
----
|
||||
<html>
|
||||
<body>
|
||||
{_foo} <1>
|
||||
{ foo} <2>
|
||||
{{foo}} <3>
|
||||
{"foo":true} <4>
|
||||
</body>
|
||||
</html>
|
||||
----
|
||||
<1> Evaluated: expression starts with underscore.
|
||||
<2> Ignored: expression starts with whitespace.
|
||||
<3> Ignored: expression starts with `{`.
|
||||
<4> Ignored: expression starts with `"`.
|
||||
|
||||
TIP: It is also possible to use escape sequences `\{` and `\}` to insert delimiters in the text. In fact, an escape sequence is usually only needed for the start delimiter, ie. `\\{foo}` will be rendered as `{foo}` (no evaluation will happen).
|
||||
|
||||
==== Expressions
|
||||
|
||||
An expression consists of:
|
||||
|
||||
@@ -33,6 +33,9 @@ class Parser implements Function<String, Expression> {
|
||||
private static final char START_DELIMITER = '{';
|
||||
private static final char END_DELIMITER = '}';
|
||||
private static final char COMMENT_DELIMITER = '!';
|
||||
private static final char UNDERSCORE = '_';
|
||||
private static final char ESCAPE_CHAR = '\\';
|
||||
|
||||
// Linux, BDS, etc.
|
||||
private static final char LINE_SEPARATOR_LF = '\n';
|
||||
// Mac OS 9, ZX Spectrum :-), etc.
|
||||
@@ -135,6 +138,9 @@ class Parser implements Function<String, Expression> {
|
||||
case TEXT:
|
||||
text(character);
|
||||
break;
|
||||
case ESCAPE:
|
||||
escape(character);
|
||||
break;
|
||||
case TAG_INSIDE:
|
||||
tag(character);
|
||||
break;
|
||||
@@ -150,9 +156,20 @@ class Parser implements Function<String, Expression> {
|
||||
lineCharacter++;
|
||||
}
|
||||
|
||||
private void escape(char character) {
|
||||
if (character != START_DELIMITER && character != END_DELIMITER) {
|
||||
// Invalid escape sequence is just ignored
|
||||
buffer.append(ESCAPE_CHAR);
|
||||
}
|
||||
buffer.append(character);
|
||||
state = State.TEXT;
|
||||
}
|
||||
|
||||
private void text(char character) {
|
||||
if (character == START_DELIMITER) {
|
||||
state = State.TAG_CANDIDATE;
|
||||
} else if (character == ESCAPE_CHAR) {
|
||||
state = State.ESCAPE;
|
||||
} else {
|
||||
if (isLineSeparator(character)) {
|
||||
line++;
|
||||
@@ -181,24 +198,29 @@ class Parser implements Function<String, Expression> {
|
||||
}
|
||||
|
||||
private void tagCandidate(char character) {
|
||||
if (Character.isWhitespace(character)) {
|
||||
if (isValidIdentifierStart(character)) {
|
||||
// Real tag start, flush text if any
|
||||
flushText();
|
||||
state = character == COMMENT_DELIMITER ? State.COMMENT : State.TAG_INSIDE;
|
||||
buffer.append(character);
|
||||
} else {
|
||||
// Ignore expressions/tags starting with an invalid identifier
|
||||
buffer.append(START_DELIMITER).append(character);
|
||||
if (isLineSeparator(character)) {
|
||||
line++;
|
||||
lineCharacter = 1;
|
||||
}
|
||||
state = State.TEXT;
|
||||
} else if (character == START_DELIMITER) {
|
||||
buffer.append(START_DELIMITER).append(START_DELIMITER);
|
||||
state = State.TEXT;
|
||||
} else {
|
||||
// Real tag start, flush text if any
|
||||
flushText();
|
||||
state = character == COMMENT_DELIMITER ? State.COMMENT : State.TAG_INSIDE;
|
||||
buffer.append(character);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidIdentifierStart(char character) {
|
||||
// A valid identifier must start with a digit, alphabet, underscore, comment delimiter or a tag command (e.g. # for sections)
|
||||
return Tag.isCommand(character) || character == COMMENT_DELIMITER || character == UNDERSCORE
|
||||
|| Character.isDigit(character)
|
||||
|| Character.isAlphabetic(character);
|
||||
}
|
||||
|
||||
private boolean isLineSeparator(char character) {
|
||||
return character == LINE_SEPARATOR_CR
|
||||
|| (character == LINE_SEPARATOR_LF
|
||||
@@ -218,7 +240,7 @@ class Parser implements Function<String, Expression> {
|
||||
String content = buffer.toString();
|
||||
String tag = START_DELIMITER + content + END_DELIMITER;
|
||||
|
||||
if (content.charAt(0) == Tag.SECTION.getCommand()) {
|
||||
if (content.charAt(0) == Tag.SECTION.command) {
|
||||
|
||||
boolean isEmptySection = false;
|
||||
if (content.charAt(content.length() - 1) == Tag.SECTION_END.command) {
|
||||
@@ -307,7 +329,7 @@ class Parser implements Function<String, Expression> {
|
||||
sectionStack.addFirst(sectionNode);
|
||||
}
|
||||
}
|
||||
} else if (content.charAt(0) == Tag.SECTION_END.getCommand()) {
|
||||
} else if (content.charAt(0) == Tag.SECTION_END.command) {
|
||||
SectionBlock.Builder block = sectionBlockStack.peek();
|
||||
SectionNode.Builder section = sectionStack.peek();
|
||||
String name = content.substring(1, content.length());
|
||||
@@ -336,7 +358,7 @@ class Parser implements Function<String, Expression> {
|
||||
// Remove the last type info map from the stack
|
||||
typeInfoStack.pop();
|
||||
|
||||
} else if (content.charAt(0) == Tag.PARAM.getCommand()) {
|
||||
} else if (content.charAt(0) == Tag.PARAM.command) {
|
||||
|
||||
// {@org.acme.Foo foo}
|
||||
Map<String, String> typeInfos = typeInfoStack.peek();
|
||||
@@ -512,18 +534,22 @@ class Parser implements Function<String, Expression> {
|
||||
EXPRESSION(null),
|
||||
SECTION('#'),
|
||||
SECTION_END('/'),
|
||||
SECTION_BLOCK(':'),
|
||||
PARAM('@'),
|
||||
;
|
||||
|
||||
private final Character command;
|
||||
final Character command;
|
||||
|
||||
private Tag(Character command) {
|
||||
Tag(Character command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
public Character getCommand() {
|
||||
return command;
|
||||
static boolean isCommand(char command) {
|
||||
for (Tag tag : Tag.values()) {
|
||||
if (tag.command != null && tag.command == command) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -534,6 +560,7 @@ class Parser implements Function<String, Expression> {
|
||||
TAG_INSIDE,
|
||||
TAG_CANDIDATE,
|
||||
COMMENT,
|
||||
ESCAPE,
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,21 @@ public class ParserTest {
|
||||
"Parser error on line 2: no section helper found for {#foo test/}", 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIgnoreInvalidIdentifier() {
|
||||
Engine engine = Engine.builder().addDefaults().build();
|
||||
assertEquals("{\"foo\":\"bar\"} bar {'} baz ZX80",
|
||||
engine.parse("{\"foo\":\"bar\"} {_foo} {'} {1foo} {čip}").data("_foo", "bar").data("1foo", "baz")
|
||||
.data("čip", "ZX80").render());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEscapingDelimiters() {
|
||||
Engine engine = Engine.builder().addDefaults().build();
|
||||
assertEquals("{foo} bar \\ignored {čip}",
|
||||
engine.parse("\\{foo\\} {foo} \\ignored \\{čip}").data("foo", "bar").render());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTypeCheckInfos() {
|
||||
Engine engine = Engine.builder().addDefaultSectionHelpers()
|
||||
|
||||
Reference in New Issue
Block a user