diff --git a/docs/src/main/asciidoc/qute-reference.adoc b/docs/src/main/asciidoc/qute-reference.adoc index 445731178971e..ae59583784fb4 100644 --- a/docs/src/main/asciidoc/qute-reference.adoc +++ b/docs/src/main/asciidoc/qute-reference.adoc @@ -80,7 +80,7 @@ NOTE: In Quarkus, the caching is done automatically. The dynamic parts of a template include: * *Comment* -** `{! This is a comment !}`, +** Starts with `{!` and ends with `!}`: `{! This is a comment !}`, ** Could be multi-line, ** May contain expressions and sections: `{! {#if true} !}`. * *Expression* @@ -93,6 +93,10 @@ The dynamic parts of a template include: ** The name in the closing tag is optional: `{#if active}ACTIVE!{/}`, ** Can be empty: `{#myTag image=true /}`, ** May declare nested section blocks: `{#if item.valid} Valid. {#else} Invalid. {/if}` and decide which block to render. +* *Unparsed Character Data* +** Starts with `{[` and ends with `]}`: `{[ ]}`, +** Could be multi-line, +** Used to mark the content that should be rendered but not parsed. === Identifiers diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java index 7b7324a3d189f..b96899989989d 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/Parser.java @@ -37,6 +37,8 @@ class Parser implements Function { private static final char START_DELIMITER = '{'; private static final char END_DELIMITER = '}'; private static final char COMMENT_DELIMITER = '!'; + private static final char CDATA_START_DELIMITER = '['; + private static final char CDATA_END_DELIMITER = ']'; private static final char UNDERSCORE = '_'; private static final char ESCAPE_CHAR = '\\'; @@ -151,6 +153,9 @@ private void processCharacter(char character) { case COMMENT: comment(character); break; + case CDATA: + cdata(character); + break; case TAG_CANDIDATE: tagCandidate(character); break; @@ -193,6 +198,17 @@ private void comment(char character) { } } + private void cdata(char character) { + if (character == END_DELIMITER && buffer.length() > 0 && buffer.charAt(buffer.length() - 1) == CDATA_END_DELIMITER) { + // End of cdata + state = State.TEXT; + buffer.deleteCharAt(buffer.length() - 1); + flushText(); + } else { + buffer.append(character); + } + } + private void tag(char character) { if (character == END_DELIMITER) { flushTag(); @@ -205,8 +221,15 @@ private void tagCandidate(char character) { if (isValidIdentifierStart(character)) { // Real tag start, flush text if any flushText(); - state = character == COMMENT_DELIMITER ? State.COMMENT : State.TAG_INSIDE; - buffer.append(character); + if (character == COMMENT_DELIMITER) { + buffer.append(character); + state = State.COMMENT; + } else if (character == CDATA_START_DELIMITER) { + state = State.CDATA; + } else { + buffer.append(character); + state = State.TAG_INSIDE; + } } else { // Ignore expressions/tags starting with an invalid identifier buffer.append(START_DELIMITER).append(character); @@ -219,8 +242,9 @@ private void tagCandidate(char 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 + // A valid identifier must start with a digit, alphabet, underscore, comment delimiter, cdata start delimiter or a tag command (e.g. # for sections) + return Tag.isCommand(character) || character == COMMENT_DELIMITER || character == CDATA_START_DELIMITER + || character == UNDERSCORE || Character.isDigit(character) || Character.isAlphabetic(character); } @@ -570,6 +594,7 @@ enum State { TAG_CANDIDATE, COMMENT, ESCAPE, + CDATA, } diff --git a/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java b/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java index a20a52f1fbcf7..d22372ac0caf7 100644 --- a/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java +++ b/independent-projects/qute/core/src/test/java/io/quarkus/qute/ParserTest.java @@ -165,6 +165,21 @@ public void testWhitespace() { assertEquals("Hello world", engine.parse("Hello {name ?: 'world' }").render()); } + @Test + public void testCdata() { + Engine engine = Engine.builder().addDefaults().build(); + String jsSnippet = ""; + try { + engine.parse("Hello {name} " + jsSnippet); + fail(); + } catch (Exception expected) { + } + assertEquals("Hello world ", engine.parse("Hello {name} {[" + + jsSnippet + + "]}").data("name", "world").render()); + assertEquals("Hello world ", engine.parse("Hello {name} {[]}").data("name", "world").render()); + } + private void assertParserError(String template, String message, int line) { Engine engine = Engine.builder().addDefaultSectionHelpers().build(); try {