From 2bc3d180c3860f9f9bc78e830dd315d9d9470442 Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Sun, 10 Nov 2024 21:35:40 +0100 Subject: [PATCH 01/43] simple template engine basics --- cms-sandbox/pom.xml | 3 +- cms-sandbox/templates/pom.xml | 16 +++++ .../src/main/java/com/condation/cms/Main.java | 7 ++ .../cms/templates/ParserExample.java | 41 +++++++++++ .../cms/templates/RenderTemplateExample.java | 34 +++++++++ .../com/condation/cms/templates/Renderer.java | 69 +++++++++++++++++++ .../cms/templates/TemplateEngine.java | 5 ++ .../condation/cms/templates/lexer/Lexer.java | 59 ++++++++++++++++ .../condation/cms/templates/lexer/Token.java | 20 ++++++ .../cms/templates/parser/ASTNode.java | 16 +++++ .../cms/templates/parser/Parser.java | 62 +++++++++++++++++ .../cms/templates/parser/TagNode.java | 18 +++++ .../cms/templates/parser/TextNode.java | 14 ++++ .../cms/templates/parser/VariableNode.java | 18 +++++ cms-sandbox/tests/pom.xml | 2 +- 15 files changed, 382 insertions(+), 2 deletions(-) create mode 100644 cms-sandbox/templates/pom.xml create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/Main.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderTemplateExample.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/Renderer.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Token.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/ASTNode.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TextNode.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/VariableNode.java diff --git a/cms-sandbox/pom.xml b/cms-sandbox/pom.xml index c65a99fa9..14f48d034 100644 --- a/cms-sandbox/pom.xml +++ b/cms-sandbox/pom.xml @@ -4,11 +4,12 @@ com.condation.cms cms-parent - 6.4.0 + 7.2.0 cms-sandbox pom tests + templates \ No newline at end of file diff --git a/cms-sandbox/templates/pom.xml b/cms-sandbox/templates/pom.xml new file mode 100644 index 000000000..856b70b79 --- /dev/null +++ b/cms-sandbox/templates/pom.xml @@ -0,0 +1,16 @@ + + + 4.0.0 + + com.condation.cms + templates + 1.0-SNAPSHOT + + + 21 + 21 + + + \ No newline at end of file diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/Main.java b/cms-sandbox/templates/src/main/java/com/condation/cms/Main.java new file mode 100644 index 000000000..384c38fa1 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/Main.java @@ -0,0 +1,7 @@ +package com.condation.cms; + +public class Main { + public static void main(String[] args) { + System.out.println("Hello world!"); + } +} \ No newline at end of file diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java new file mode 100644 index 000000000..da2bf3a2c --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java @@ -0,0 +1,41 @@ +package com.condation.cms.templates; + +import java.util.List; + +import com.condation.cms.templates.lexer.Lexer; +import com.condation.cms.templates.lexer.Token; +import com.condation.cms.templates.parser.ASTNode; +import com.condation.cms.templates.parser.Parser; +import com.condation.cms.templates.parser.TagNode; +import com.condation.cms.templates.parser.TextNode; +import com.condation.cms.templates.parser.VariableNode; + +public class ParserExample { + public static void main(String[] args) { + String template = "Hello {{ name }}! {% if condition %}World{% endif %}"; + Lexer lexer = new Lexer(template); + List tokens = lexer.tokenize(); + + Parser parser = new Parser(tokens); + ASTNode ast = parser.parse(); + + System.out.println("Tokens:"); + tokens.forEach(System.out::println); + + System.out.println("AST:"); + printAST(ast, 0); + } + + private static void printAST(ASTNode node, int depth) { + String indent = " ".repeat(depth * 2); + if (node instanceof TextNode) { + System.out.println(indent + "Text: " + ((TextNode) node).text); + } else if (node instanceof VariableNode) { + System.out.println(indent + "Variable: " + ((VariableNode) node).getVariable()); + } else if (node instanceof TagNode) { + TagNode tag = (TagNode) node; + System.out.println(indent + "Tag: " + tag.getName()); + tag.getChildren().forEach(child -> printAST(child, depth + 1)); + } + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderTemplateExample.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderTemplateExample.java new file mode 100644 index 000000000..2574be89e --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderTemplateExample.java @@ -0,0 +1,34 @@ +package com.condation.cms.templates; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.condation.cms.templates.Renderer.Context; +import com.condation.cms.templates.lexer.Lexer; +import com.condation.cms.templates.lexer.Token; +import com.condation.cms.templates.parser.ASTNode; +import com.condation.cms.templates.parser.Parser; + +public class RenderTemplateExample { + public static void main(String[] args) { + // Beispiel-Template und Kontext + String template = "Hello, {% if user %}{{ user }}{% endif %}!"; + Lexer lexer = new Lexer(template); + List tokens = lexer.tokenize(); + + Parser parser = new Parser(tokens); + ASTNode ast = parser.parse(); + + // Kontext mit Werten für das Template + var context = new Renderer.Context(); + context.setVariable("user", "Thorsten"); + + // Rendering + Renderer renderer = new Renderer(context); + String output = renderer.render(ast); + + System.out.println("Rendered Output:"); + System.out.println(output); + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Renderer.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Renderer.java new file mode 100644 index 000000000..95511deda --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Renderer.java @@ -0,0 +1,69 @@ +package com.condation.cms.templates; + +import java.util.HashMap; +import java.util.Map; + +import com.condation.cms.templates.parser.ASTNode; +import com.condation.cms.templates.parser.TagNode; +import com.condation.cms.templates.parser.TextNode; +import com.condation.cms.templates.parser.VariableNode; + +public class Renderer { + + public static class Context { + private final Map variables = new HashMap<>(); + + public void setVariable(String name, String value) { + variables.put(name, value); + } + + public String getVariable(String name) { + return variables.get(name); + } + } + + private final Context context; + + public Renderer(Context context) { + this.context = context; + } + + public String render(ASTNode node) { + StringBuilder output = new StringBuilder(); + renderNode(node, output); + return output.toString(); + } + + private void renderNode(ASTNode node, StringBuilder output) { + if (node instanceof TextNode) { + output.append(((TextNode) node).text); + } else if (node instanceof VariableNode) { + String variableValue = context.getVariable(((VariableNode) node).getVariable()); + output.append(variableValue != null ? variableValue : ""); + } else if (node instanceof TagNode) { + TagNode tagNode = (TagNode) node; + // Beispiel: Wir unterstützen das "if"-Tag + if ("if".equals(tagNode.getName())) { + ASTNode conditionNode = tagNode.getChildren().get(0); + if (conditionNode instanceof VariableNode) { + String variableValue = context.getVariable(((VariableNode) conditionNode).getVariable()); + if (variableValue != null && !variableValue.isEmpty()) { + for (int i = 1; i < tagNode.getChildren().size(); i++) { + renderNode(tagNode.getChildren().get(i), output); + } + } + } + } else { + // Anderes Tag-Verhalten kann hier hinzugefügt werden + for (ASTNode child : tagNode.getChildren()) { + renderNode(child, output); + } + } + } else { + // Rekursiv alle Kindknoten verarbeiten + for (ASTNode child : node.getChildren()) { + renderNode(child, output); + } + } + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java new file mode 100644 index 000000000..56715b491 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java @@ -0,0 +1,5 @@ +package com.condation.cms.templates; + +public class TemplateEngine { + +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java new file mode 100644 index 000000000..ba161f738 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java @@ -0,0 +1,59 @@ +package com.condation.cms.templates.lexer; + +import java.util.ArrayList; +import java.util.List; + +public class Lexer { + private final String input; + private int position = 0; + private boolean inTag = false; // neuer Zustand + + public Lexer(String input) { + this.input = input; + } + + public List tokenize() { + List tokens = new ArrayList<>(); + while (position < input.length()) { + char c = input.charAt(position); + + if (c == '{' && peek(1) == '{') { + tokens.add(new Token(Token.Type.VARIABLE, "{{")); + position += 2; + inTag = true; // Wir sind jetzt in einer Variablen + } else if (c == '{' && peek(1) == '%') { + tokens.add(new Token(Token.Type.TAG_START, "{%")); + position += 2; + inTag = true; // Wir sind jetzt in einem Tag + } else if (inTag && c == '%' && peek(1) == '}') { + tokens.add(new Token(Token.Type.TAG_END, "%}")); + position += 2; + inTag = false; // Tag endet hier + } else if (inTag && c == '}' && peek(1) == '}') { + tokens.add(new Token(Token.Type.TAG_END, "}}")); + position += 2; + inTag = false; // Variable endet hier + } else if (inTag && Character.isLetter(c)) { + // Nur wenn wir im Tag sind, identifizieren wir einen IDENTIFIER + tokens.add(new Token(Token.Type.IDENTIFIER, readWhile(Character::isLetter))); + } else { + tokens.add(new Token(Token.Type.TEXT, String.valueOf(c))); + position++; + } + } + tokens.add(new Token(Token.Type.END, "")); + return tokens; + } + + private char peek(int offset) { + return (position + offset < input.length()) ? input.charAt(position + offset) : '\0'; + } + + private String readWhile(java.util.function.Predicate condition) { + StringBuilder result = new StringBuilder(); + while (position < input.length() && condition.test(input.charAt(position))) { + result.append(input.charAt(position++)); + } + return result.toString(); + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Token.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Token.java new file mode 100644 index 000000000..e228a7f37 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Token.java @@ -0,0 +1,20 @@ +package com.condation.cms.templates.lexer; + +public class Token { + public static enum Type { + TEXT, VARIABLE, TAG_START, TAG_END, IDENTIFIER, END + } + + public final Type type; + public final String value; + + public Token(Type type, String value) { + this.type = type; + this.value = value; + } + + @Override + public String toString() { + return type + "('" + value + "')"; + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/ASTNode.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/ASTNode.java new file mode 100644 index 000000000..82b88d2c3 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/ASTNode.java @@ -0,0 +1,16 @@ +package com.condation.cms.templates.parser; + +import java.util.ArrayList; +import java.util.List; + +public class ASTNode { + private final List children = new ArrayList<>(); + + public void addChild(ASTNode child) { + children.add(child); + } + + public List getChildren() { + return children; + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java new file mode 100644 index 000000000..93f53c018 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java @@ -0,0 +1,62 @@ +package com.condation.cms.templates.parser; + +import java.util.List; +import java.util.Stack; + +import com.condation.cms.templates.lexer.Token; + +public class Parser { + private final List tokens; + private int position = 0; + + public Parser(List tokens) { + this.tokens = tokens; + } + + public ASTNode parse() { + ASTNode root = new ASTNode(); + Stack nodeStack = new Stack<>(); + nodeStack.push(root); + + while (position < tokens.size()) { + Token token = tokens.get(position); + + switch (token.type) { + case TEXT: + nodeStack.peek().addChild(new TextNode(token.value)); + break; + case VARIABLE: + VariableNode variableNode = new VariableNode(); + nodeStack.peek().addChild(variableNode); + nodeStack.push(variableNode); // In den neuen Kontext für Variablen wechseln + break; + case TAG_START: + TagNode tagNode = new TagNode(); + nodeStack.peek().addChild(tagNode); + nodeStack.push(tagNode); // In den neuen Kontext für Tags wechseln + break; + case TAG_END: + if (!nodeStack.isEmpty()) { + nodeStack.pop(); // Aus dem aktuellen Tag-/Variable-Block heraustreten + } + break; + case IDENTIFIER: + ASTNode currentNode = nodeStack.peek(); + if (currentNode instanceof TagNode) { + ((TagNode) currentNode).setName(token.value); // Tag-Name setzen + } else if (currentNode instanceof VariableNode) { + ((VariableNode) currentNode).setVariable(token.value); // Variable setzen + } + break; + case END: + System.out.println("end token?"); + break; + default: + throw new RuntimeException("Unexpected token: " + token.type); + } + position++; + } + + return root; + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java new file mode 100644 index 000000000..cfaf10709 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java @@ -0,0 +1,18 @@ +package com.condation.cms.templates.parser; + +public class TagNode extends ASTNode { + private String name; + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return "TagNode('" + name + "')"; + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TextNode.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TextNode.java new file mode 100644 index 000000000..ec9b271fe --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TextNode.java @@ -0,0 +1,14 @@ +package com.condation.cms.templates.parser; + +public class TextNode extends ASTNode { + public final String text; + + public TextNode(String text) { + this.text = text; + } + + @Override + public String toString() { + return "TextNode('" + text + "')"; + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/VariableNode.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/VariableNode.java new file mode 100644 index 000000000..00d2e017f --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/VariableNode.java @@ -0,0 +1,18 @@ +package com.condation.cms.templates.parser; + +public class VariableNode extends ASTNode { + private String variable; + + public void setVariable(String variable) { + this.variable = variable; + } + + public String getVariable() { + return variable; + } + + @Override + public String toString() { + return "VariableNode('" + variable + "')"; + } +} diff --git a/cms-sandbox/tests/pom.xml b/cms-sandbox/tests/pom.xml index e9ce593b3..79fef65d3 100644 --- a/cms-sandbox/tests/pom.xml +++ b/cms-sandbox/tests/pom.xml @@ -6,7 +6,7 @@ com.condation.cms cms-sandbox - 6.4.0 + 7.2.0 tests jar From 00a8ee6633c0eaee3be13ef9e7db815da2e6c510 Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Mon, 11 Nov 2024 16:37:51 +0100 Subject: [PATCH 02/43] add basic template syntax --- cms-sandbox/pom.xml | 9 ++ cms-sandbox/templates/pom.xml | 9 ++ .../src/main/java/com/condation/cms/Main.java | 7 -- .../cms/templates/ParserExample.java | 31 ++++++- .../condation/cms/templates/lexer/Lexer.java | 30 ++++++- .../condation/cms/templates/lexer/Token.java | 2 +- .../cms/templates/parser/Parser.java | 8 ++ .../cms/templates/parser/TagNode.java | 16 +++- .../cms/templates/renderer/ScopeStack.java | 48 ++++++++++ .../templates/renderer/ScopeStackTest.java | 89 +++++++++++++++++++ .../cms/tests/mustache/HandlebarTest.java | 6 -- 11 files changed, 237 insertions(+), 18 deletions(-) delete mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/Main.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeStack.java create mode 100644 cms-sandbox/templates/src/test/java/com/condation/cms/templates/renderer/ScopeStackTest.java diff --git a/cms-sandbox/pom.xml b/cms-sandbox/pom.xml index 14f48d034..1c1a14bb4 100644 --- a/cms-sandbox/pom.xml +++ b/cms-sandbox/pom.xml @@ -12,4 +12,13 @@ tests templates + + + + org.junit.jupiter + junit-jupiter-engine + test + 5.11.3 + + \ No newline at end of file diff --git a/cms-sandbox/templates/pom.xml b/cms-sandbox/templates/pom.xml index 856b70b79..c5973f25b 100644 --- a/cms-sandbox/templates/pom.xml +++ b/cms-sandbox/templates/pom.xml @@ -13,4 +13,13 @@ 21 + + + + org.junit.jupiter + junit-jupiter-engine + test + 5.11.3 + + \ No newline at end of file diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/Main.java b/cms-sandbox/templates/src/main/java/com/condation/cms/Main.java deleted file mode 100644 index 384c38fa1..000000000 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/Main.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.condation.cms; - -public class Main { - public static void main(String[] args) { - System.out.println("Hello world!"); - } -} \ No newline at end of file diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java index da2bf3a2c..8d3cf6f3d 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java @@ -12,7 +12,33 @@ public class ParserExample { public static void main(String[] args) { - String template = "Hello {{ name }}! {% if condition %}World{% endif %}"; + run_example( + "Hello {{ name }}! {% if condition %}World{% endif %}" + ); + + run_example( + """ + Hello {{ name }}! + {% if condition %} + World + {% elseif condition2 %} + {% else %} + {% endif %} + Bye {{ name }} + """ + ); + + run_example( + """ + {% if condition %} + World + {% elseif condition2 %} + {% endif %} + """ + ); + } + + public static void run_example(String template) { Lexer lexer = new Lexer(template); List tokens = lexer.tokenize(); @@ -35,7 +61,10 @@ private static void printAST(ASTNode node, int depth) { } else if (node instanceof TagNode) { TagNode tag = (TagNode) node; System.out.println(indent + "Tag: " + tag.getName()); + System.out.println(indent + "Condition: " + tag.getCondition()); tag.getChildren().forEach(child -> printAST(child, depth + 1)); + } else if (!node.getChildren().isEmpty()) { + node.getChildren().forEach(child -> printAST(child, depth + 1)); } } } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java index ba161f738..8db69d3bb 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java @@ -25,6 +25,9 @@ public List tokenize() { tokens.add(new Token(Token.Type.TAG_START, "{%")); position += 2; inTag = true; // Wir sind jetzt in einem Tag + + readTagContent(tokens); // Inhalte des Tags lesen + } else if (inTag && c == '%' && peek(1) == '}') { tokens.add(new Token(Token.Type.TAG_END, "%}")); position += 2; @@ -36,8 +39,9 @@ public List tokenize() { } else if (inTag && Character.isLetter(c)) { // Nur wenn wir im Tag sind, identifizieren wir einen IDENTIFIER tokens.add(new Token(Token.Type.IDENTIFIER, readWhile(Character::isLetter))); + } else if (!inTag) { + tokens.add(new Token(Token.Type.TEXT, readUntil("{"))); // Alles bis zum nächsten '{' als Text speichern } else { - tokens.add(new Token(Token.Type.TEXT, String.valueOf(c))); position++; } } @@ -45,6 +49,16 @@ public List tokenize() { return tokens; } + private void readTagContent(List tokens) { + skipWhitespace(); + + String keyword = readWhile(Character::isLetter); + tokens.add(new Token(Token.Type.IDENTIFIER, keyword)); + + String condition = readUntil("%"); + tokens.add(new Token(Token.Type.CONDITION, condition)); + } + private char peek(int offset) { return (position + offset < input.length()) ? input.charAt(position + offset) : '\0'; } @@ -56,4 +70,18 @@ private String readWhile(java.util.function.Predicate condition) { } return result.toString(); } + + private String readUntil(String delimiter) { + StringBuilder result = new StringBuilder(); + while (position < input.length() && !input.startsWith(delimiter, position)) { + result.append(input.charAt(position++)); + } + return result.toString(); + } + + private void skipWhitespace() { + while (position < input.length() && Character.isWhitespace(input.charAt(position))) { + position++; + } + } } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Token.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Token.java index e228a7f37..d18afa9b6 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Token.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Token.java @@ -2,7 +2,7 @@ public class Token { public static enum Type { - TEXT, VARIABLE, TAG_START, TAG_END, IDENTIFIER, END + TEXT, VARIABLE, TAG_START, TAG_END, IDENTIFIER, END, CONDITION } public final Type type; diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java index 93f53c018..d4326e183 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java @@ -1,5 +1,6 @@ package com.condation.cms.templates.parser; +import java.util.ArrayList; import java.util.List; import java.util.Stack; @@ -32,12 +33,15 @@ public ASTNode parse() { break; case TAG_START: TagNode tagNode = new TagNode(); + nodeStack.peek().addChild(tagNode); nodeStack.push(tagNode); // In den neuen Kontext für Tags wechseln break; case TAG_END: if (!nodeStack.isEmpty()) { nodeStack.pop(); // Aus dem aktuellen Tag-/Variable-Block heraustreten + } else { + throw new RuntimeException("Unexpected token: TAG_END"); } break; case IDENTIFIER: @@ -48,6 +52,10 @@ public ASTNode parse() { ((VariableNode) currentNode).setVariable(token.value); // Variable setzen } break; + case CONDITION: + TagNode ifNode = (TagNode) nodeStack.peek(); + ifNode.setCondition(token.value); + break; case END: System.out.println("end token?"); break; diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java index cfaf10709..1ae7d2c95 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java @@ -1,8 +1,12 @@ package com.condation.cms.templates.parser; +import java.util.ArrayList; +import java.util.List; + public class TagNode extends ASTNode { private String name; - + private String condition; + public void setName(String name) { this.name = name; } @@ -11,8 +15,16 @@ public String getName() { return name; } + public void setCondition (String condition) { + this.condition = condition; + } + + public String getCondition () { + return condition; + } + @Override public String toString() { - return "TagNode('" + name + "')"; + return "TagNode('" + name + ", " + condition + "')"; } } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeStack.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeStack.java new file mode 100644 index 000000000..dcec2c68c --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeStack.java @@ -0,0 +1,48 @@ +package com.condation.cms.templates.renderer; + +import java.util.*; + +public class ScopeStack { + private final Deque> scopes = new ArrayDeque<>(); + + public ScopeStack() { + // Füge den globalen Scope hinzu, der immer verfügbar ist + pushScope(); + } + + // Fügt einen neuen Scope hinzu + public void pushScope() { + scopes.push(new HashMap<>()); + } + + // Entfernt den obersten Scope und alle Variablen darin + public void popScope() { + if (scopes.size() > 1) { // Der globale Scope darf nicht entfernt werden + scopes.pop(); + } else { + throw new IllegalStateException("Globaler Scope darf nicht entfernt werden."); + } + } + + // Setzt eine Variable im entsprechenden Scope + public void setVariable(String name, Object value) { + for (Map scope : scopes) { + if (scope.containsKey(name)) { + scope.put(name, value); // Überschreibt die Variable, wenn sie existiert + return; + } + } + // Wenn die Variable nicht existiert, wird sie im aktuellen Scope gesetzt + scopes.peek().put(name, value); + } + + // Ruft eine Variable ab, beginnend im obersten Scope + public Optional getVariable(String name) { + for (Map scope : scopes) { + if (scope.containsKey(name)) { + return Optional.of(scope.get(name)); + } + } + return Optional.empty(); // Variable nicht gefunden + } +} \ No newline at end of file diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/renderer/ScopeStackTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/renderer/ScopeStackTest.java new file mode 100644 index 000000000..94845fe50 --- /dev/null +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/renderer/ScopeStackTest.java @@ -0,0 +1,89 @@ +package com.condation.cms.templates.renderer; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import java.util.Optional; +import static org.junit.jupiter.api.Assertions.*; + +class ScopeStackTest { + private ScopeStack scopeStack; + + @BeforeEach + void setUp() { + scopeStack = new ScopeStack(); + } + + @Test + void testGlobalScopeVariable() { + scopeStack.setVariable("globalVar", "Global Value"); + assertEquals(Optional.of("Global Value"), scopeStack.getVariable("globalVar")); + } + + @Test + void testVariableInNewScope() { + scopeStack.setVariable("globalVar", "Global Value"); + + scopeStack.pushScope(); + scopeStack.setVariable("localVar", "Local Value"); + + assertEquals(Optional.of("Global Value"), scopeStack.getVariable("globalVar")); + assertEquals(Optional.of("Local Value"), scopeStack.getVariable("localVar")); + + scopeStack.popScope(); + assertEquals(Optional.of("Global Value"), scopeStack.getVariable("globalVar")); + assertEquals(Optional.empty(), scopeStack.getVariable("localVar")); // localVar sollte entfernt sein + } + + @Test + void testOverwriteVariableInLowerScope() { + scopeStack.setVariable("var", "Initial Value"); + + scopeStack.pushScope(); + scopeStack.setVariable("var", "Overwritten Value"); + + assertEquals(Optional.of("Overwritten Value"), scopeStack.getVariable("var")); + + scopeStack.popScope(); + assertEquals(Optional.of("Overwritten Value"), scopeStack.getVariable("var")); + } + + @Test + void testAddAndRemoveScopes() { + scopeStack.setVariable("var", "Global Value"); + + scopeStack.pushScope(); + scopeStack.setVariable("newVar", "Value in New Scope"); + + assertEquals(Optional.of("Global Value"), scopeStack.getVariable("var")); + assertEquals(Optional.of("Value in New Scope"), scopeStack.getVariable("newVar")); + + scopeStack.popScope(); + assertEquals(Optional.of("Global Value"), scopeStack.getVariable("var")); + assertEquals(Optional.empty(), scopeStack.getVariable("newVar")); // newVar sollte entfernt sein + } + + @Test + void testMultipleScopeVariableResolution() { + scopeStack.setVariable("var", "Global Value"); + + scopeStack.pushScope(); + scopeStack.setVariable("var", "Value in First Scope"); + + scopeStack.pushScope(); + assertEquals(Optional.of("Value in First Scope"), scopeStack.getVariable("var")); + + scopeStack.setVariable("var", "Value in Second Scope"); + assertEquals(Optional.of("Value in Second Scope"), scopeStack.getVariable("var")); + + scopeStack.popScope(); + assertEquals(Optional.of("Value in Second Scope"), scopeStack.getVariable("var")); + + scopeStack.popScope(); + assertEquals(Optional.of("Value in Second Scope"), scopeStack.getVariable("var")); + } + + @Test + void testPopGlobalScopeThrowsException() { + assertThrows(IllegalStateException.class, () -> scopeStack.popScope(), "Globaler Scope darf nicht entfernt werden."); + } +} diff --git a/cms-sandbox/tests/src/main/java/com/condation/cms/tests/mustache/HandlebarTest.java b/cms-sandbox/tests/src/main/java/com/condation/cms/tests/mustache/HandlebarTest.java index ace80392d..674e43aad 100644 --- a/cms-sandbox/tests/src/main/java/com/condation/cms/tests/mustache/HandlebarTest.java +++ b/cms-sandbox/tests/src/main/java/com/condation/cms/tests/mustache/HandlebarTest.java @@ -23,12 +23,6 @@ */ import com.github.jknack.handlebars.Handlebars; -import com.github.mustachejava.DefaultMustacheFactory; -import com.github.mustachejava.Mustache; -import com.github.mustachejava.MustacheFactory; -import java.io.OutputStreamWriter; -import java.io.StringReader; -import java.io.Writer; import java.util.HashMap; import java.util.List; import java.util.Map; From f5b7409995ebe4b28fc254b80ea34c9ea13ed39e Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Wed, 13 Nov 2024 21:21:17 +0100 Subject: [PATCH 03/43] test for multiline short code --- .../cms/content/shortcodes/TagParserTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/cms-content/src/test/java/com/condation/cms/content/shortcodes/TagParserTest.java b/cms-content/src/test/java/com/condation/cms/content/shortcodes/TagParserTest.java index 4ebb118c1..f795e6653 100644 --- a/cms-content/src/test/java/com/condation/cms/content/shortcodes/TagParserTest.java +++ b/cms-content/src/test/java/com/condation/cms/content/shortcodes/TagParserTest.java @@ -143,4 +143,17 @@ public void namespace() { result = tagParser.parse("[[ns1:print message='Hello CondationCMS' /]]", tagMap); Assertions.assertThat(result).isEqualTo("message: Hello CondationCMS"); } + + @Test + public void multiline () { + String content = """ + [[content]] + This is a multiline shortcode! + [[/content]] + """; + + String result = tagParser.parse(content, tagMap); + + System.out.println("-" + result + "-"); + } } From 7cab45054c68ec83de55cb5970a6d1674c9811dd Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Thu, 21 Nov 2024 21:02:10 +0100 Subject: [PATCH 04/43] add support for comments --- .../cms/templates/ParserExample.java | 6 + .../cms/templates/RenderTemplateExample.java | 4 - .../condation/cms/templates/lexer/Lexer.java | 164 ++++++++++-------- .../condation/cms/templates/lexer/State.java | 36 ++++ .../condation/cms/templates/lexer/Token.java | 18 +- .../cms/templates/parser/CommentNode.java | 24 +++ .../cms/templates/parser/Parser.java | 27 ++- 7 files changed, 198 insertions(+), 81 deletions(-) create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/State.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/CommentNode.java diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java index 8d3cf6f3d..0a926b8d4 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java @@ -12,10 +12,12 @@ public class ParserExample { public static void main(String[] args) { + System.out.println("TEST 1"); run_example( "Hello {{ name }}! {% if condition %}World{% endif %}" ); + System.out.println("TEST 2"); run_example( """ Hello {{ name }}! @@ -28,11 +30,15 @@ public static void main(String[] args) { """ ); + System.out.println("TEST 3"); run_example( """ {% if condition %} World {% elseif condition2 %} + {# + Here is a comment + #} {% endif %} """ ); diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderTemplateExample.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderTemplateExample.java index 2574be89e..69b7d7a12 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderTemplateExample.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderTemplateExample.java @@ -1,10 +1,6 @@ package com.condation.cms.templates; -import java.util.HashMap; import java.util.List; -import java.util.Map; - -import com.condation.cms.templates.Renderer.Context; import com.condation.cms.templates.lexer.Lexer; import com.condation.cms.templates.lexer.Token; import com.condation.cms.templates.parser.ASTNode; diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java index 8db69d3bb..da76aad79 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java @@ -4,84 +4,110 @@ import java.util.List; public class Lexer { - private final String input; - private int position = 0; - private boolean inTag = false; // neuer Zustand - public Lexer(String input) { - this.input = input; - } + private final String input; + private int position = 0; - public List tokenize() { - List tokens = new ArrayList<>(); - while (position < input.length()) { - char c = input.charAt(position); + private int line = 1; + private int column = 1;// neuer Zustand - if (c == '{' && peek(1) == '{') { - tokens.add(new Token(Token.Type.VARIABLE, "{{")); - position += 2; - inTag = true; // Wir sind jetzt in einer Variablen - } else if (c == '{' && peek(1) == '%') { - tokens.add(new Token(Token.Type.TAG_START, "{%")); - position += 2; - inTag = true; // Wir sind jetzt in einem Tag + private final State state = new State(); + + public Lexer(String input) { + this.input = input; + } - readTagContent(tokens); // Inhalte des Tags lesen + public List tokenize() { + List tokens = new ArrayList<>(); + while (position < input.length()) { + char c = input.charAt(position); - } else if (inTag && c == '%' && peek(1) == '}') { - tokens.add(new Token(Token.Type.TAG_END, "%}")); - position += 2; - inTag = false; // Tag endet hier - } else if (inTag && c == '}' && peek(1) == '}') { - tokens.add(new Token(Token.Type.TAG_END, "}}")); - position += 2; - inTag = false; // Variable endet hier - } else if (inTag && Character.isLetter(c)) { - // Nur wenn wir im Tag sind, identifizieren wir einen IDENTIFIER - tokens.add(new Token(Token.Type.IDENTIFIER, readWhile(Character::isLetter))); - } else if (!inTag) { - tokens.add(new Token(Token.Type.TEXT, readUntil("{"))); // Alles bis zum nächsten '{' als Text speichern - } else { - position++; - } - } - tokens.add(new Token(Token.Type.END, "")); - return tokens; - } + if (c == '{' && peek(1) == '{') { + tokens.add(new Token(Token.Type.VARIABLE_START, "{{", line, column)); + position += 2; + state.set(State.Type.VARIABLE); + } else if (c == '{' && peek(1) == '%') { + tokens.add(new Token(Token.Type.TAG_START, "{%", line, column)); + position += 2; + state.set(State.Type.TAG); + readTagContent(tokens); // Inhalte des Tags lesen + } else if (c == '{' && peek(1) == '#') { + tokens.add(new Token(Token.Type.COMMENT_START, "{*", line, column)); + position += 2; + state.set(State.Type.COMMENT); + } else if (state.is(State.Type.TAG) && c == '%' && peek(1) == '}') { + tokens.add(new Token(Token.Type.TAG_END, "%}", line, column)); + position += 2; + state.set(State.Type.NONE); + } else if (state.is(State.Type.VARIABLE) && c == '}' && peek(1) == '}') { + tokens.add(new Token(Token.Type.VARIABLE_END, "}}", line, column)); + position += 2; + state.set(State.Type.NONE); + } else if (state.is(State.Type.COMMENT) && c == '#' && peek(1) == '}') { + tokens.add(new Token(Token.Type.COMMENT_END, "#}", line, column)); + position += 2; + state.set(State.Type.NONE); + } else if (state.is(State.Type.VARIABLE, State.Type.TAG) && Character.isLetter(c)) { + // Nur wenn wir im Tag sind, identifizieren wir einen IDENTIFIER + tokens.add(new Token(Token.Type.IDENTIFIER, readWhile(Character::isLetter), line, column)); + } else if (state.is(State.Type.COMMENT)) { + tokens.add(new Token(Token.Type.COMMENT_VALUE, readUntil("#}"), line, column)); // Alles bis zum nächsten '{' als Text speichern + } else if (!state.is(State.Type.VARIABLE, State.Type.TAG)) { + tokens.add(new Token(Token.Type.TEXT, readUntil("{"), line, column)); // Alles bis zum nächsten '{' als Text speichern + } else { + advance(); + } + } + tokens.add(new Token(Token.Type.END, "", line, column)); + return tokens; + } - private void readTagContent(List tokens) { - skipWhitespace(); + private void readTagContent(List tokens) { + skipWhitespace(); - String keyword = readWhile(Character::isLetter); - tokens.add(new Token(Token.Type.IDENTIFIER, keyword)); - - String condition = readUntil("%"); - tokens.add(new Token(Token.Type.CONDITION, condition)); - } + String keyword = readWhile(Character::isLetter); + tokens.add(new Token(Token.Type.IDENTIFIER, keyword, line, column)); - private char peek(int offset) { - return (position + offset < input.length()) ? input.charAt(position + offset) : '\0'; - } + String condition = readUntil("%"); + tokens.add(new Token(Token.Type.EXPRESSION, condition, line, column)); + } - private String readWhile(java.util.function.Predicate condition) { - StringBuilder result = new StringBuilder(); - while (position < input.length() && condition.test(input.charAt(position))) { - result.append(input.charAt(position++)); - } - return result.toString(); - } + private char peek(int offset) { + return (position + offset < input.length()) ? input.charAt(position + offset) : '\0'; + } - private String readUntil(String delimiter) { - StringBuilder result = new StringBuilder(); - while (position < input.length() && !input.startsWith(delimiter, position)) { - result.append(input.charAt(position++)); - } - return result.toString(); - } + private String readWhile(java.util.function.Predicate condition) { + StringBuilder result = new StringBuilder(); + while (position < input.length() && condition.test(input.charAt(position))) { + result.append(input.charAt(position)); + + advance(); + } + return result.toString(); + } - private void skipWhitespace() { - while (position < input.length() && Character.isWhitespace(input.charAt(position))) { - position++; - } - } + private String readUntil(String delimiter) { + StringBuilder result = new StringBuilder(); + while (position < input.length() && !input.startsWith(delimiter, position)) { + result.append(input.charAt(position)); + advance(); + } + return result.toString(); + } + + private void skipWhitespace() { + while (position < input.length() && Character.isWhitespace(input.charAt(position))) { + advance(); + } + } + + private void advance() { + if (input.charAt(position) == '\n') { + line++; + column = 1; + } else { + column++; + } + position++; + } } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/State.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/State.java new file mode 100644 index 000000000..5fc8ce3cb --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/State.java @@ -0,0 +1,36 @@ +package com.condation.cms.templates.lexer; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * + * @author thmar + */ +public class State { + public enum Type { + NONE, + TAG, + VARIABLE, + COMMENT + } + + private Type current = Type.NONE; + + + public void set (Type type) { + current = type; + } + + public boolean is (Type... types) { + if (types == null || types.length == 0) { + return false; + } + + List candidates = new ArrayList<>(); + candidates.addAll(Arrays.asList(types)); + + return candidates.contains(current); + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Token.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Token.java index d18afa9b6..6d20d2f80 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Token.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Token.java @@ -2,15 +2,29 @@ public class Token { public static enum Type { - TEXT, VARIABLE, TAG_START, TAG_END, IDENTIFIER, END, CONDITION + TEXT, + VARIABLE_START, + VARIABLE_END, + TAG_START, + TAG_END, + COMMENT_START, + COMMENT_END, + COMMENT_VALUE, + IDENTIFIER, + END, + EXPRESSION } public final Type type; public final String value; + public final int line; + public final int column; - public Token(Type type, String value) { + public Token(Type type, String value, int line, int column) { this.type = type; this.value = value; + this.line = line; + this.column = column; } @Override diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/CommentNode.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/CommentNode.java new file mode 100644 index 000000000..83ebfdc22 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/CommentNode.java @@ -0,0 +1,24 @@ +package com.condation.cms.templates.parser; + +public class CommentNode extends ASTNode { + + private String value; + + public CommentNode() { + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + + + @Override + public String toString() { + return "CommentNode()"; + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java index d4326e183..e3f642340 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java @@ -5,6 +5,7 @@ import java.util.Stack; import com.condation.cms.templates.lexer.Token; +import static com.condation.cms.templates.lexer.Token.Type.VARIABLE_START; public class Parser { private final List tokens; @@ -26,10 +27,21 @@ public ASTNode parse() { case TEXT: nodeStack.peek().addChild(new TextNode(token.value)); break; - case VARIABLE: + case COMMENT_VALUE: + ASTNode node = nodeStack.peek(); + if (node instanceof CommentNode commentNode) { + commentNode.setValue(token.value); + } + break; + case VARIABLE_START: VariableNode variableNode = new VariableNode(); nodeStack.peek().addChild(variableNode); nodeStack.push(variableNode); // In den neuen Kontext für Variablen wechseln + break; + case COMMENT_START: + CommentNode commentNode = new CommentNode(); + nodeStack.peek().addChild(commentNode); + nodeStack.push(commentNode); // In den neuen Kontext für Variablen wechseln break; case TAG_START: TagNode tagNode = new TagNode(); @@ -38,6 +50,8 @@ public ASTNode parse() { nodeStack.push(tagNode); // In den neuen Kontext für Tags wechseln break; case TAG_END: + case VARIABLE_END: + case COMMENT_END: if (!nodeStack.isEmpty()) { nodeStack.pop(); // Aus dem aktuellen Tag-/Variable-Block heraustreten } else { @@ -46,13 +60,14 @@ public ASTNode parse() { break; case IDENTIFIER: ASTNode currentNode = nodeStack.peek(); - if (currentNode instanceof TagNode) { - ((TagNode) currentNode).setName(token.value); // Tag-Name setzen - } else if (currentNode instanceof VariableNode) { - ((VariableNode) currentNode).setVariable(token.value); // Variable setzen + if (currentNode instanceof TagNode tagNode1) { + tagNode1.setName(token.value); // Tag-Name setzen + } else if (currentNode instanceof VariableNode variableNode1) { + variableNode1.setVariable(token.value); // Variable setzen } break; - case CONDITION: + + case EXPRESSION: TagNode ifNode = (TagNode) nodeStack.peek(); ifNode.setCondition(token.value); break; From 64534d4dbd81f25b9cda489454020637681a427c Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Fri, 22 Nov 2024 16:58:07 +0100 Subject: [PATCH 05/43] update some token processing --- cms-sandbox/templates/pom.xml | 44 +++-- .../cms/templates/ParserExample.java | 96 ++++++---- .../cms/templates/RenderTemplateExample.java | 5 +- .../java/com/condation/cms/templates/Tag.java | 20 ++ .../cms/templates/TemplateConfiguration.java | 26 +++ .../cms/templates/parser/Parser.java | 178 ++++++++++++------ .../cms/templates/parser/TagNode.java | 10 +- .../cms/templates/parser/TokenStream.java | 72 +++++++ .../cms/templates/tags/ElseIfTag.java | 16 ++ .../condation/cms/templates/tags/ElseTag.java | 15 ++ .../cms/templates/tags/EndIfTag.java | 15 ++ .../condation/cms/templates/tags/IfTag.java | 26 +++ .../cms/templates/parser/ParserTest.java | 40 ++++ .../cms/templates/parser/TokenStreamTest.java | 38 ++++ 14 files changed, 480 insertions(+), 121 deletions(-) create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/Tag.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateConfiguration.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TokenStream.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseIfTag.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseTag.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndIfTag.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IfTag.java create mode 100644 cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/ParserTest.java create mode 100644 cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/TokenStreamTest.java diff --git a/cms-sandbox/templates/pom.xml b/cms-sandbox/templates/pom.xml index c5973f25b..1a2c2e1a7 100644 --- a/cms-sandbox/templates/pom.xml +++ b/cms-sandbox/templates/pom.xml @@ -1,25 +1,33 @@ - 4.0.0 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - com.condation.cms - templates - 1.0-SNAPSHOT + com.condation.cms + templates + 1.0-SNAPSHOT - - 21 - 21 - + + 21 + 21 + - - - org.junit.jupiter - junit-jupiter-engine - test - 5.11.3 - - + + + org.junit.jupiter + junit-jupiter-engine + test + 5.11.3 + + + + org.assertj + assertj-core + 3.26.3 + test + + + \ No newline at end of file diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java index 0a926b8d4..b901ac432 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java @@ -9,30 +9,46 @@ import com.condation.cms.templates.parser.TagNode; import com.condation.cms.templates.parser.TextNode; import com.condation.cms.templates.parser.VariableNode; +import com.condation.cms.templates.tags.ElseIfTag; +import com.condation.cms.templates.tags.ElseTag; +import com.condation.cms.templates.tags.EndIfTag; +import com.condation.cms.templates.tags.IfTag; public class ParserExample { - public static void main(String[] args) { - System.out.println("TEST 1"); - run_example( - "Hello {{ name }}! {% if condition %}World{% endif %}" - ); + + static TemplateConfiguration config = new TemplateConfiguration(); + static { + config.registerTag(new IfTag()); + config.registerTag(new ElseIfTag()); + config.registerTag(new ElseTag()); + config.registerTag(new EndIfTag()); + } + + public static void main(String[] args) { + + System.out.println("TEST 1"); + run_example( + "Hello {{ name }}! {% if condition %}World{% endif %}" + ); System.out.println("TEST 2"); - run_example( - """ + run_example( + """ Hello {{ name }}! {% if condition %} - World + Hello {% elseif condition2 %} + World {% else %} + Moon {% endif %} Bye {{ name }} """ - ); + ); System.out.println("TEST 3"); - run_example( - """ + run_example( + """ {% if condition %} World {% elseif condition2 %} @@ -41,36 +57,36 @@ public static void main(String[] args) { #} {% endif %} """ - ); - } + ); + } + + public static void run_example(String template) { + Lexer lexer = new Lexer(template); + List tokens = lexer.tokenize(); + + Parser parser = new Parser(tokens, config); + ASTNode ast = parser.parse(); - public static void run_example(String template) { - Lexer lexer = new Lexer(template); - List tokens = lexer.tokenize(); - - Parser parser = new Parser(tokens); - ASTNode ast = parser.parse(); + System.out.println("Tokens:"); + tokens.forEach(System.out::println); - System.out.println("Tokens:"); - tokens.forEach(System.out::println); - - System.out.println("AST:"); - printAST(ast, 0); - } + System.out.println("AST:"); + printAST(ast, 0); + } - private static void printAST(ASTNode node, int depth) { - String indent = " ".repeat(depth * 2); - if (node instanceof TextNode) { - System.out.println(indent + "Text: " + ((TextNode) node).text); - } else if (node instanceof VariableNode) { - System.out.println(indent + "Variable: " + ((VariableNode) node).getVariable()); - } else if (node instanceof TagNode) { - TagNode tag = (TagNode) node; - System.out.println(indent + "Tag: " + tag.getName()); - System.out.println(indent + "Condition: " + tag.getCondition()); - tag.getChildren().forEach(child -> printAST(child, depth + 1)); - } else if (!node.getChildren().isEmpty()) { - node.getChildren().forEach(child -> printAST(child, depth + 1)); - } - } + private static void printAST(ASTNode node, int depth) { + String indent = " ".repeat(depth * 2); + if (node instanceof TextNode) { + System.out.println(indent + "Text: " + ((TextNode) node).text); + } else if (node instanceof VariableNode) { + System.out.println(indent + "Variable: " + ((VariableNode) node).getVariable()); + } else if (node instanceof TagNode) { + TagNode tag = (TagNode) node; + System.out.println(indent + "Tag: " + tag.getName()); + System.out.println(indent + "Condition: " + tag.getCondition()); + tag.getChildren().forEach(child -> printAST(child, depth + 1)); + } else if (!node.getChildren().isEmpty()) { + node.getChildren().forEach(child -> printAST(child, depth + 1)); + } + } } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderTemplateExample.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderTemplateExample.java index 69b7d7a12..4e72a2634 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderTemplateExample.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderTemplateExample.java @@ -8,12 +8,15 @@ public class RenderTemplateExample { public static void main(String[] args) { + + TemplateConfiguration config = new TemplateConfiguration(); + // Beispiel-Template und Kontext String template = "Hello, {% if user %}{{ user }}{% endif %}!"; Lexer lexer = new Lexer(template); List tokens = lexer.tokenize(); - Parser parser = new Parser(tokens); + Parser parser = new Parser(tokens, config); ASTNode ast = parser.parse(); // Kontext mit Werten für das Template diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Tag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Tag.java new file mode 100644 index 000000000..7313e398d --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Tag.java @@ -0,0 +1,20 @@ +package com.condation.cms.templates; + +import java.util.Optional; + +/** + * + * @author t.marx + */ +public interface Tag { + + String getTagName(); + + default Optional getCloseTagName () { + return Optional.empty(); + } + + default boolean supportsNestedTag(String tagName) { + return false; + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateConfiguration.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateConfiguration.java new file mode 100644 index 000000000..4ed82f009 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateConfiguration.java @@ -0,0 +1,26 @@ +package com.condation.cms.templates; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * + * @author t.marx + */ +public class TemplateConfiguration { + + private final Map registeredTags = new HashMap<>(); + + public void registerTag (Tag tag) { + registeredTags.put(tag.getTagName(), tag); + } + + public boolean hasTag (String tagName) { + return registeredTags.containsKey(tagName); + } + + public Optional getTag (String tagName) { + return Optional.ofNullable(registeredTags.get(tagName)); + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java index e3f642340..93054815d 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java @@ -1,85 +1,141 @@ package com.condation.cms.templates.parser; -import java.util.ArrayList; +import com.condation.cms.templates.Tag; +import com.condation.cms.templates.TemplateConfiguration; import java.util.List; import java.util.Stack; import com.condation.cms.templates.lexer.Token; import static com.condation.cms.templates.lexer.Token.Type.VARIABLE_START; +import java.util.Optional; public class Parser { - private final List tokens; - private int position = 0; - public Parser(List tokens) { - this.tokens = tokens; - } + private final TemplateConfiguration configuration; - public ASTNode parse() { - ASTNode root = new ASTNode(); - Stack nodeStack = new Stack<>(); - nodeStack.push(root); +// private final List tokens; +// private int position = 0; + + private final TokenStream tokenStream; - while (position < tokens.size()) { - Token token = tokens.get(position); + public Parser(List tokens, TemplateConfiguration configuration) { +// this.tokens = tokens; + this.configuration = configuration; + + tokenStream = new TokenStream(tokens); + } - switch (token.type) { - case TEXT: - nodeStack.peek().addChild(new TextNode(token.value)); - break; + public ASTNode parse() { + ASTNode root = new ASTNode(); + Stack nodeStack = new Stack<>(); + nodeStack.push(root); + + Token token = null; + while ((token = tokenStream.peek()) != null) { + switch (token.type) { + case TEXT: + { + nodeStack.peek().addChild(new TextNode(token.value)); + break; + } case COMMENT_VALUE: + { ASTNode node = nodeStack.peek(); - if (node instanceof CommentNode commentNode) { + if (node instanceof CommentNode commentNode) { commentNode.setValue(token.value); } break; - case VARIABLE_START: - VariableNode variableNode = new VariableNode(); - nodeStack.peek().addChild(variableNode); - nodeStack.push(variableNode); // In den neuen Kontext für Variablen wechseln - break; + } + case VARIABLE_START: + { + VariableNode variableNode = new VariableNode(); + nodeStack.peek().addChild(variableNode); + nodeStack.push(variableNode); // In den neuen Kontext für Variablen wechseln + break; + } case COMMENT_START: - CommentNode commentNode = new CommentNode(); - nodeStack.peek().addChild(commentNode); - nodeStack.push(commentNode); // In den neuen Kontext für Variablen wechseln - break; - case TAG_START: - TagNode tagNode = new TagNode(); - - nodeStack.peek().addChild(tagNode); - nodeStack.push(tagNode); // In den neuen Kontext für Tags wechseln - break; - case TAG_END: + { + CommentNode commentNode = new CommentNode(); + nodeStack.peek().addChild(commentNode); + nodeStack.push(commentNode); // In den neuen Kontext für Variablen wechseln + break; + } + case TAG_START: + { + TagNode tagNode = new TagNode(); + + nodeStack.peek().addChild(tagNode); + nodeStack.push(tagNode); // In den neuen Kontext für Tags wechseln + break; + } + case TAG_END: + { + if (!nodeStack.isEmpty() && nodeStack.peek() instanceof TagNode tempNode) { + nodeStack.pop(); + /* + if (configuration.hasTag(tempNode.getName())) { + Tag tag = configuration.getTag(tempNode.getName()).get(); + if (tag.getCloseTagName().isEmpty()) { + nodeStack.pop(); + } + } else { + throw new RuntimeException("Undefined tag: " + tempNode.getName()); + } + */ + } else { + throw new RuntimeException("Unexpected token: TAG_END"); + } + break; + } case VARIABLE_END: + { + if (!nodeStack.isEmpty()) { + nodeStack.pop(); // Aus dem aktuellen Tag-/Variable-Block heraustreten + } else { + throw new RuntimeException("Unexpected token: VARIABLE_END"); + } + break; + } case COMMENT_END: - if (!nodeStack.isEmpty()) { - nodeStack.pop(); // Aus dem aktuellen Tag-/Variable-Block heraustreten - } else { - throw new RuntimeException("Unexpected token: TAG_END"); - } - break; - case IDENTIFIER: - ASTNode currentNode = nodeStack.peek(); - if (currentNode instanceof TagNode tagNode1) { - tagNode1.setName(token.value); // Tag-Name setzen - } else if (currentNode instanceof VariableNode variableNode1) { - variableNode1.setVariable(token.value); // Variable setzen - } - break; + { + if (!nodeStack.isEmpty()) { + nodeStack.pop(); // Aus dem aktuellen Tag-/Variable-Block heraustreten + } else { + throw new RuntimeException("Unexpected token: COMMENT_END"); + } + break; + } + case IDENTIFIER: + { + ASTNode currentNode = nodeStack.peek(); + if (currentNode instanceof TagNode tagNode1) { + tagNode1.setName(token.value); // Tag-Name setzen + } else if (currentNode instanceof VariableNode variableNode1) { + variableNode1.setVariable(token.value); // Variable setzen + } + break; + } + case EXPRESSION: + { + TagNode ifNode = (TagNode) nodeStack.peek(); + ifNode.setCondition(token.value); + break; + } + case END: + { + System.out.println("end token?"); + break; + } + default: + throw new RuntimeException("Unexpected token: " + token.type); + } + tokenStream.next(); + } - case EXPRESSION: - TagNode ifNode = (TagNode) nodeStack.peek(); - ifNode.setCondition(token.value); - break; - case END: - System.out.println("end token?"); - break; - default: - throw new RuntimeException("Unexpected token: " + token.type); - } - position++; - } + if (nodeStack.size() > 1) { + throw new RuntimeException("Unclosed tag or block detected"); + } - return root; - } + return root; + } } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java index 1ae7d2c95..c1fa06c8c 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java @@ -6,7 +6,15 @@ public class TagNode extends ASTNode { private String name; private String condition; - + + public TagNode () { + + } + + public TagNode(String name) { + this.name = name; + } + public void setName(String name) { this.name = name; } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TokenStream.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TokenStream.java new file mode 100644 index 000000000..68106eb53 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TokenStream.java @@ -0,0 +1,72 @@ +package com.condation.cms.templates.parser; + +import com.condation.cms.templates.lexer.Token; +import java.util.List; + +/** + * + * @author t.marx + */ +public class TokenStream { + + private final List tokens; + + private int position = 0; + + public TokenStream(List tokens) { + this.tokens = tokens; + } + + public Token next() { + if (position >= tokens.size()) { + return null; + } + return tokens.get(position++); + } + + public Token peek() { + if (position >= tokens.size()) { + return null; + } + return tokens.get(position); + } + + public void skip() { + skip(1); + } + + public void skip(int n) { + position += n; + } + + /** + * Setzt die Position auf einen bestimmten Index zurück. + * + * @param position Die neue Position (muss innerhalb der Grenzen liegen). + * @throws IllegalArgumentException Wenn die Position ungültig ist. + */ + public void reset(int position) { + if (position < 0 || position > tokens.size()) { + throw new IllegalArgumentException("Invalid position: " + position); + } + this.position = position; + } + + /** + * Gibt die aktuelle Position im Stream zurück. + * + * @return Die aktuelle Position. + */ + public int getPosition() { + return position; + } + + /** + * Gibt an, ob das Ende des Streams erreicht wurde. + * + * @return true, wenn keine weiteren Tokens verfügbar sind. + */ + public boolean isEnd() { + return position >= tokens.size(); + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseIfTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseIfTag.java new file mode 100644 index 000000000..7d1840c05 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseIfTag.java @@ -0,0 +1,16 @@ +package com.condation.cms.templates.tags; + +import com.condation.cms.templates.Tag; +import java.util.Optional; + +/** + * + * @author t.marx + */ +public class ElseIfTag implements Tag { + + @Override + public String getTagName() { + return "elseif"; + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseTag.java new file mode 100644 index 000000000..3ef6b7cd6 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseTag.java @@ -0,0 +1,15 @@ +package com.condation.cms.templates.tags; + +import com.condation.cms.templates.Tag; + +/** + * + * @author t.marx + */ +public class ElseTag implements Tag { + + @Override + public String getTagName() { + return "else"; + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndIfTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndIfTag.java new file mode 100644 index 000000000..c8e6dc51b --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndIfTag.java @@ -0,0 +1,15 @@ +package com.condation.cms.templates.tags; + +import com.condation.cms.templates.Tag; + +/** + * + * @author t.marx + */ +public class EndIfTag implements Tag { + + @Override + public String getTagName() { + return "endif"; + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IfTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IfTag.java new file mode 100644 index 000000000..568a96c60 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IfTag.java @@ -0,0 +1,26 @@ +package com.condation.cms.templates.tags; + +import com.condation.cms.templates.Tag; +import java.util.Optional; + +/** + * + * @author t.marx + */ +public class IfTag implements Tag { + + @Override + public String getTagName() { + return "if"; + } + + @Override + public Optional getCloseTagName() { + return Optional.of("endif"); + } + + @Override + public boolean supportsNestedTag(String tagName) { + return tagName.equals("elseif") || tagName.equals("else"); + } +} diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/ParserTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/ParserTest.java new file mode 100644 index 000000000..bcc687170 --- /dev/null +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/ParserTest.java @@ -0,0 +1,40 @@ +package com.condation.cms.templates.parser; + +import com.condation.cms.templates.TemplateConfiguration; +import com.condation.cms.templates.lexer.Token; +import com.condation.cms.templates.tags.ElseIfTag; +import com.condation.cms.templates.tags.ElseTag; +import com.condation.cms.templates.tags.IfTag; +import java.util.List; +import org.junit.jupiter.api.Test; + +/** + * + * @author t.marx + */ +public class ParserTest { + + @Test + public void testSomeMethod() { + TemplateConfiguration config = new TemplateConfiguration(); + config.registerTag(new IfTag()); + config.registerTag(new ElseIfTag()); + config.registerTag(new ElseTag()); + + List tokens = List.of( + new Token(Token.Type.TAG_START, "if", 0,0), + new Token(Token.Type.EXPRESSION, "condition", 0,0), + new Token(Token.Type.TAG_START, "elseif", 0,0), + new Token(Token.Type.EXPRESSION, "condition2", 0,0), + new Token(Token.Type.TAG_START, "else", 0,0), + new Token(Token.Type.TEXT, "fallback", 0,0), + new Token(Token.Type.TAG_END, "endif", 0,0) + ); + + Parser parser = new Parser(tokens, config); + ASTNode ast = parser.parse(); + + System.out.println(ast); + } + +} diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/TokenStreamTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/TokenStreamTest.java new file mode 100644 index 000000000..f76a835c3 --- /dev/null +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/TokenStreamTest.java @@ -0,0 +1,38 @@ +package com.condation.cms.templates.parser; + +import com.condation.cms.templates.lexer.Token; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * + * @author t.marx + */ +public class TokenStreamTest { + + @Test + public void testSomeMethod() { + + var tokenStream = new TokenStream(List.of( + new Token(Token.Type.TAG_START, "eins", 0, 0), + new Token(Token.Type.EXPRESSION, "zwei", 0, 0), + new Token(Token.Type.TAG_START, "drei", 0, 0) + )); + + Assertions.assertThat(tokenStream.peek().value).isEqualTo("eins"); + Assertions.assertThat(tokenStream.next().value).isEqualTo("eins"); + Assertions.assertThat(tokenStream.getPosition()).isEqualTo(1); + Assertions.assertThat(tokenStream.next().value).isEqualTo("zwei"); + Assertions.assertThat(tokenStream.getPosition()).isEqualTo(2); + Assertions.assertThat(tokenStream.next().value).isEqualTo("drei"); + + tokenStream.reset(1); + Assertions.assertThat(tokenStream.getPosition()).isEqualTo(1); + Assertions.assertThat(tokenStream.peek().value).isEqualTo("zwei"); + tokenStream.skip(); + Assertions.assertThat(tokenStream.getPosition()).isEqualTo(2); + Assertions.assertThat(tokenStream.peek().value).isEqualTo("drei"); + } + +} From f0a643e443e02e4b3b6f4da37ee0796ec541f4b6 Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Sun, 24 Nov 2024 18:13:25 +0100 Subject: [PATCH 06/43] fix parser to detect and tags --- .../cms/templates/ParserExample.java | 1 + .../java/com/condation/cms/templates/Tag.java | 4 ++ .../cms/templates/parser/Parser.java | 56 +++++++++---------- .../cms/templates/tags/EndIfTag.java | 5 ++ 4 files changed, 38 insertions(+), 28 deletions(-) diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java index b901ac432..6dda5a54c 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java @@ -17,6 +17,7 @@ public class ParserExample { static TemplateConfiguration config = new TemplateConfiguration(); + static { config.registerTag(new IfTag()); config.registerTag(new ElseIfTag()); diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Tag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Tag.java index 7313e398d..5660b2908 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Tag.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Tag.java @@ -17,4 +17,8 @@ default Optional getCloseTagName () { default boolean supportsNestedTag(String tagName) { return false; } + + default boolean isEndTag () { + return false; + } } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java index 93054815d..11f16cafe 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java @@ -15,13 +15,12 @@ public class Parser { // private final List tokens; // private int position = 0; - private final TokenStream tokenStream; public Parser(List tokens, TemplateConfiguration configuration) { // this.tokens = tokens; this.configuration = configuration; - + tokenStream = new TokenStream(tokens); } @@ -33,62 +32,67 @@ public ASTNode parse() { Token token = null; while ((token = tokenStream.peek()) != null) { switch (token.type) { - case TEXT: - { + case TEXT: { nodeStack.peek().addChild(new TextNode(token.value)); break; } - case COMMENT_VALUE: - { + case COMMENT_VALUE: { ASTNode node = nodeStack.peek(); if (node instanceof CommentNode commentNode) { commentNode.setValue(token.value); } break; } - case VARIABLE_START: - { + case VARIABLE_START: { VariableNode variableNode = new VariableNode(); nodeStack.peek().addChild(variableNode); nodeStack.push(variableNode); // In den neuen Kontext für Variablen wechseln break; } - case COMMENT_START: - { + case COMMENT_START: { CommentNode commentNode = new CommentNode(); nodeStack.peek().addChild(commentNode); nodeStack.push(commentNode); // In den neuen Kontext für Variablen wechseln break; } - case TAG_START: - { + case TAG_START: { TagNode tagNode = new TagNode(); nodeStack.peek().addChild(tagNode); nodeStack.push(tagNode); // In den neuen Kontext für Tags wechseln break; } - case TAG_END: - { + case TAG_END: { if (!nodeStack.isEmpty() && nodeStack.peek() instanceof TagNode tempNode) { - nodeStack.pop(); - /* if (configuration.hasTag(tempNode.getName())) { Tag tag = configuration.getTag(tempNode.getName()).get(); - if (tag.getCloseTagName().isEmpty()) { + + if (tag.isEndTag()) { + nodeStack.pop(); + + var temp = (TagNode) nodeStack.peek(); + + var ptag = configuration.getTag(temp.getName()).get(); + + if (ptag.getCloseTagName().isPresent() + && ptag.getCloseTagName().get().equals(tag.getTagName())) { + nodeStack.pop(); + } else { + throw new RuntimeException("invalid closing tag"); + } + } else if (tag.getCloseTagName().isEmpty()) { nodeStack.pop(); } + } else { throw new RuntimeException("Undefined tag: " + tempNode.getName()); } - */ } else { throw new RuntimeException("Unexpected token: TAG_END"); } break; } - case VARIABLE_END: - { + case VARIABLE_END: { if (!nodeStack.isEmpty()) { nodeStack.pop(); // Aus dem aktuellen Tag-/Variable-Block heraustreten } else { @@ -96,8 +100,7 @@ public ASTNode parse() { } break; } - case COMMENT_END: - { + case COMMENT_END: { if (!nodeStack.isEmpty()) { nodeStack.pop(); // Aus dem aktuellen Tag-/Variable-Block heraustreten } else { @@ -105,8 +108,7 @@ public ASTNode parse() { } break; } - case IDENTIFIER: - { + case IDENTIFIER: { ASTNode currentNode = nodeStack.peek(); if (currentNode instanceof TagNode tagNode1) { tagNode1.setName(token.value); // Tag-Name setzen @@ -115,14 +117,12 @@ public ASTNode parse() { } break; } - case EXPRESSION: - { + case EXPRESSION: { TagNode ifNode = (TagNode) nodeStack.peek(); ifNode.setCondition(token.value); break; } - case END: - { + case END: { System.out.println("end token?"); break; } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndIfTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndIfTag.java index c8e6dc51b..a1acce19e 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndIfTag.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndIfTag.java @@ -12,4 +12,9 @@ public class EndIfTag implements Tag { public String getTagName() { return "endif"; } + + @Override + public boolean isEndTag() { + return true; + } } From f44db8f2cfbb1e71c4a04fa168ab4b3253a66646 Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Sun, 24 Nov 2024 21:25:52 +0100 Subject: [PATCH 07/43] implement template class --- cms-sandbox/templates/pom.xml | 6 +++ .../cms/templates/DefaultTemplate.java | 21 ++++++++++ .../cms/templates/ParserExample.java | 9 ++-- .../cms/templates/RenderTemplateExample.java | 6 +-- .../com/condation/cms/templates/Template.java | 16 +++++++ .../cms/templates/TemplateConfiguration.java | 10 ++++- .../cms/templates/TemplateEngine.java | 22 ++++++++++ .../cms/templates/TemplateLoader.java | 10 +++++ .../condation/cms/templates/lexer/Lexer.java | 5 ++- .../loaders/StringTemplateLoader.java | 27 ++++++++++++ .../cms/templates/parser/Parser.java | 11 +---- .../cms/templates/parser/TokenStream.java | 5 +++ .../cms/templates/TemplateEngineTest.java | 42 +++++++++++++++++++ .../cms/templates/parser/ParserTest.java | 6 ++- 14 files changed, 175 insertions(+), 21 deletions(-) create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/DefaultTemplate.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/Template.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateLoader.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/loaders/StringTemplateLoader.java create mode 100644 cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineTest.java diff --git a/cms-sandbox/templates/pom.xml b/cms-sandbox/templates/pom.xml index 1a2c2e1a7..f3e945b82 100644 --- a/cms-sandbox/templates/pom.xml +++ b/cms-sandbox/templates/pom.xml @@ -28,6 +28,12 @@ 3.26.3 test + + org.projectlombok + lombok + 1.18.36 + provided + \ No newline at end of file diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/DefaultTemplate.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/DefaultTemplate.java new file mode 100644 index 000000000..9d71e40d7 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/DefaultTemplate.java @@ -0,0 +1,21 @@ +package com.condation.cms.templates; + +import com.condation.cms.templates.parser.ASTNode; +import java.util.Map; +import lombok.RequiredArgsConstructor; + +/** + * + * @author thmar + */ +@RequiredArgsConstructor +public class DefaultTemplate implements Template { + + private final ASTNode rootNode; + + @Override + public String execute(Map context) { + return rootNode.toString(); + } + +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java index 6dda5a54c..3e81a2822 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java @@ -8,6 +8,7 @@ import com.condation.cms.templates.parser.Parser; import com.condation.cms.templates.parser.TagNode; import com.condation.cms.templates.parser.TextNode; +import com.condation.cms.templates.parser.TokenStream; import com.condation.cms.templates.parser.VariableNode; import com.condation.cms.templates.tags.ElseIfTag; import com.condation.cms.templates.tags.ElseTag; @@ -63,13 +64,13 @@ public static void main(String[] args) { public static void run_example(String template) { Lexer lexer = new Lexer(template); - List tokens = lexer.tokenize(); + TokenStream tokenStream = lexer.tokenize(); - Parser parser = new Parser(tokens, config); - ASTNode ast = parser.parse(); + Parser parser = new Parser( config); + ASTNode ast = parser.parse(tokenStream); System.out.println("Tokens:"); - tokens.forEach(System.out::println); + tokenStream.forEach(System.out::println); System.out.println("AST:"); printAST(ast, 0); diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderTemplateExample.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderTemplateExample.java index 4e72a2634..90a94062e 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderTemplateExample.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderTemplateExample.java @@ -14,10 +14,10 @@ public static void main(String[] args) { // Beispiel-Template und Kontext String template = "Hello, {% if user %}{{ user }}{% endif %}!"; Lexer lexer = new Lexer(template); - List tokens = lexer.tokenize(); + var tokenStream = lexer.tokenize(); - Parser parser = new Parser(tokens, config); - ASTNode ast = parser.parse(); + Parser parser = new Parser(config); + ASTNode ast = parser.parse(tokenStream); // Kontext mit Werten für das Template var context = new Renderer.Context(); diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Template.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Template.java new file mode 100644 index 000000000..a711b7719 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Template.java @@ -0,0 +1,16 @@ +package com.condation.cms.templates; + +import java.util.Map; + +/** + * + * @author thmar + */ +public interface Template { + + default String execute () { + return execute(Map.of()); + } + + String execute(Map context); +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateConfiguration.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateConfiguration.java index 4ed82f009..b631f24d3 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateConfiguration.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateConfiguration.java @@ -3,6 +3,8 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; +import lombok.Getter; +import lombok.Setter; /** * @@ -12,8 +14,14 @@ public class TemplateConfiguration { private final Map registeredTags = new HashMap<>(); - public void registerTag (Tag tag) { + @Getter + @Setter + private TemplateLoader templateLoader; + + public TemplateConfiguration registerTag (Tag tag) { registeredTags.put(tag.getTagName(), tag); + + return this; } public boolean hasTag (String tagName) { diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java index 56715b491..631ff7517 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java @@ -1,5 +1,27 @@ package com.condation.cms.templates; +import com.condation.cms.templates.lexer.Lexer; +import com.condation.cms.templates.parser.Parser; + public class TemplateEngine { + private final TemplateConfiguration configuration; + + private Parser parser; + + public TemplateEngine(TemplateConfiguration configuration) { + this.configuration = configuration; + parser = new Parser(configuration); + } + + public Template getTemplate (String template) { + + String templateString = configuration.getTemplateLoader().load(template); + + var tokenStream = new Lexer(templateString).tokenize(); + + var rootNode = parser.parse(tokenStream); + + return new DefaultTemplate(rootNode); + } } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateLoader.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateLoader.java new file mode 100644 index 000000000..47656db16 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateLoader.java @@ -0,0 +1,10 @@ +package com.condation.cms.templates; + +/** + * + * @author thmar + */ +public interface TemplateLoader { + + String load (String template); +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java index da76aad79..bd69d99da 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java @@ -1,5 +1,6 @@ package com.condation.cms.templates.lexer; +import com.condation.cms.templates.parser.TokenStream; import java.util.ArrayList; import java.util.List; @@ -17,7 +18,7 @@ public Lexer(String input) { this.input = input; } - public List tokenize() { + public TokenStream tokenize() { List tokens = new ArrayList<>(); while (position < input.length()) { char c = input.charAt(position); @@ -59,7 +60,7 @@ public List tokenize() { } } tokens.add(new Token(Token.Type.END, "", line, column)); - return tokens; + return new TokenStream(tokens); } private void readTagContent(List tokens) { diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/loaders/StringTemplateLoader.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/loaders/StringTemplateLoader.java new file mode 100644 index 000000000..cd9d0e563 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/loaders/StringTemplateLoader.java @@ -0,0 +1,27 @@ +package com.condation.cms.templates.loaders; + +import com.condation.cms.templates.TemplateLoader; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * + * @author thmar + */ +public class StringTemplateLoader implements TemplateLoader { + + private ConcurrentMap templates = new ConcurrentHashMap<>(); + + public StringTemplateLoader add (String template, String content) { + templates.put(template, content); + + return this; + } + + @Override + public String load(String template) { + return templates.get(template); + } + + +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java index 11f16cafe..c0222a5d3 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java @@ -13,18 +13,11 @@ public class Parser { private final TemplateConfiguration configuration; -// private final List tokens; -// private int position = 0; - private final TokenStream tokenStream; - - public Parser(List tokens, TemplateConfiguration configuration) { -// this.tokens = tokens; + public Parser(TemplateConfiguration configuration) { this.configuration = configuration; - - tokenStream = new TokenStream(tokens); } - public ASTNode parse() { + public ASTNode parse(final TokenStream tokenStream) { ASTNode root = new ASTNode(); Stack nodeStack = new Stack<>(); nodeStack.push(root); diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TokenStream.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TokenStream.java index 68106eb53..d328cbe2f 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TokenStream.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TokenStream.java @@ -2,6 +2,7 @@ import com.condation.cms.templates.lexer.Token; import java.util.List; +import java.util.function.Consumer; /** * @@ -16,6 +17,10 @@ public class TokenStream { public TokenStream(List tokens) { this.tokens = tokens; } + + public void forEach (Consumer tokenConsumer) { + tokens.forEach(tokenConsumer); + } public Token next() { if (position >= tokens.size()) { diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineTest.java new file mode 100644 index 000000000..dbd90daeb --- /dev/null +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineTest.java @@ -0,0 +1,42 @@ +package com.condation.cms.templates; + +import com.condation.cms.templates.loaders.StringTemplateLoader; +import com.condation.cms.templates.tags.ElseIfTag; +import com.condation.cms.templates.tags.ElseTag; +import com.condation.cms.templates.tags.EndIfTag; +import com.condation.cms.templates.tags.IfTag; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * + * @author thmar + */ +public class TemplateEngineTest { + + TemplateEngine templateEngine; + + @BeforeEach + void setupTemplateEngine() { + TemplateConfiguration config = new TemplateConfiguration(); + config + .registerTag(new IfTag()) + .registerTag(new ElseIfTag()) + .registerTag(new ElseTag()) + .registerTag(new EndIfTag()); + + config.setTemplateLoader(new StringTemplateLoader().add("simple", "Hallo {{ name }}")); + + this.templateEngine = new TemplateEngine(config); + } + + @Test + public void testSomeMethod() { + Template simpleTemplate = templateEngine.getTemplate("simple"); + + Assertions.assertThat(simpleTemplate).isNotNull(); + System.out.println(simpleTemplate.execute()); + } + +} diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/ParserTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/ParserTest.java index bcc687170..bdc00573b 100644 --- a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/ParserTest.java +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/ParserTest.java @@ -31,8 +31,10 @@ public void testSomeMethod() { new Token(Token.Type.TAG_END, "endif", 0,0) ); - Parser parser = new Parser(tokens, config); - ASTNode ast = parser.parse(); + var tokenStream = new TokenStream(tokens); + + Parser parser = new Parser(config); + ASTNode ast = parser.parse(tokenStream); System.out.println(ast); } From 748f64fa9ac04ce2a6cb9a0a81aafe7aaf8a318e Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Mon, 25 Nov 2024 13:09:50 +0100 Subject: [PATCH 08/43] add simple parsing and rendering --- cms-sandbox/templates/pom.xml | 19 +++- .../cms/templates/DefaultTemplate.java | 34 ++++++- .../cms/templates/ParserExample.java | 94 ------------------- .../cms/templates/RenderTemplateExample.java | 33 ------- .../java/com/condation/cms/templates/Tag.java | 22 +++++ .../com/condation/cms/templates/Template.java | 22 +++++ .../cms/templates/TemplateConfiguration.java | 22 +++++ .../cms/templates/TemplateEngine.java | 36 ++++++- .../cms/templates/TemplateLoader.java | 22 +++++ .../condation/cms/templates/lexer/Lexer.java | 28 +++++- .../condation/cms/templates/lexer/State.java | 22 +++++ .../condation/cms/templates/lexer/Token.java | 22 +++++ .../loaders/StringTemplateLoader.java | 22 +++++ .../cms/templates/parser/ASTNode.java | 22 +++++ .../cms/templates/parser/CommentNode.java | 22 +++++ .../cms/templates/parser/Parser.java | 32 ++++++- .../cms/templates/parser/TagNode.java | 22 +++++ .../cms/templates/parser/TextNode.java | 22 +++++ .../cms/templates/parser/TokenStream.java | 22 +++++ .../cms/templates/parser/VariableNode.java | 39 ++++++-- .../templates/{ => renderer}/Renderer.java | 67 +++++++------ .../cms/templates/renderer/ScopeContext.java | 55 +++++++++++ .../cms/templates/renderer/ScopeStack.java | 28 +++++- .../cms/templates/tags/ElseIfTag.java | 22 +++++ .../condation/cms/templates/tags/ElseTag.java | 22 +++++ .../cms/templates/tags/EndIfTag.java | 22 +++++ .../condation/cms/templates/tags/IfTag.java | 22 +++++ .../cms/templates/TemplateEngineTest.java | 69 +++++++++++++- .../cms/templates/parser/ParserTest.java | 42 --------- .../cms/templates/parser/TokenStreamTest.java | 22 +++++ .../templates/renderer/ScopeStackTest.java | 22 +++++ 31 files changed, 746 insertions(+), 226 deletions(-) delete mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java delete mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderTemplateExample.java rename cms-sandbox/templates/src/main/java/com/condation/cms/templates/{ => renderer}/Renderer.java (50%) create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeContext.java delete mode 100644 cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/ParserTest.java diff --git a/cms-sandbox/templates/pom.xml b/cms-sandbox/templates/pom.xml index f3e945b82..20fe1212b 100644 --- a/cms-sandbox/templates/pom.xml +++ b/cms-sandbox/templates/pom.xml @@ -4,9 +4,13 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - com.condation.cms + + com.condation.cms + cms-sandbox + 7.2.0 + + templates - 1.0-SNAPSHOT 21 @@ -15,6 +19,17 @@ + + + org.apache.commons + commons-jexl3 + + + + com.google.guava + guava + + org.junit.jupiter junit-jupiter-engine diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/DefaultTemplate.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/DefaultTemplate.java index 9d71e40d7..b776c42c1 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/DefaultTemplate.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/DefaultTemplate.java @@ -1,8 +1,33 @@ package com.condation.cms.templates; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import com.condation.cms.templates.parser.ASTNode; +import com.condation.cms.templates.renderer.Renderer; +import com.condation.cms.templates.renderer.ScopeStack; import java.util.Map; import lombok.RequiredArgsConstructor; +import org.apache.commons.jexl3.JexlEngine; /** * @@ -13,9 +38,16 @@ public class DefaultTemplate implements Template { private final ASTNode rootNode; + private final JexlEngine engine; + + private final Renderer renderer; + @Override public String execute(Map context) { - return rootNode.toString(); + + ScopeStack scopes = new ScopeStack(context); + + return renderer.render(rootNode, engine, scopes); } } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java deleted file mode 100644 index 3e81a2822..000000000 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/ParserExample.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.condation.cms.templates; - -import java.util.List; - -import com.condation.cms.templates.lexer.Lexer; -import com.condation.cms.templates.lexer.Token; -import com.condation.cms.templates.parser.ASTNode; -import com.condation.cms.templates.parser.Parser; -import com.condation.cms.templates.parser.TagNode; -import com.condation.cms.templates.parser.TextNode; -import com.condation.cms.templates.parser.TokenStream; -import com.condation.cms.templates.parser.VariableNode; -import com.condation.cms.templates.tags.ElseIfTag; -import com.condation.cms.templates.tags.ElseTag; -import com.condation.cms.templates.tags.EndIfTag; -import com.condation.cms.templates.tags.IfTag; - -public class ParserExample { - - static TemplateConfiguration config = new TemplateConfiguration(); - - static { - config.registerTag(new IfTag()); - config.registerTag(new ElseIfTag()); - config.registerTag(new ElseTag()); - config.registerTag(new EndIfTag()); - } - - public static void main(String[] args) { - - System.out.println("TEST 1"); - run_example( - "Hello {{ name }}! {% if condition %}World{% endif %}" - ); - - System.out.println("TEST 2"); - run_example( - """ - Hello {{ name }}! - {% if condition %} - Hello - {% elseif condition2 %} - World - {% else %} - Moon - {% endif %} - Bye {{ name }} - """ - ); - - System.out.println("TEST 3"); - run_example( - """ - {% if condition %} - World - {% elseif condition2 %} - {# - Here is a comment - #} - {% endif %} - """ - ); - } - - public static void run_example(String template) { - Lexer lexer = new Lexer(template); - TokenStream tokenStream = lexer.tokenize(); - - Parser parser = new Parser( config); - ASTNode ast = parser.parse(tokenStream); - - System.out.println("Tokens:"); - tokenStream.forEach(System.out::println); - - System.out.println("AST:"); - printAST(ast, 0); - } - - private static void printAST(ASTNode node, int depth) { - String indent = " ".repeat(depth * 2); - if (node instanceof TextNode) { - System.out.println(indent + "Text: " + ((TextNode) node).text); - } else if (node instanceof VariableNode) { - System.out.println(indent + "Variable: " + ((VariableNode) node).getVariable()); - } else if (node instanceof TagNode) { - TagNode tag = (TagNode) node; - System.out.println(indent + "Tag: " + tag.getName()); - System.out.println(indent + "Condition: " + tag.getCondition()); - tag.getChildren().forEach(child -> printAST(child, depth + 1)); - } else if (!node.getChildren().isEmpty()) { - node.getChildren().forEach(child -> printAST(child, depth + 1)); - } - } -} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderTemplateExample.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderTemplateExample.java deleted file mode 100644 index 90a94062e..000000000 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderTemplateExample.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.condation.cms.templates; - -import java.util.List; -import com.condation.cms.templates.lexer.Lexer; -import com.condation.cms.templates.lexer.Token; -import com.condation.cms.templates.parser.ASTNode; -import com.condation.cms.templates.parser.Parser; - -public class RenderTemplateExample { - public static void main(String[] args) { - - TemplateConfiguration config = new TemplateConfiguration(); - - // Beispiel-Template und Kontext - String template = "Hello, {% if user %}{{ user }}{% endif %}!"; - Lexer lexer = new Lexer(template); - var tokenStream = lexer.tokenize(); - - Parser parser = new Parser(config); - ASTNode ast = parser.parse(tokenStream); - - // Kontext mit Werten für das Template - var context = new Renderer.Context(); - context.setVariable("user", "Thorsten"); - - // Rendering - Renderer renderer = new Renderer(context); - String output = renderer.render(ast); - - System.out.println("Rendered Output:"); - System.out.println(output); - } -} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Tag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Tag.java index 5660b2908..46a8aaf8f 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Tag.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Tag.java @@ -1,5 +1,27 @@ package com.condation.cms.templates; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import java.util.Optional; /** diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Template.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Template.java index a711b7719..0dbf461d3 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Template.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Template.java @@ -1,5 +1,27 @@ package com.condation.cms.templates; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import java.util.Map; /** diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateConfiguration.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateConfiguration.java index b631f24d3..3309e8404 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateConfiguration.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateConfiguration.java @@ -1,5 +1,27 @@ package com.condation.cms.templates; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import java.util.HashMap; import java.util.Map; import java.util.Optional; diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java index 631ff7517..731a3c120 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java @@ -1,17 +1,47 @@ package com.condation.cms.templates; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import com.condation.cms.templates.lexer.Lexer; import com.condation.cms.templates.parser.Parser; +import com.condation.cms.templates.renderer.Renderer; +import org.apache.commons.jexl3.JexlBuilder; +import org.apache.commons.jexl3.JexlEngine; public class TemplateEngine { + private static final JexlEngine jexl = new JexlBuilder().cache(512).strict(true).silent(false).create(); + private final TemplateConfiguration configuration; - private Parser parser; + private final Parser parser; + + private final Renderer renderer; public TemplateEngine(TemplateConfiguration configuration) { this.configuration = configuration; - parser = new Parser(configuration); + parser = new Parser(configuration, jexl); + this.renderer = new Renderer(); } public Template getTemplate (String template) { @@ -22,6 +52,6 @@ public Template getTemplate (String template) { var rootNode = parser.parse(tokenStream); - return new DefaultTemplate(rootNode); + return new DefaultTemplate(rootNode, jexl, renderer); } } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateLoader.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateLoader.java index 47656db16..336ae775a 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateLoader.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateLoader.java @@ -1,5 +1,27 @@ package com.condation.cms.templates; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + /** * * @author thmar diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java index bd69d99da..33798f2dc 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java @@ -1,5 +1,27 @@ package com.condation.cms.templates.lexer; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import com.condation.cms.templates.parser.TokenStream; import java.util.ArrayList; import java.util.List; @@ -48,9 +70,9 @@ public TokenStream tokenize() { tokens.add(new Token(Token.Type.COMMENT_END, "#}", line, column)); position += 2; state.set(State.Type.NONE); - } else if (state.is(State.Type.VARIABLE, State.Type.TAG) && Character.isLetter(c)) { - // Nur wenn wir im Tag sind, identifizieren wir einen IDENTIFIER - tokens.add(new Token(Token.Type.IDENTIFIER, readWhile(Character::isLetter), line, column)); + } else if (state.is(State.Type.VARIABLE, State.Type.TAG) && Character.isLetterOrDigit(c)) { + //tokens.add(new Token(Token.Type.IDENTIFIER, readWhile(Character::isLetter), line, column)); + tokens.add(new Token(Token.Type.IDENTIFIER, readUntil("}}"), line, column)); } else if (state.is(State.Type.COMMENT)) { tokens.add(new Token(Token.Type.COMMENT_VALUE, readUntil("#}"), line, column)); // Alles bis zum nächsten '{' als Text speichern } else if (!state.is(State.Type.VARIABLE, State.Type.TAG)) { diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/State.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/State.java index 5fc8ce3cb..3f91c018d 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/State.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/State.java @@ -1,5 +1,27 @@ package com.condation.cms.templates.lexer; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import java.util.ArrayList; import java.util.Arrays; import java.util.List; diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Token.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Token.java index 6d20d2f80..63fd6706a 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Token.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Token.java @@ -1,5 +1,27 @@ package com.condation.cms.templates.lexer; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + public class Token { public static enum Type { TEXT, diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/loaders/StringTemplateLoader.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/loaders/StringTemplateLoader.java index cd9d0e563..be21ba6f7 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/loaders/StringTemplateLoader.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/loaders/StringTemplateLoader.java @@ -1,5 +1,27 @@ package com.condation.cms.templates.loaders; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import com.condation.cms.templates.TemplateLoader; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/ASTNode.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/ASTNode.java index 82b88d2c3..e8a6a723c 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/ASTNode.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/ASTNode.java @@ -1,5 +1,27 @@ package com.condation.cms.templates.parser; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import java.util.ArrayList; import java.util.List; diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/CommentNode.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/CommentNode.java index 83ebfdc22..657daab0c 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/CommentNode.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/CommentNode.java @@ -1,5 +1,27 @@ package com.condation.cms.templates.parser; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + public class CommentNode extends ASTNode { private String value; diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java index c0222a5d3..ff2ba908c 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java @@ -1,21 +1,42 @@ package com.condation.cms.templates.parser; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import com.condation.cms.templates.Tag; import com.condation.cms.templates.TemplateConfiguration; -import java.util.List; import java.util.Stack; import com.condation.cms.templates.lexer.Token; import static com.condation.cms.templates.lexer.Token.Type.VARIABLE_START; -import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.apache.commons.jexl3.JexlEngine; +@RequiredArgsConstructor public class Parser { private final TemplateConfiguration configuration; - public Parser(TemplateConfiguration configuration) { - this.configuration = configuration; - } + private final JexlEngine engine; public ASTNode parse(final TokenStream tokenStream) { ASTNode root = new ASTNode(); @@ -107,6 +128,7 @@ public ASTNode parse(final TokenStream tokenStream) { tagNode1.setName(token.value); // Tag-Name setzen } else if (currentNode instanceof VariableNode variableNode1) { variableNode1.setVariable(token.value); // Variable setzen + variableNode1.setExpression(engine.createExpression(token.value)); } break; } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java index c1fa06c8c..7e3031abb 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java @@ -1,5 +1,27 @@ package com.condation.cms.templates.parser; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import java.util.ArrayList; import java.util.List; diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TextNode.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TextNode.java index ec9b271fe..748b073ee 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TextNode.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TextNode.java @@ -1,5 +1,27 @@ package com.condation.cms.templates.parser; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + public class TextNode extends ASTNode { public final String text; diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TokenStream.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TokenStream.java index d328cbe2f..3d42edb5e 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TokenStream.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TokenStream.java @@ -1,5 +1,27 @@ package com.condation.cms.templates.parser; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import com.condation.cms.templates.lexer.Token; import java.util.List; import java.util.function.Consumer; diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/VariableNode.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/VariableNode.java index 00d2e017f..7c6e769d8 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/VariableNode.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/VariableNode.java @@ -1,15 +1,38 @@ package com.condation.cms.templates.parser; -public class VariableNode extends ASTNode { - private String variable; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.jexl3.JexlExpression; - public void setVariable(String variable) { - this.variable = variable; - } +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ - public String getVariable() { - return variable; - } +public class VariableNode extends ASTNode { + @Getter + @Setter + private String variable; + @Getter + @Setter + private JexlExpression expression; @Override public String toString() { diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Renderer.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java similarity index 50% rename from cms-sandbox/templates/src/main/java/com/condation/cms/templates/Renderer.java rename to cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java index 95511deda..85cca8920 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Renderer.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java @@ -1,46 +1,55 @@ -package com.condation.cms.templates; +package com.condation.cms.templates.renderer; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ -import java.util.HashMap; -import java.util.Map; import com.condation.cms.templates.parser.ASTNode; import com.condation.cms.templates.parser.TagNode; import com.condation.cms.templates.parser.TextNode; import com.condation.cms.templates.parser.VariableNode; +import lombok.RequiredArgsConstructor; +import org.apache.commons.jexl3.JexlEngine; +@RequiredArgsConstructor public class Renderer { - public static class Context { - private final Map variables = new HashMap<>(); - - public void setVariable(String name, String value) { - variables.put(name, value); - } - - public String getVariable(String name) { - return variables.get(name); - } - } - - private final Context context; - - public Renderer(Context context) { - this.context = context; - } - - public String render(ASTNode node) { + public String render(ASTNode node, final JexlEngine engine, final ScopeStack scopes) { StringBuilder output = new StringBuilder(); - renderNode(node, output); + renderNode(node, engine, scopes, output); return output.toString(); } - private void renderNode(ASTNode node, StringBuilder output) { + private void renderNode(ASTNode node, final JexlEngine engine, final ScopeStack scopes, StringBuilder output) { + + var scopeContext = new ScopeContext(scopes); + if (node instanceof TextNode) { output.append(((TextNode) node).text); - } else if (node instanceof VariableNode) { - String variableValue = context.getVariable(((VariableNode) node).getVariable()); + } else if (node instanceof VariableNode vnode) { + Object variableValue = vnode.getExpression().evaluate(scopeContext); output.append(variableValue != null ? variableValue : ""); } else if (node instanceof TagNode) { + /* TagNode tagNode = (TagNode) node; // Beispiel: Wir unterstützen das "if"-Tag if ("if".equals(tagNode.getName())) { @@ -56,13 +65,13 @@ private void renderNode(ASTNode node, StringBuilder output) { } else { // Anderes Tag-Verhalten kann hier hinzugefügt werden for (ASTNode child : tagNode.getChildren()) { - renderNode(child, output); + renderNode(child, engine, scopes, output); } } + */ } else { - // Rekursiv alle Kindknoten verarbeiten for (ASTNode child : node.getChildren()) { - renderNode(child, output); + renderNode(child, engine, scopes, output); } } } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeContext.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeContext.java new file mode 100644 index 000000000..e7ed638c1 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeContext.java @@ -0,0 +1,55 @@ +package com.condation.cms.templates.renderer; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import lombok.RequiredArgsConstructor; +import org.apache.commons.jexl3.JexlContext; + +/** + * + * @author t.marx + */ +@RequiredArgsConstructor +public class ScopeContext implements JexlContext { + + private final ScopeStack scopeStack; + + @Override + public Object get(String name) { + if (has(name)) { + return scopeStack.getVariable(name).get(); + } + return null; + } + + @Override + public boolean has(String name) { + return scopeStack.getVariable(name).isPresent(); + } + + @Override + public void set(String name, Object value) { + scopeStack.setVariable(name, value); + } + +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeStack.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeStack.java index dcec2c68c..99140c64c 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeStack.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeStack.java @@ -1,5 +1,27 @@ package com.condation.cms.templates.renderer; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import java.util.*; public class ScopeStack { @@ -9,6 +31,10 @@ public ScopeStack() { // Füge den globalen Scope hinzu, der immer verfügbar ist pushScope(); } + + public ScopeStack(Map root) { + scopes.push(root); + } // Fügt einen neuen Scope hinzu public void pushScope() { @@ -45,4 +71,4 @@ public Optional getVariable(String name) { } return Optional.empty(); // Variable nicht gefunden } -} \ No newline at end of file +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseIfTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseIfTag.java index 7d1840c05..7f3799e47 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseIfTag.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseIfTag.java @@ -1,5 +1,27 @@ package com.condation.cms.templates.tags; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import com.condation.cms.templates.Tag; import java.util.Optional; diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseTag.java index 3ef6b7cd6..505f9ca35 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseTag.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseTag.java @@ -1,5 +1,27 @@ package com.condation.cms.templates.tags; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import com.condation.cms.templates.Tag; /** diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndIfTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndIfTag.java index a1acce19e..97835bbe3 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndIfTag.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndIfTag.java @@ -1,5 +1,27 @@ package com.condation.cms.templates.tags; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import com.condation.cms.templates.Tag; /** diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IfTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IfTag.java index 568a96c60..b371e8c0a 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IfTag.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IfTag.java @@ -1,5 +1,27 @@ package com.condation.cms.templates.tags; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import com.condation.cms.templates.Tag; import java.util.Optional; diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineTest.java index dbd90daeb..f70568284 100644 --- a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineTest.java +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineTest.java @@ -1,12 +1,38 @@ package com.condation.cms.templates; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import com.condation.cms.templates.loaders.StringTemplateLoader; import com.condation.cms.templates.tags.ElseIfTag; import com.condation.cms.templates.tags.ElseTag; import com.condation.cms.templates.tags.EndIfTag; import com.condation.cms.templates.tags.IfTag; +import com.google.common.base.Stopwatch; +import java.util.Map; +import java.util.concurrent.TimeUnit; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; /** @@ -26,17 +52,50 @@ void setupTemplateEngine() { .registerTag(new ElseTag()) .registerTag(new EndIfTag()); - config.setTemplateLoader(new StringTemplateLoader().add("simple", "Hallo {{ name }}")); + config.setTemplateLoader(new StringTemplateLoader() + .add("simple", "Hallo {{ name }}") + .add("map", "Hallo {{ person.name }}") + ); this.templateEngine = new TemplateEngine(config); } - - @Test - public void testSomeMethod() { + + @RepeatedTest(5) + public void test_simple() { + + Stopwatch stopwatch = Stopwatch.createStarted(); + Template simpleTemplate = templateEngine.getTemplate("simple"); + System.out.println("creating simple template took: " + stopwatch.elapsed(TimeUnit.MILLISECONDS) + "ms"); + Assertions.assertThat(simpleTemplate).isNotNull(); - System.out.println(simpleTemplate.execute()); + + Map context = Map.of("name", "CondationCMS"); + + stopwatch.reset(); + stopwatch.start(); + System.out.println(simpleTemplate.execute(context)); + System.out.println("executing simple template took: " + stopwatch.elapsed(TimeUnit.MILLISECONDS) + "ms"); } + @RepeatedTest(5) + public void test_map() { + + Stopwatch stopwatch = Stopwatch.createStarted(); + + Template simpleTemplate = templateEngine.getTemplate("map"); + + System.out.println("creating map template took: " + stopwatch.elapsed(TimeUnit.MILLISECONDS) + "ms"); + + Assertions.assertThat(simpleTemplate).isNotNull(); + + Map context = Map.of("person", Map.of("name", "CondationCMS")); + + stopwatch.reset(); + stopwatch.start(); + System.out.println(simpleTemplate.execute(context)); + System.out.println("executing map template took: " + stopwatch.elapsed(TimeUnit.MILLISECONDS) + "ms"); + } + } diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/ParserTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/ParserTest.java deleted file mode 100644 index bdc00573b..000000000 --- a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/ParserTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.condation.cms.templates.parser; - -import com.condation.cms.templates.TemplateConfiguration; -import com.condation.cms.templates.lexer.Token; -import com.condation.cms.templates.tags.ElseIfTag; -import com.condation.cms.templates.tags.ElseTag; -import com.condation.cms.templates.tags.IfTag; -import java.util.List; -import org.junit.jupiter.api.Test; - -/** - * - * @author t.marx - */ -public class ParserTest { - - @Test - public void testSomeMethod() { - TemplateConfiguration config = new TemplateConfiguration(); - config.registerTag(new IfTag()); - config.registerTag(new ElseIfTag()); - config.registerTag(new ElseTag()); - - List tokens = List.of( - new Token(Token.Type.TAG_START, "if", 0,0), - new Token(Token.Type.EXPRESSION, "condition", 0,0), - new Token(Token.Type.TAG_START, "elseif", 0,0), - new Token(Token.Type.EXPRESSION, "condition2", 0,0), - new Token(Token.Type.TAG_START, "else", 0,0), - new Token(Token.Type.TEXT, "fallback", 0,0), - new Token(Token.Type.TAG_END, "endif", 0,0) - ); - - var tokenStream = new TokenStream(tokens); - - Parser parser = new Parser(config); - ASTNode ast = parser.parse(tokenStream); - - System.out.println(ast); - } - -} diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/TokenStreamTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/TokenStreamTest.java index f76a835c3..679ab3add 100644 --- a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/TokenStreamTest.java +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/TokenStreamTest.java @@ -1,5 +1,27 @@ package com.condation.cms.templates.parser; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import com.condation.cms.templates.lexer.Token; import java.util.List; import org.assertj.core.api.Assertions; diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/renderer/ScopeStackTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/renderer/ScopeStackTest.java index 94845fe50..0c2c3d5dd 100644 --- a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/renderer/ScopeStackTest.java +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/renderer/ScopeStackTest.java @@ -1,5 +1,27 @@ package com.condation.cms.templates.renderer; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.Optional; From 65dade7f85261d1ed3e0a8ac3c710ffed66b6ed1 Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Mon, 25 Nov 2024 16:45:51 +0100 Subject: [PATCH 09/43] update if-elseif-else --- .../cms/templates/RenderFunction.java | 36 ++++++++ .../java/com/condation/cms/templates/Tag.java | 12 +-- .../cms/templates/TemplateEngine.java | 2 +- .../templates/exceptions/ParserException.java | 19 ++++ .../condation/cms/templates/lexer/Lexer.java | 1 - .../{parser => lexer}/TokenStream.java | 3 +- .../cms/templates/parser/Parser.java | 12 ++- .../cms/templates/parser/TagNode.java | 30 +++---- .../cms/templates/renderer/Renderer.java | 32 +++++-- .../cms/templates/tags/ElseIfTag.java | 1 - .../cms/templates/tags/EndIfTag.java | 2 +- .../condation/cms/templates/tags/IfTag.java | 71 +++++++++++++-- .../cms/templates/TemplateEngineIFTest.java | 90 +++++++++++++++++++ .../{parser => lexer}/TokenStreamTest.java | 3 +- 14 files changed, 267 insertions(+), 47 deletions(-) create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderFunction.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/exceptions/ParserException.java rename cms-sandbox/templates/src/main/java/com/condation/cms/templates/{parser => lexer}/TokenStream.java (95%) create mode 100644 cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineIFTest.java rename cms-sandbox/templates/src/test/java/com/condation/cms/templates/{parser => lexer}/TokenStreamTest.java (95%) diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderFunction.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderFunction.java new file mode 100644 index 000000000..d1c076633 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/RenderFunction.java @@ -0,0 +1,36 @@ +package com.condation.cms.templates; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.templates.parser.ASTNode; +import com.condation.cms.templates.renderer.Renderer; + +/** + * + * @author t.marx + */ +@FunctionalInterface +public interface RenderFunction { + + void render(ASTNode node, Renderer.Context context, StringBuilder output); +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Tag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Tag.java index 46a8aaf8f..3b65d00e1 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Tag.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Tag.java @@ -22,6 +22,8 @@ * #L% */ +import com.condation.cms.templates.parser.TagNode; +import com.condation.cms.templates.renderer.Renderer; import java.util.Optional; /** @@ -36,11 +38,11 @@ default Optional getCloseTagName () { return Optional.empty(); } - default boolean supportsNestedTag(String tagName) { - return false; - } - - default boolean isEndTag () { + default boolean isClosingTag () { return false; } + + default void render (TagNode node, Renderer.Context context, StringBuilder sb) { + // default render does nothing + }; } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java index 731a3c120..34ef7bf91 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java @@ -41,7 +41,7 @@ public class TemplateEngine { public TemplateEngine(TemplateConfiguration configuration) { this.configuration = configuration; parser = new Parser(configuration, jexl); - this.renderer = new Renderer(); + this.renderer = new Renderer(configuration); } public Template getTemplate (String template) { diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/exceptions/ParserException.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/exceptions/ParserException.java new file mode 100644 index 000000000..f91f1b788 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/exceptions/ParserException.java @@ -0,0 +1,19 @@ +package com.condation.cms.templates.exceptions; + +/** + * + * @author t.marx + */ +public class ParserException extends RuntimeException { + + private final int line; + private final int column; + + public ParserException(String message, int line, int column) { + super(message); + this.line = line; + this.column = column; + } + + +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java index 33798f2dc..7d8f9a137 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java @@ -22,7 +22,6 @@ * #L% */ -import com.condation.cms.templates.parser.TokenStream; import java.util.ArrayList; import java.util.List; diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TokenStream.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/TokenStream.java similarity index 95% rename from cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TokenStream.java rename to cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/TokenStream.java index 3d42edb5e..072023aa2 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TokenStream.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/TokenStream.java @@ -1,4 +1,4 @@ -package com.condation.cms.templates.parser; +package com.condation.cms.templates.lexer; /*- * #%L @@ -22,7 +22,6 @@ * #L% */ -import com.condation.cms.templates.lexer.Token; import java.util.List; import java.util.function.Consumer; diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java index ff2ba908c..8441c5a52 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java @@ -22,6 +22,7 @@ * #L% */ +import com.condation.cms.templates.lexer.TokenStream; import com.condation.cms.templates.Tag; import com.condation.cms.templates.TemplateConfiguration; import java.util.Stack; @@ -81,7 +82,7 @@ public ASTNode parse(final TokenStream tokenStream) { if (configuration.hasTag(tempNode.getName())) { Tag tag = configuration.getTag(tempNode.getName()).get(); - if (tag.isEndTag()) { + if (tag.isClosingTag()) { nodeStack.pop(); var temp = (TagNode) nodeStack.peek(); @@ -133,12 +134,15 @@ public ASTNode parse(final TokenStream tokenStream) { break; } case EXPRESSION: { - TagNode ifNode = (TagNode) nodeStack.peek(); - ifNode.setCondition(token.value); + ASTNode currentNode = nodeStack.peek(); + if (currentNode instanceof TagNode tagNode) { + tagNode.setCondition(token.value); + tagNode.setExpression(engine.createExpression(token.value)); + } + break; } case END: { - System.out.println("end token?"); break; } default: diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java index 7e3031abb..254bda844 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java @@ -1,5 +1,9 @@ package com.condation.cms.templates.parser; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.jexl3.JexlExpression; + /*- * #%L * templates @@ -22,12 +26,16 @@ * #L% */ -import java.util.ArrayList; -import java.util.List; - public class TagNode extends ASTNode { + @Getter + @Setter private String name; + @Getter + @Setter private String condition; + @Getter + @Setter + private JexlExpression expression; public TagNode () { @@ -37,22 +45,6 @@ public TagNode(String name) { this.name = name; } - public void setName(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public void setCondition (String condition) { - this.condition = condition; - } - - public String getCondition () { - return condition; - } - @Override public String toString() { return "TagNode('" + name + ", " + condition + "')"; diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java index 85cca8920..2ae2b5e8f 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java @@ -23,6 +23,8 @@ */ +import com.condation.cms.templates.RenderFunction; +import com.condation.cms.templates.TemplateConfiguration; import com.condation.cms.templates.parser.ASTNode; import com.condation.cms.templates.parser.TagNode; import com.condation.cms.templates.parser.TextNode; @@ -33,22 +35,38 @@ @RequiredArgsConstructor public class Renderer { + private final TemplateConfiguration configuration; + + public static record Context ( + JexlEngine engine, + ScopeStack scopes, + RenderFunction renderer) { + + public ScopeContext createEngineContext () { + return new ScopeContext(scopes); + } + } + public String render(ASTNode node, final JexlEngine engine, final ScopeStack scopes) { StringBuilder output = new StringBuilder(); - renderNode(node, engine, scopes, output); + renderNode(node, new Context(engine, scopes, this::renderNode), output); return output.toString(); } - private void renderNode(ASTNode node, final JexlEngine engine, final ScopeStack scopes, StringBuilder output) { + private void renderNode(ASTNode node, Context context, StringBuilder output) { - var scopeContext = new ScopeContext(scopes); + var scopeContext = context.createEngineContext(); - if (node instanceof TextNode) { - output.append(((TextNode) node).text); + if (node instanceof TextNode textNode) { + output.append(textNode.text); } else if (node instanceof VariableNode vnode) { Object variableValue = vnode.getExpression().evaluate(scopeContext); output.append(variableValue != null ? variableValue : ""); - } else if (node instanceof TagNode) { + } else if (node instanceof TagNode tagNode) { + var tag = configuration.getTag(tagNode.getName()); + if (tag.isPresent()) { + tag.get().render(tagNode, context, output); + } /* TagNode tagNode = (TagNode) node; // Beispiel: Wir unterstützen das "if"-Tag @@ -71,7 +89,7 @@ private void renderNode(ASTNode node, final JexlEngine engine, final ScopeStack */ } else { for (ASTNode child : node.getChildren()) { - renderNode(child, engine, scopes, output); + renderNode(child, context, output); } } } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseIfTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseIfTag.java index 7f3799e47..75ca489f8 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseIfTag.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseIfTag.java @@ -23,7 +23,6 @@ */ import com.condation.cms.templates.Tag; -import java.util.Optional; /** * diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndIfTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndIfTag.java index 97835bbe3..5db013669 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndIfTag.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndIfTag.java @@ -36,7 +36,7 @@ public String getTagName() { } @Override - public boolean isEndTag() { + public boolean isClosingTag() { return true; } } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IfTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IfTag.java index b371e8c0a..d8329d608 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IfTag.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IfTag.java @@ -21,9 +21,14 @@ * . * #L% */ - import com.condation.cms.templates.Tag; +import com.condation.cms.templates.parser.ASTNode; +import com.condation.cms.templates.parser.TagNode; +import com.condation.cms.templates.renderer.Renderer; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; +import org.apache.commons.jexl3.JexlExpression; /** * @@ -40,9 +45,65 @@ public String getTagName() { public Optional getCloseTagName() { return Optional.of("endif"); } - + @Override - public boolean supportsNestedTag(String tagName) { - return tagName.equals("elseif") || tagName.equals("else"); - } + public void render(TagNode node, Renderer.Context context, StringBuilder sb) { + List conditions = buildConditions(node); + + var scopeContext = context.createEngineContext(); + for (Condition condition : conditions) { + try { + context.scopes().pushScope(); + if (condition.expression() != null) { + Object value = condition.expression().evaluate(scopeContext); + if (value instanceof Boolean boolValue && boolValue == true) { + for (var child : condition.currentChildren) { + context.renderer().render(child, context, sb); + } + break; + } + } else { + for (var child : condition.currentChildren) { + context.renderer().render(child, context, sb); + } + break; + } + } finally { + context.scopes().popScope(); + } + } + } + + private List buildConditions(TagNode node) { + + List conditions = new ArrayList<>(); + + TagNode currentTag = node; + List currentChildren = new ArrayList<>(); + for (var child : node.getChildren()) { + if (child instanceof TagNode tagNode) { + if ("elseif".equals(tagNode.getName())) { + conditions.add(new Condition(currentTag.getName(), currentTag.getExpression(), currentChildren)); + currentTag = tagNode; + currentChildren = new ArrayList<>(); + } else if ("else".equals(tagNode.getName())) { + conditions.add(new Condition(currentTag.getName(), currentTag.getExpression(), currentChildren)); + currentTag = tagNode; + currentChildren = new ArrayList<>(); + } else if ("endif".equals(tagNode.getName())) { + conditions.add(new Condition(currentTag.getName(), currentTag.getExpression(), currentChildren)); + } else { + currentChildren.add(child); + } + } else { + currentChildren.add(child); + } + } + + return conditions; + } + + private static record Condition(String name, JexlExpression expression, List currentChildren) { + + } } diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineIFTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineIFTest.java new file mode 100644 index 000000000..a17fa0996 --- /dev/null +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineIFTest.java @@ -0,0 +1,90 @@ +package com.condation.cms.templates; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.templates.loaders.StringTemplateLoader; +import com.condation.cms.templates.tags.ElseIfTag; +import com.condation.cms.templates.tags.ElseTag; +import com.condation.cms.templates.tags.EndIfTag; +import com.condation.cms.templates.tags.IfTag; +import java.util.Map; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * + * @author thmar + */ +public class TemplateEngineIFTest { + + TemplateEngine templateEngine; + + @BeforeEach + void setupTemplateEngine() { + TemplateConfiguration config = new TemplateConfiguration(); + config + .registerTag(new IfTag()) + .registerTag(new ElseIfTag()) + .registerTag(new ElseTag()) + .registerTag(new EndIfTag()); + + config.setTemplateLoader(new StringTemplateLoader() + .add("simple", """ + {% if name == 'CondationCMS' %} + Best CMS ever! + {% elseif name == 'AnotherCMS' %} + Just a CMS! + {% else %} + Not even a CMS! + {% endif %} + """) + ); + + this.templateEngine = new TemplateEngine(config); + } + + @Test + public void test_if() { + Template simpleTemplate = templateEngine.getTemplate("simple"); + Assertions.assertThat(simpleTemplate).isNotNull(); + Map context = Map.of("name", "CondationCMS"); + Assertions.assertThat(simpleTemplate.execute(context)).isEqualToIgnoringWhitespace("Best CMS ever!"); + } + + @Test + public void test_elseif() { + Template simpleTemplate = templateEngine.getTemplate("simple"); + Assertions.assertThat(simpleTemplate).isNotNull(); + Map context = Map.of("name", "AnotherCMS"); + Assertions.assertThat(simpleTemplate.execute(context)).isEqualToIgnoringWhitespace("Just a CMS!"); + } + + @Test + public void test_else() { + Template simpleTemplate = templateEngine.getTemplate("simple"); + Assertions.assertThat(simpleTemplate).isNotNull(); + Map context = Map.of("name", "some thing else"); + Assertions.assertThat(simpleTemplate.execute(context)).isEqualToIgnoringWhitespace("Not even a CMS!"); + } +} diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/TokenStreamTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/lexer/TokenStreamTest.java similarity index 95% rename from cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/TokenStreamTest.java rename to cms-sandbox/templates/src/test/java/com/condation/cms/templates/lexer/TokenStreamTest.java index 679ab3add..4dd9b800c 100644 --- a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/TokenStreamTest.java +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/lexer/TokenStreamTest.java @@ -1,4 +1,4 @@ -package com.condation.cms.templates.parser; +package com.condation.cms.templates.lexer; /*- * #%L @@ -22,6 +22,7 @@ * #L% */ +import com.condation.cms.templates.lexer.TokenStream; import com.condation.cms.templates.lexer.Token; import java.util.List; import org.assertj.core.api.Assertions; From a644c80beb21ea18531fb65ee2f678c165ca2542 Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Tue, 26 Nov 2024 13:09:21 +0100 Subject: [PATCH 10/43] for tag added --- cms-sandbox/templates/pom.xml | 5 + .../java/com/condation/cms/templates/Tag.java | 4 + .../cms/templates/TemplateEngine.java | 6 +- .../templates/exceptions/ParserException.java | 22 ++++ .../templates/exceptions/TagException.java | 41 +++++++ .../cms/templates/parser/ASTNode.java | 9 ++ .../cms/templates/parser/CommentNode.java | 3 +- .../cms/templates/parser/Parser.java | 16 ++- .../cms/templates/parser/TagNode.java | 10 +- .../cms/templates/parser/TextNode.java | 3 +- .../cms/templates/parser/VariableNode.java | 4 + .../cms/templates/renderer/Renderer.java | 8 +- .../cms/templates/renderer/ScopeStack.java | 6 +- .../cms/templates/tags/ElseIfTag.java | 5 + .../cms/templates/tags/EndForTag.java | 42 +++++++ .../condation/cms/templates/tags/ForTag.java | 106 ++++++++++++++++++ .../condation/cms/templates/tags/IfTag.java | 9 +- .../cms/templates/TemplateEngineFORTest.java | 85 ++++++++++++++ 18 files changed, 364 insertions(+), 20 deletions(-) create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/exceptions/TagException.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndForTag.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ForTag.java create mode 100644 cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineFORTest.java diff --git a/cms-sandbox/templates/pom.xml b/cms-sandbox/templates/pom.xml index 20fe1212b..bd4539507 100644 --- a/cms-sandbox/templates/pom.xml +++ b/cms-sandbox/templates/pom.xml @@ -25,6 +25,11 @@ commons-jexl3 + + org.apache.commons + commons-text + + com.google.guava guava diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Tag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Tag.java index 3b65d00e1..8f57b24f3 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Tag.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/Tag.java @@ -34,6 +34,10 @@ public interface Tag { String getTagName(); + default boolean parseExpressions () { + return false; + } + default Optional getCloseTagName () { return Optional.empty(); } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java index 34ef7bf91..ae972ea32 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java @@ -30,7 +30,11 @@ public class TemplateEngine { - private static final JexlEngine jexl = new JexlBuilder().cache(512).strict(true).silent(false).create(); + private static final JexlEngine jexl = new JexlBuilder() + .cache(512) + .strict(true) + .silent(false) + .create(); private final TemplateConfiguration configuration; diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/exceptions/ParserException.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/exceptions/ParserException.java index f91f1b788..15604cbc2 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/exceptions/ParserException.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/exceptions/ParserException.java @@ -1,5 +1,27 @@ package com.condation.cms.templates.exceptions; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + /** * * @author t.marx diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/exceptions/TagException.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/exceptions/TagException.java new file mode 100644 index 000000000..483171502 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/exceptions/TagException.java @@ -0,0 +1,41 @@ +package com.condation.cms.templates.exceptions; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +/** + * + * @author t.marx + */ +public class TagException extends RuntimeException { + + private final int line; + private final int column; + + public TagException(String message, int line, int column) { + super(message); + this.line = line; + this.column = column; + } + + +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/ASTNode.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/ASTNode.java index e8a6a723c..6e77725ec 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/ASTNode.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/ASTNode.java @@ -24,9 +24,18 @@ import java.util.ArrayList; import java.util.List; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +@RequiredArgsConstructor public class ASTNode { private final List children = new ArrayList<>(); + + @Getter + private final int line; + @Getter + private final int column; + public void addChild(ASTNode child) { children.add(child); diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/CommentNode.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/CommentNode.java index 657daab0c..bdba50889 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/CommentNode.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/CommentNode.java @@ -26,7 +26,8 @@ public class CommentNode extends ASTNode { private String value; - public CommentNode() { + public CommentNode(int line, int column) { + super(line, column); } public String getValue() { diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java index 8441c5a52..209a0a688 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java @@ -40,7 +40,7 @@ public class Parser { private final JexlEngine engine; public ASTNode parse(final TokenStream tokenStream) { - ASTNode root = new ASTNode(); + ASTNode root = new ASTNode(0, 0); Stack nodeStack = new Stack<>(); nodeStack.push(root); @@ -48,7 +48,7 @@ public ASTNode parse(final TokenStream tokenStream) { while ((token = tokenStream.peek()) != null) { switch (token.type) { case TEXT: { - nodeStack.peek().addChild(new TextNode(token.value)); + nodeStack.peek().addChild(new TextNode(token.value, token.line, token.column)); break; } case COMMENT_VALUE: { @@ -59,19 +59,19 @@ public ASTNode parse(final TokenStream tokenStream) { break; } case VARIABLE_START: { - VariableNode variableNode = new VariableNode(); + VariableNode variableNode = new VariableNode(token.line, token.column); nodeStack.peek().addChild(variableNode); nodeStack.push(variableNode); // In den neuen Kontext für Variablen wechseln break; } case COMMENT_START: { - CommentNode commentNode = new CommentNode(); + CommentNode commentNode = new CommentNode(token.line, token.column); nodeStack.peek().addChild(commentNode); nodeStack.push(commentNode); // In den neuen Kontext für Variablen wechseln break; } case TAG_START: { - TagNode tagNode = new TagNode(); + TagNode tagNode = new TagNode(token.line, token.column); nodeStack.peek().addChild(tagNode); nodeStack.push(tagNode); // In den neuen Kontext für Tags wechseln @@ -137,7 +137,11 @@ public ASTNode parse(final TokenStream tokenStream) { ASTNode currentNode = nodeStack.peek(); if (currentNode instanceof TagNode tagNode) { tagNode.setCondition(token.value); - tagNode.setExpression(engine.createExpression(token.value)); + + Tag tag = configuration.getTag(tagNode.getName()).get(); + if (tag.parseExpressions()) { + tagNode.setExpression(engine.createExpression(token.value)); + } } break; diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java index 254bda844..455cb1201 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TagNode.java @@ -37,13 +37,9 @@ public class TagNode extends ASTNode { @Setter private JexlExpression expression; - public TagNode () { - - } - - public TagNode(String name) { - this.name = name; - } + public TagNode(int line, int column) { + super(line, column); + } @Override public String toString() { diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TextNode.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TextNode.java index 748b073ee..efdd6b137 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TextNode.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/TextNode.java @@ -25,7 +25,8 @@ public class TextNode extends ASTNode { public final String text; - public TextNode(String text) { + public TextNode(String text, int line, int column) { + super(line, column); this.text = text; } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/VariableNode.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/VariableNode.java index 7c6e769d8..1b33364c4 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/VariableNode.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/VariableNode.java @@ -34,6 +34,10 @@ public class VariableNode extends ASTNode { @Setter private JexlExpression expression; + public VariableNode(int line, int column) { + super(line, column); + } + @Override public String toString() { return "VariableNode('" + variable + "')"; diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java index 2ae2b5e8f..a5ae48128 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java @@ -31,6 +31,7 @@ import com.condation.cms.templates.parser.VariableNode; import lombok.RequiredArgsConstructor; import org.apache.commons.jexl3.JexlEngine; +import org.apache.commons.text.StringEscapeUtils; @RequiredArgsConstructor public class Renderer { @@ -61,7 +62,12 @@ private void renderNode(ASTNode node, Context context, StringBuilder output) { output.append(textNode.text); } else if (node instanceof VariableNode vnode) { Object variableValue = vnode.getExpression().evaluate(scopeContext); - output.append(variableValue != null ? variableValue : ""); + if (variableValue != null && variableValue instanceof String stringValue) { + output.append(StringEscapeUtils.ESCAPE_HTML4.translate(stringValue)); + } else { + output.append(variableValue != null ? variableValue : ""); + } + } else if (node instanceof TagNode tagNode) { var tag = configuration.getTag(tagNode.getName()); if (tag.isPresent()) { diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeStack.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeStack.java index 99140c64c..dd5c3207c 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeStack.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeStack.java @@ -38,7 +38,11 @@ public ScopeStack(Map root) { // Fügt einen neuen Scope hinzu public void pushScope() { - scopes.push(new HashMap<>()); + pushScope(Map.of()); + } + + public void pushScope(Map values) { + scopes.push(new HashMap<>(values)); } // Entfernt den obersten Scope und alle Variablen darin diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseIfTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseIfTag.java index 75ca489f8..dd3711c29 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseIfTag.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ElseIfTag.java @@ -34,4 +34,9 @@ public class ElseIfTag implements Tag { public String getTagName() { return "elseif"; } + + @Override + public boolean parseExpressions() { + return true; + } } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndForTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndForTag.java new file mode 100644 index 000000000..6858fd1c4 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndForTag.java @@ -0,0 +1,42 @@ +package com.condation.cms.templates.tags; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.templates.Tag; + +/** + * + * @author t.marx + */ +public class EndForTag implements Tag { + + @Override + public String getTagName() { + return "endfor"; + } + + @Override + public boolean isClosingTag() { + return true; + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ForTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ForTag.java new file mode 100644 index 000000000..7974a2b38 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ForTag.java @@ -0,0 +1,106 @@ +package com.condation.cms.templates.tags; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +import com.condation.cms.templates.Tag; +import com.condation.cms.templates.exceptions.TagException; +import com.condation.cms.templates.parser.ASTNode; +import com.condation.cms.templates.parser.TagNode; +import com.condation.cms.templates.renderer.Renderer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.apache.commons.jexl3.JexlExpression; + +/** + * + * @author t.marx + */ +public class ForTag implements Tag { + + @Override + public String getTagName() { + return "for"; + } + + @Override + public Optional getCloseTagName() { + return Optional.of("endfor"); + } + + @Override + public void render(TagNode node, Renderer.Context context, StringBuilder sb) { + var forCondition = parseForLoop(node); + + var collection = context.scopes().getVariable(forCondition.collection); + if (collection.isEmpty() || !(collection.get() instanceof Collection)) { + throw new TagException("variable '%s' not found".formatted(forCondition.collection), node.getLine(), node.getColumn()); + } + + var index = 0; + for (var item : (Collection) collection.get()) { + var loop = new Loop(index++); + context.scopes().pushScope( + Map.of( + "loop", loop, + forCondition.variable, item + ) + ); + try { + for (var child : node.getChildren()) { + context.renderer().render(child, context, sb); + } + } finally { + context.scopes().popScope(); + } + } + } + + public ForDefinition parseForLoop(TagNode node) { + var loopDefinition = node.getCondition(); + // Überprüfen, ob der String das richtige Format hat + if (!loopDefinition.contains(" in ")) { + throw new IllegalArgumentException("Ungültige Schleifendefinition: " + loopDefinition); + } + + // Extrahiere die Variable und die Collection + String[] parts = loopDefinition.split(" in "); + if (parts.length != 2) { + throw new IllegalArgumentException("Ungültige Schleifendefinition: " + loopDefinition); + } + + String variable = parts[0].trim(); + String collectionName = parts[1].trim(); + + return new ForDefinition(collectionName, variable); + } + + private static record ForDefinition(String collection, String variable) { + + } + + private static record Loop(int index) { + + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IfTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IfTag.java index d8329d608..d6b0f3b48 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IfTag.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IfTag.java @@ -46,15 +46,20 @@ public Optional getCloseTagName() { return Optional.of("endif"); } + @Override + public boolean parseExpressions() { + return true; + } + @Override public void render(TagNode node, Renderer.Context context, StringBuilder sb) { List conditions = buildConditions(node); var scopeContext = context.createEngineContext(); for (Condition condition : conditions) { + context.scopes().pushScope(); try { - context.scopes().pushScope(); - if (condition.expression() != null) { + if (!condition.name.equals("else")) { Object value = condition.expression().evaluate(scopeContext); if (value instanceof Boolean boolValue && boolValue == true) { for (var child : condition.currentChildren) { diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineFORTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineFORTest.java new file mode 100644 index 000000000..b6bfff23e --- /dev/null +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineFORTest.java @@ -0,0 +1,85 @@ +package com.condation.cms.templates; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.templates.loaders.StringTemplateLoader; +import com.condation.cms.templates.tags.ElseIfTag; +import com.condation.cms.templates.tags.ElseTag; +import com.condation.cms.templates.tags.EndForTag; +import com.condation.cms.templates.tags.EndIfTag; +import com.condation.cms.templates.tags.ForTag; +import com.condation.cms.templates.tags.IfTag; +import java.util.List; +import java.util.Map; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * + * @author thmar + */ +public class TemplateEngineFORTest { + + TemplateEngine templateEngine; + + @BeforeEach + void setupTemplateEngine() { + TemplateConfiguration config = new TemplateConfiguration(); + config + .registerTag(new IfTag()) + .registerTag(new ElseIfTag()) + .registerTag(new ElseTag()) + .registerTag(new EndIfTag()) + .registerTag(new ForTag()) + .registerTag(new EndForTag()) + ; + + config.setTemplateLoader(new StringTemplateLoader() + .add("simple", """ + {% for name in names %} +
  • {{ name }}
  • + {% endfor %} + """) + ); + + this.templateEngine = new TemplateEngine(config); + } + + @Test + public void test_for() { + + var expected = """ +
  • one
  • +
  • two
  • +
  • three
  • + """; + + Template simpleTemplate = templateEngine.getTemplate("simple"); + Assertions.assertThat(simpleTemplate).isNotNull(); + Map context = Map.of("names", List.of("one", "two", "three")); + Assertions.assertThat(simpleTemplate.execute(context)).isEqualToIgnoringWhitespace(expected); + } + + +} From 923c71442269b04193bf4de624a7554f93c05bda Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Tue, 26 Nov 2024 16:01:53 +0100 Subject: [PATCH 11/43] implement set and include --- cms-sandbox/templates/pom.xml | 7 +- .../cms/templates/DefaultTemplate.java | 4 + .../cms/templates/TemplateEngine.java | 2 +- .../cms/templates/renderer/Renderer.java | 64 ++++++++------- .../cms/templates/renderer/ScopeStack.java | 4 +- .../cms/templates/tags/IncludeTag.java | 70 ++++++++++++++++ .../condation/cms/templates/tags/SetTag.java | 56 +++++++++++++ .../templates/TemplateEngineIncludeTest.java | 80 +++++++++++++++++++ .../cms/templates/TemplateEngineSetTest.java | 71 ++++++++++++++++ .../cms/templates/TemplateEngineTest.java | 11 +++ 10 files changed, 332 insertions(+), 37 deletions(-) create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IncludeTag.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/SetTag.java create mode 100644 cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineIncludeTest.java create mode 100644 cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineSetTest.java diff --git a/cms-sandbox/templates/pom.xml b/cms-sandbox/templates/pom.xml index bd4539507..54575f89d 100644 --- a/cms-sandbox/templates/pom.xml +++ b/cms-sandbox/templates/pom.xml @@ -39,21 +39,22 @@ org.junit.jupiter junit-jupiter-engine test - 5.11.3
    org.assertj assertj-core - 3.26.3 test org.projectlombok lombok - 1.18.36 provided + + org.slf4j + slf4j-api + \ No newline at end of file diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/DefaultTemplate.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/DefaultTemplate.java index b776c42c1..c08ef1b47 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/DefaultTemplate.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/DefaultTemplate.java @@ -47,6 +47,10 @@ public String execute(Map context) { ScopeStack scopes = new ScopeStack(context); + return evaluate(scopes); + } + + public String evaluate (ScopeStack scopes) { return renderer.render(rootNode, engine, scopes); } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java index ae972ea32..be1e110f9 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java @@ -45,7 +45,7 @@ public class TemplateEngine { public TemplateEngine(TemplateConfiguration configuration) { this.configuration = configuration; parser = new Parser(configuration, jexl); - this.renderer = new Renderer(configuration); + this.renderer = new Renderer(configuration, this); } public Template getTemplate (String template) { diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java index a5ae48128..388b6ca98 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java @@ -21,10 +21,10 @@ * . * #L% */ - - import com.condation.cms.templates.RenderFunction; import com.condation.cms.templates.TemplateConfiguration; +import com.condation.cms.templates.TemplateEngine; +import com.condation.cms.templates.TemplateLoader; import com.condation.cms.templates.parser.ASTNode; import com.condation.cms.templates.parser.TagNode; import com.condation.cms.templates.parser.TextNode; @@ -37,38 +37,40 @@ public class Renderer { private final TemplateConfiguration configuration; - - public static record Context ( - JexlEngine engine, - ScopeStack scopes, - RenderFunction renderer) { - - public ScopeContext createEngineContext () { + private final TemplateEngine templateEngine; + + public static record Context( + JexlEngine engine, + ScopeStack scopes, + RenderFunction renderer, + TemplateEngine templateEngine) { + + public ScopeContext createEngineContext() { return new ScopeContext(scopes); } } - - public String render(ASTNode node, final JexlEngine engine, final ScopeStack scopes) { - StringBuilder output = new StringBuilder(); - renderNode(node, new Context(engine, scopes, this::renderNode), output); - return output.toString(); - } - private void renderNode(ASTNode node, Context context, StringBuilder output) { - + public String render(ASTNode node, final JexlEngine engine, final ScopeStack scopes) { + StringBuilder output = new StringBuilder(); + renderNode(node, new Context(engine, scopes, this::renderNode, templateEngine), output); + return output.toString(); + } + + private void renderNode(ASTNode node, Context context, StringBuilder output) { + var scopeContext = context.createEngineContext(); - - if (node instanceof TextNode textNode) { - output.append(textNode.text); - } else if (node instanceof VariableNode vnode) { - Object variableValue = vnode.getExpression().evaluate(scopeContext); + + if (node instanceof TextNode textNode) { + output.append(textNode.text); + } else if (node instanceof VariableNode vnode) { + Object variableValue = vnode.getExpression().evaluate(scopeContext); if (variableValue != null && variableValue instanceof String stringValue) { output.append(StringEscapeUtils.ESCAPE_HTML4.translate(stringValue)); } else { output.append(variableValue != null ? variableValue : ""); } - - } else if (node instanceof TagNode tagNode) { + + } else if (node instanceof TagNode tagNode) { var tag = configuration.getTag(tagNode.getName()); if (tag.isPresent()) { tag.get().render(tagNode, context, output); @@ -92,11 +94,11 @@ private void renderNode(ASTNode node, Context context, StringBuilder output) { renderNode(child, engine, scopes, output); } } - */ - } else { - for (ASTNode child : node.getChildren()) { - renderNode(child, context, output); - } - } - } + */ + } else { + for (ASTNode child : node.getChildren()) { + renderNode(child, context, output); + } + } + } } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeStack.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeStack.java index dd5c3207c..ae4a9edf6 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeStack.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeStack.java @@ -33,12 +33,12 @@ public ScopeStack() { } public ScopeStack(Map root) { - scopes.push(root); + pushScope(root); } // Fügt einen neuen Scope hinzu public void pushScope() { - pushScope(Map.of()); + pushScope(new HashMap<>()); } public void pushScope(Map values) { diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IncludeTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IncludeTag.java new file mode 100644 index 000000000..2b903f7a4 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IncludeTag.java @@ -0,0 +1,70 @@ +package com.condation.cms.templates.tags; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.templates.DefaultTemplate; +import com.condation.cms.templates.Tag; +import com.condation.cms.templates.exceptions.TagException; +import com.condation.cms.templates.parser.TagNode; +import com.condation.cms.templates.renderer.Renderer; +import lombok.extern.slf4j.Slf4j; + +/** + * + * @author t.marx + */ +@Slf4j +public class IncludeTag implements Tag { + + @Override + public String getTagName() { + return "include"; + } + + @Override + public void render(TagNode node, Renderer.Context context, StringBuilder sb) { + try { + var templateString = getTemplate(node); + + var template = (DefaultTemplate)context.templateEngine().getTemplate(templateString); + if (template != null) { + String result = template.evaluate(context.scopes()); + sb.append(result); + } + } catch (Exception e) { + throw new TagException("error including template", node.getLine(), node.getColumn()); + } + } + + private String getTemplate (TagNode node) { + var template = node.getCondition().trim(); + if (template.startsWith("\"") || template.startsWith("'")) { + template = template.substring(1); + } + if (template.endsWith("\"") || template.endsWith("'")) { + template = template.substring(0, template.length() - 1); + } + + return template; + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/SetTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/SetTag.java new file mode 100644 index 000000000..baa6d8687 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/SetTag.java @@ -0,0 +1,56 @@ +package com.condation.cms.templates.tags; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +import com.condation.cms.templates.Tag; +import com.condation.cms.templates.parser.ASTNode; +import com.condation.cms.templates.parser.TagNode; +import com.condation.cms.templates.renderer.Renderer; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import org.apache.commons.jexl3.JexlExpression; + +/** + * + * @author t.marx + */ +public class SetTag implements Tag { + + @Override + public String getTagName() { + return "set"; + } + + @Override + public boolean parseExpressions() { + return true; + } + + @Override + public void render(TagNode node, Renderer.Context context, StringBuilder sb) { + var scopeContext = context.createEngineContext(); + + node.getExpression().evaluate(scopeContext); + + } +} diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineIncludeTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineIncludeTest.java new file mode 100644 index 000000000..07b05210e --- /dev/null +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineIncludeTest.java @@ -0,0 +1,80 @@ +package com.condation.cms.templates; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +import com.condation.cms.templates.loaders.StringTemplateLoader; +import com.condation.cms.templates.tags.ElseIfTag; +import com.condation.cms.templates.tags.ElseTag; +import com.condation.cms.templates.tags.EndIfTag; +import com.condation.cms.templates.tags.IfTag; +import com.condation.cms.templates.tags.IncludeTag; +import com.condation.cms.templates.tags.SetTag; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * + * @author thmar + */ +public class TemplateEngineIncludeTest { + + TemplateEngine templateEngine; + + @BeforeEach + void setupTemplateEngine() { + TemplateConfiguration config = new TemplateConfiguration(); + config + .registerTag(new IncludeTag()); + + config.setTemplateLoader(new StringTemplateLoader() + .add("simple1", """ + {% include "temp1" %} + """) + .add("simple2", """ + {% include "nested/temp2" %} + """) + .add("temp1", """ + This is from template1 + """) + .add("nested/temp2", """ + This is from template2 + """) + ); + + this.templateEngine = new TemplateEngine(config); + } + + @Test + public void test_template1() { + Template simpleTemplate = templateEngine.getTemplate("simple1"); + Assertions.assertThat(simpleTemplate).isNotNull(); + Assertions.assertThat(simpleTemplate.execute()).isEqualToIgnoringWhitespace("This is from template1"); + } + + @Test + public void test_template2() { + Template simpleTemplate = templateEngine.getTemplate("simple2"); + Assertions.assertThat(simpleTemplate).isNotNull(); + Assertions.assertThat(simpleTemplate.execute()).isEqualToIgnoringWhitespace("This is from template2"); + } +} diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineSetTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineSetTest.java new file mode 100644 index 000000000..26c541cc8 --- /dev/null +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineSetTest.java @@ -0,0 +1,71 @@ +package com.condation.cms.templates; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.templates.loaders.StringTemplateLoader; +import com.condation.cms.templates.tags.ElseIfTag; +import com.condation.cms.templates.tags.ElseTag; +import com.condation.cms.templates.tags.EndIfTag; +import com.condation.cms.templates.tags.IfTag; +import com.condation.cms.templates.tags.SetTag; +import java.util.Map; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * + * @author thmar + */ +public class TemplateEngineSetTest { + + TemplateEngine templateEngine; + + @BeforeEach + void setupTemplateEngine() { + TemplateConfiguration config = new TemplateConfiguration(); + config + .registerTag(new IfTag()) + .registerTag(new ElseIfTag()) + .registerTag(new ElseTag()) + .registerTag(new EndIfTag()) + .registerTag(new SetTag()) + ; + + config.setTemplateLoader(new StringTemplateLoader() + .add("simple", """ + {% set name = 'CondationCMS' %} + {{ name }} + """) + ); + + this.templateEngine = new TemplateEngine(config); + } + + @Test + public void test_set() { + Template simpleTemplate = templateEngine.getTemplate("simple"); + Assertions.assertThat(simpleTemplate).isNotNull(); + Assertions.assertThat(simpleTemplate.execute()).isEqualToIgnoringWhitespace("CondationCMS"); + } +} diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineTest.java index f70568284..b3d8b1127 100644 --- a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineTest.java +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineTest.java @@ -55,6 +55,7 @@ void setupTemplateEngine() { config.setTemplateLoader(new StringTemplateLoader() .add("simple", "Hallo {{ name }}") .add("map", "Hallo {{ person.name }}") + .add("text", "{{ content }}") ); this.templateEngine = new TemplateEngine(config); @@ -98,4 +99,14 @@ public void test_map() { System.out.println("executing map template took: " + stopwatch.elapsed(TimeUnit.MILLISECONDS) + "ms"); } + @Test + public void test_escape() { + + Template template = templateEngine.getTemplate("text"); + + Map context = Map.of("content", "

    heading

    "); + + + Assertions.assertThat(template.execute(context)).isEqualToIgnoringWhitespace("<h1>heading</h1>"); + } } From 2e002a53cb2c984a0c8fdb507e88e1cf7506ca99 Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Wed, 27 Nov 2024 16:48:10 +0100 Subject: [PATCH 12/43] support for macros --- .../condation/cms/templates/lexer/Lexer.java | 2 +- .../cms/templates/parser/Filter.java | 31 ++++ .../cms/templates/parser/Parser.java | 36 +++-- .../cms/templates/parser/VariableNode.java | 9 ++ .../cms/templates/renderer/Renderer.java | 34 +---- .../cms/templates/renderer/ScopeStack.java | 10 ++ .../renderer/VariableNodeRenderer.java | 66 ++++++++ .../cms/templates/tags/EndMacroTag.java | 42 ++++++ .../cms/templates/tags/MacroTag.java | 141 ++++++++++++++++++ .../cms/templates/utils/MacroUtils.java | 62 ++++++++ .../cms/templates/utils/TemplateUtils.java | 67 +++++++++ .../com/condation/cms/templates/JexlTest.java | 107 +++++++++++++ .../templates/TemplateEngineMacroTest.java | 85 +++++++++++ .../cms/templates/TemplateEngineTest.java | 12 ++ .../cms/templates/TemplateUtilsTest.java | 70 +++++++++ .../cms/templates/parser/ParserTest.java | 73 +++++++++ 16 files changed, 810 insertions(+), 37 deletions(-) create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Filter.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/VariableNodeRenderer.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndMacroTag.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/MacroTag.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/utils/MacroUtils.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/utils/TemplateUtils.java create mode 100644 cms-sandbox/templates/src/test/java/com/condation/cms/templates/JexlTest.java create mode 100644 cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineMacroTest.java create mode 100644 cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateUtilsTest.java create mode 100644 cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/ParserTest.java diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java index 7d8f9a137..f6d67b3cd 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java @@ -90,7 +90,7 @@ private void readTagContent(List tokens) { String keyword = readWhile(Character::isLetter); tokens.add(new Token(Token.Type.IDENTIFIER, keyword, line, column)); - String condition = readUntil("%"); + String condition = readUntil("%}"); tokens.add(new Token(Token.Type.EXPRESSION, condition, line, column)); } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Filter.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Filter.java new file mode 100644 index 000000000..e35e151e9 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Filter.java @@ -0,0 +1,31 @@ +package com.condation.cms.templates.parser; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +/** + * + * @author t.marx + */ +public record Filter(String name) { + +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java index 209a0a688..fbd1b44e6 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java @@ -25,6 +25,8 @@ import com.condation.cms.templates.lexer.TokenStream; import com.condation.cms.templates.Tag; import com.condation.cms.templates.TemplateConfiguration; +import com.condation.cms.templates.exceptions.ParserException; +import com.condation.cms.templates.utils.TemplateUtils; import java.util.Stack; import com.condation.cms.templates.lexer.Token; @@ -93,17 +95,17 @@ public ASTNode parse(final TokenStream tokenStream) { && ptag.getCloseTagName().get().equals(tag.getTagName())) { nodeStack.pop(); } else { - throw new RuntimeException("invalid closing tag"); + throw new ParserException("invalid closing tag", token.line, token.column); } } else if (tag.getCloseTagName().isEmpty()) { nodeStack.pop(); } } else { - throw new RuntimeException("Undefined tag: " + tempNode.getName()); + throw new ParserException("Undefined tag: " + tempNode.getName(), token.line, token.column); } } else { - throw new RuntimeException("Unexpected token: TAG_END"); + throw new ParserException("Unexpected token: TAG_END", token.line, token.column); } break; } @@ -111,7 +113,7 @@ public ASTNode parse(final TokenStream tokenStream) { if (!nodeStack.isEmpty()) { nodeStack.pop(); // Aus dem aktuellen Tag-/Variable-Block heraustreten } else { - throw new RuntimeException("Unexpected token: VARIABLE_END"); + throw new ParserException("Unexpected token: VARIABLE_END", token.line, token.column); } break; } @@ -119,7 +121,7 @@ public ASTNode parse(final TokenStream tokenStream) { if (!nodeStack.isEmpty()) { nodeStack.pop(); // Aus dem aktuellen Tag-/Variable-Block heraustreten } else { - throw new RuntimeException("Unexpected token: COMMENT_END"); + throw new ParserException("Unexpected token: COMMENT_END", token.line, token.column); } break; } @@ -128,8 +130,22 @@ public ASTNode parse(final TokenStream tokenStream) { if (currentNode instanceof TagNode tagNode1) { tagNode1.setName(token.value); // Tag-Name setzen } else if (currentNode instanceof VariableNode variableNode1) { - variableNode1.setVariable(token.value); // Variable setzen - variableNode1.setExpression(engine.createExpression(token.value)); + var identifier = token.value; + if (TemplateUtils.hasFilters(identifier)) { + var variable = TemplateUtils.extractVariableName(identifier); + + variableNode1.setVariable(variable); // Variable setzen + variableNode1.setExpression(engine.createExpression(variable)); + + variableNode1.setFilters(TemplateUtils.extractFilters(identifier) + .stream() + .map(filter -> new Filter(filter)) + .toList() + ); + } else { + variableNode1.setVariable(token.value); // Variable setzen + variableNode1.setExpression(engine.createExpression(token.value)); + } } break; } @@ -142,6 +158,8 @@ public ASTNode parse(final TokenStream tokenStream) { if (tag.parseExpressions()) { tagNode.setExpression(engine.createExpression(token.value)); } + } else if (currentNode instanceof TagNode vNode) { + } break; @@ -150,13 +168,13 @@ public ASTNode parse(final TokenStream tokenStream) { break; } default: - throw new RuntimeException("Unexpected token: " + token.type); + throw new ParserException("Unexpected token: " + token.type, token.line, token.column); } tokenStream.next(); } if (nodeStack.size() > 1) { - throw new RuntimeException("Unclosed tag or block detected"); + throw new ParserException("Unclosed tag or block detected", 0, 0); } return root; diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/VariableNode.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/VariableNode.java index 1b33364c4..1e4a51bba 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/VariableNode.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/VariableNode.java @@ -1,5 +1,7 @@ package com.condation.cms.templates.parser; +import java.util.ArrayList; +import java.util.List; import lombok.Getter; import lombok.Setter; import org.apache.commons.jexl3.JexlExpression; @@ -33,11 +35,18 @@ public class VariableNode extends ASTNode { @Getter @Setter private JexlExpression expression; + @Setter + @Getter + private List filters = new ArrayList<>(); public VariableNode(int line, int column) { super(line, column); } + public boolean hasFilters () { + return filters != null && !filters.isEmpty(); + } + @Override public String toString() { return "VariableNode('" + variable + "')"; diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java index 388b6ca98..e3b8e087b 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java @@ -38,6 +38,8 @@ public class Renderer { private final TemplateConfiguration configuration; private final TemplateEngine templateEngine; + + private final VariableNodeRenderer variableNodeRenderer = new VariableNodeRenderer(); public static record Context( JexlEngine engine, @@ -63,42 +65,20 @@ private void renderNode(ASTNode node, Context context, StringBuilder output) { if (node instanceof TextNode textNode) { output.append(textNode.text); } else if (node instanceof VariableNode vnode) { - Object variableValue = vnode.getExpression().evaluate(scopeContext); - if (variableValue != null && variableValue instanceof String stringValue) { - output.append(StringEscapeUtils.ESCAPE_HTML4.translate(stringValue)); - } else { - output.append(variableValue != null ? variableValue : ""); - } - + renderVariable(vnode, scopeContext, output); } else if (node instanceof TagNode tagNode) { var tag = configuration.getTag(tagNode.getName()); if (tag.isPresent()) { tag.get().render(tagNode, context, output); } - /* - TagNode tagNode = (TagNode) node; - // Beispiel: Wir unterstützen das "if"-Tag - if ("if".equals(tagNode.getName())) { - ASTNode conditionNode = tagNode.getChildren().get(0); - if (conditionNode instanceof VariableNode) { - String variableValue = context.getVariable(((VariableNode) conditionNode).getVariable()); - if (variableValue != null && !variableValue.isEmpty()) { - for (int i = 1; i < tagNode.getChildren().size(); i++) { - renderNode(tagNode.getChildren().get(i), output); - } - } - } - } else { - // Anderes Tag-Verhalten kann hier hinzugefügt werden - for (ASTNode child : tagNode.getChildren()) { - renderNode(child, engine, scopes, output); - } - } - */ } else { for (ASTNode child : node.getChildren()) { renderNode(child, context, output); } } } + + private void renderVariable (VariableNode node, ScopeContext context, StringBuilder output) { + variableNodeRenderer.render(node, context, output); + } } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeStack.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeStack.java index ae4a9edf6..708937cca 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeStack.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/ScopeStack.java @@ -27,6 +27,13 @@ public class ScopeStack { private final Deque> scopes = new ArrayDeque<>(); + public ScopeStack parent; + + public ScopeStack ( ScopeStack parent ) { + pushScope(); + this.parent = parent; + } + public ScopeStack() { // Füge den globalen Scope hinzu, der immer verfügbar ist pushScope(); @@ -73,6 +80,9 @@ public Optional getVariable(String name) { return Optional.of(scope.get(name)); } } + if (parent != null) { + return parent.getVariable(name); + } return Optional.empty(); // Variable nicht gefunden } } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/VariableNodeRenderer.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/VariableNodeRenderer.java new file mode 100644 index 000000000..ee4859651 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/VariableNodeRenderer.java @@ -0,0 +1,66 @@ +package com.condation.cms.templates.renderer; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.templates.parser.Filter; +import com.condation.cms.templates.parser.VariableNode; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.commons.text.StringEscapeUtils; + +/** + * + * @author t.marx + */ +@RequiredArgsConstructor +class VariableNodeRenderer { + + protected void render(VariableNode node, ScopeContext context, StringBuilder output) { + Object variableValue = node.getExpression().evaluate(context); + if (variableValue != null && variableValue instanceof String stringValue) { + output.append(evaluateStringFilters(stringValue, node.getFilters())); + } else { + output.append(variableValue != null ? variableValue : ""); + } + } + + protected String evaluateStringFilters(String value, List filters) { + + var returnValue = StringEscapeUtils.ESCAPE_HTML4.translate(value); + + if (filters != null && !filters.isEmpty()) { + for (var filter : filters) { + returnValue = switch (filter.name()) { + case "raw" -> + StringEscapeUtils.UNESCAPE_HTML4.translate(value); + case "trim" -> + returnValue.trim(); + default -> + returnValue; + }; + } + } + + return returnValue; + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndMacroTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndMacroTag.java new file mode 100644 index 000000000..63cf61297 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/EndMacroTag.java @@ -0,0 +1,42 @@ +package com.condation.cms.templates.tags; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.templates.Tag; + +/** + * + * @author t.marx + */ +public class EndMacroTag implements Tag { + + @Override + public String getTagName() { + return "endmacro"; + } + + @Override + public boolean isClosingTag() { + return true; + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/MacroTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/MacroTag.java new file mode 100644 index 000000000..95813b4f5 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/MacroTag.java @@ -0,0 +1,141 @@ +package com.condation.cms.templates.tags; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.templates.Tag; +import com.condation.cms.templates.parser.ASTNode; +import com.condation.cms.templates.parser.TagNode; +import com.condation.cms.templates.renderer.Renderer; +import com.condation.cms.templates.renderer.ScopeStack; +import com.condation.cms.templates.utils.MacroUtils; +import java.awt.image.renderable.RenderContext; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.jexl3.JexlEngine; +import org.apache.commons.jexl3.JexlException; +import org.apache.commons.jexl3.introspection.JexlMethod; + +/** + * + * @author t.marx + */ +public class MacroTag implements Tag { + + @Override + public String getTagName() { + return "macro"; + } + + @Override + public Optional getCloseTagName() { + return Optional.of("endmacro"); + } + + @Override + public void render(TagNode node, Renderer.Context context, StringBuilder sb) { + Optional macroOpt = MacroUtils.parseMacro(node.getCondition()); + if (macroOpt.isEmpty()) { + return; + } + + var macro = macroOpt.get(); + macro.setChildren(node.getChildren()); + + context.scopes().setVariable(macro.name, new MacroFunction(macro, context)); + } + + @RequiredArgsConstructor + public static class Macro { + + private final String name; + private final List parameters; + + @Getter + @Setter + private List children; + } + + @Slf4j + @RequiredArgsConstructor + public static class MacroFunction implements JexlMethod { + + private final Macro macro; + private final Renderer.Context context; + + @Override + public Class getReturnType() { + return String.class; + } + + @Override + public Object invoke(Object obj, Object... params) throws Exception { + + ScopeStack scope = new ScopeStack(); + var engineContext = context.createEngineContext(); + for (int i = 0; i < macro.parameters.size(); i++) { + Object value = ""; + if ( i < params.length ) { + value = params[i]; + } + + scope.setVariable(macro.parameters.get(i), value); + } + StringBuilder sb = new StringBuilder(); + + var newContext = new Renderer.Context(context.engine(), scope, context.renderer(), context.templateEngine()); + for (var child : macro.children) { + context.renderer().render(child, newContext, sb); + } + + return sb.toString(); + } + + @Override + public boolean isCacheable() { + return false; + } + + @Override + public boolean tryFailed(Object rval) { + return JexlEngine.TRY_FAILED.equals(rval); + } + + @Override + public Object tryInvoke(String name, Object obj, Object... params) throws JexlException.TryFailed { + if (macro.name.equals(name)) { + try { + return invoke(obj, params); + } catch (Exception ex) { + log.error("error calling macro", ex); + } + } + return JexlEngine.TRY_FAILED; + } + + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/utils/MacroUtils.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/utils/MacroUtils.java new file mode 100644 index 000000000..b5da1e2af --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/utils/MacroUtils.java @@ -0,0 +1,62 @@ +package com.condation.cms.templates.utils; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.templates.tags.MacroTag; +import com.google.common.base.Strings; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * + * @author t.marx + */ +public class MacroUtils { + public static final Pattern MACRO_PATTERN = Pattern.compile("([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\((.*)\\)"); + + public static Optional parseMacro(String expression) { + if (Strings.isNullOrEmpty(expression)) { + return Optional.empty(); + } + expression = expression.trim(); + Matcher matcher = MACRO_PATTERN.matcher(expression); + + if (matcher.matches()) { + String methodName = matcher.group(1); + String params = matcher.group(2).trim(); + + List paramList = new ArrayList<>(); + if (!params.isEmpty()) { + paramList = Arrays.asList(params.split("\\s*,\\s*")); + } + + return Optional.of(new MacroTag.Macro(methodName, paramList)); + } + + return Optional.empty(); + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/utils/TemplateUtils.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/utils/TemplateUtils.java new file mode 100644 index 000000000..e103f69e1 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/utils/TemplateUtils.java @@ -0,0 +1,67 @@ +package com.condation.cms.templates.utils; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * + * @author t.marx + */ +public class TemplateUtils { + + public static final String CHECK_FILTER_REGEX = "\\s*\\w+\\s*\\|"; + public static final Pattern CHECK_FILTER_PATTERN = Pattern.compile(CHECK_FILTER_REGEX); + + public static final String GET_FILTER_REGEX = "\\s*(.*?)\\s*"; + public static final Pattern GET_FILTER_PATTERN = Pattern.compile(GET_FILTER_REGEX); + + public static boolean hasFilters(String variable) { + Matcher matcher = CHECK_FILTER_PATTERN.matcher(variable); + return matcher.find(); + } + + public static List extractFilters(String variable) { + List filters = new ArrayList<>(); + + // Split basierend auf "|" + String[] parts = variable.split("\\|"); + + // Entferne den Variablennamen (erstes Element) und trimme Filter + for (int i = 1; i < parts.length; i++) { + filters.add(parts[i].trim()); + } + + return filters; + } + + public static String extractVariableName(String input) { + // Split basierend auf "|" + String[] parts = input.split("\\|"); + + // Rückgabe des ersten Elements (Variablenname), getrimmt + return parts[0].trim(); + } +} diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/JexlTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/JexlTest.java new file mode 100644 index 000000000..bdac88b0e --- /dev/null +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/JexlTest.java @@ -0,0 +1,107 @@ +package com.condation.cms.templates; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import java.util.function.Function; +import org.apache.commons.jexl3.JexlBuilder; +import org.apache.commons.jexl3.JexlContext; +import org.apache.commons.jexl3.JexlEngine; +import org.apache.commons.jexl3.JexlException; +import org.apache.commons.jexl3.JexlInfo; +import org.apache.commons.jexl3.MapContext; +import org.apache.commons.jexl3.introspection.JexlMethod; +import org.apache.commons.jexl3.parser.JexlNode; +import org.junit.jupiter.api.Test; + +/** + * + * @author t.marx + */ +public class JexlTest { + + private static final JexlEngine jexl = new JexlBuilder() + .cache(512) + .strict(true) + .silent(false) + .create(); + + @Test + void test_fn () { + + JexlContext context = new MapContext(); + context.set("fn", (Function) (String t) -> "hello " + t); + + var exp = jexl.createExpression("fn('CondationCMS')"); + + exp.evaluate(context); + } + + @Test + void test_fn_wrapper () { + + JexlContext context = new MapContext(); + + + context.set("fn", new HelloFunction()); + + var exp = jexl.createExpression("fn('CondationCMS')"); + + exp.evaluate(context); + } + + public static class HelloFunction implements JexlMethod { + + + + @Override + public Class getReturnType() { + return String.class; + } + + @Override + public Object invoke(Object obj, Object... params) throws Exception { + return "Hello " + params[0]; + } + + @Override + public boolean isCacheable() { + return false; + } + + @Override + public boolean tryFailed(Object rval) { + return rval == JexlEngine.TRY_FAILED; + } + + @Override + public Object tryInvoke(String name, Object obj, Object... params) throws JexlException.TryFailed { + try { + return invoke(obj, params); + } catch (Exception e) { + + } + return this; + } + + } +} diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineMacroTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineMacroTest.java new file mode 100644 index 000000000..e5da7e374 --- /dev/null +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineMacroTest.java @@ -0,0 +1,85 @@ +package com.condation.cms.templates; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.templates.loaders.StringTemplateLoader; +import com.condation.cms.templates.tags.ElseIfTag; +import com.condation.cms.templates.tags.ElseTag; +import com.condation.cms.templates.tags.EndIfTag; +import com.condation.cms.templates.tags.EndMacroTag; +import com.condation.cms.templates.tags.IfTag; +import com.condation.cms.templates.tags.MacroTag; +import com.condation.cms.templates.tags.SetTag; +import java.util.Map; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * + * @author thmar + */ +public class TemplateEngineMacroTest { + + TemplateEngine templateEngine; + + @BeforeEach + void setupTemplateEngine() { + TemplateConfiguration config = new TemplateConfiguration(); + config + .registerTag(new MacroTag()) + .registerTag(new EndMacroTag()) + ; + + config.setTemplateLoader(new StringTemplateLoader() + .add("simple", """ + {% macro hello(name) %} + Hello {{ name }}! + {% endmacro %} + {{ hello('CondationCMS') }} + """) + .add("param", """ + {% macro hello(name) %} + Hello {{ name }}! + {% endmacro %} + {{ hello(name) }} + """) + ); + + this.templateEngine = new TemplateEngine(config); + } + + @Test + public void test_simple() { + Template simpleTemplate = templateEngine.getTemplate("simple"); + Assertions.assertThat(simpleTemplate).isNotNull(); + Assertions.assertThat(simpleTemplate.execute()).isEqualToIgnoringWhitespace("Hello CondationCMS!"); + } + + @Test + public void test_param() { + Template simpleTemplate = templateEngine.getTemplate("param"); + Assertions.assertThat(simpleTemplate).isNotNull(); + Assertions.assertThat(simpleTemplate.execute(Map.of("name", "Developer"))).isEqualToIgnoringWhitespace("Hello Developer!"); + } +} diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineTest.java index b3d8b1127..fe711ff82 100644 --- a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineTest.java +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineTest.java @@ -56,6 +56,7 @@ void setupTemplateEngine() { .add("simple", "Hallo {{ name }}") .add("map", "Hallo {{ person.name }}") .add("text", "{{ content }}") + .add("text_raw", "{{ content | raw }}") ); this.templateEngine = new TemplateEngine(config); @@ -109,4 +110,15 @@ public void test_escape() { Assertions.assertThat(template.execute(context)).isEqualToIgnoringWhitespace("<h1>heading</h1>"); } + + @Test + public void test_raw() { + + Template template = templateEngine.getTemplate("text_raw"); + + Map context = Map.of("content", "

    heading

    "); + + + Assertions.assertThat(template.execute(context)).isEqualToIgnoringWhitespace("

    heading

    "); + } } diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateUtilsTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateUtilsTest.java new file mode 100644 index 000000000..80ba6fdd0 --- /dev/null +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateUtilsTest.java @@ -0,0 +1,70 @@ +package com.condation.cms.templates; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.templates.utils.TemplateUtils; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * + * @author t.marx + */ +public class TemplateUtilsTest { + + @Test + public void no_filter() { + Assertions.assertThat(TemplateUtils.hasFilters("var")).isFalse(); + + Assertions.assertThat(TemplateUtils.extractFilters("var")).isEmpty(); + + Assertions.assertThat(TemplateUtils.extractVariableName("var")).isEqualTo("var"); + } + + @Test + public void one_filter() { + Assertions.assertThat(TemplateUtils.hasFilters("var | trim")).isTrue(); + + Assertions.assertThat(TemplateUtils.extractFilters("var | trim")).containsExactly("trim"); + + Assertions.assertThat(TemplateUtils.extractVariableName("var")).isEqualTo("var"); + } + + @Test + public void more_filter() { + Assertions.assertThat(TemplateUtils.hasFilters("var | trim | upper | raw")).isTrue(); + + Assertions.assertThat(TemplateUtils.extractFilters("var | trim | upper | raw")).containsExactly("trim", "upper", "raw"); + + Assertions.assertThat(TemplateUtils.extractVariableName("var")).isEqualTo("var"); + } + + @Test + public void filter_with_params() { + Assertions.assertThat(TemplateUtils.hasFilters("var | trim(100) | raw(html)")).isTrue(); + + Assertions.assertThat(TemplateUtils.extractFilters("var | trim(100) | raw(html)")).containsExactly("trim(100)", "raw(html)"); + + Assertions.assertThat(TemplateUtils.extractVariableName("var")).isEqualTo("var"); + } +} diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/ParserTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/ParserTest.java new file mode 100644 index 000000000..7be49044f --- /dev/null +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/ParserTest.java @@ -0,0 +1,73 @@ +package com.condation.cms.templates.parser; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.templates.TemplateConfiguration; +import com.condation.cms.templates.lexer.Lexer; +import org.apache.commons.jexl3.JexlBuilder; +import org.apache.commons.jexl3.JexlEngine; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeAll; + +/** + * + * @author t.marx + */ +public class ParserTest { + + private static final JexlEngine jexl = new JexlBuilder() + .cache(512) + .strict(true) + .silent(false) + .create(); + + static Parser parser; + static Lexer lexer; + + @BeforeAll + public static void setup () { + var config = new TemplateConfiguration(); + parser = new Parser(config, jexl); + } + + @Test + public void test_filters() { + var input = "{{ content | raw | trim }}"; + lexer = new Lexer(input); + + var tokenStream = lexer.tokenize(); + + var ast = parser.parse(tokenStream); + + Assertions.assertThat(ast.getChildren().getFirst()).isInstanceOf(VariableNode.class); + + var variableNode = (VariableNode)ast.getChildren().getFirst(); + + Assertions.assertThat(variableNode.hasFilters()).isEqualTo(true); + Assertions.assertThat(variableNode.getFilters()).containsExactly( + new Filter("raw"), new Filter("trim") + ); + } + +} From 8c462b3873a3d12967dccaa1db9dc4d162289e90 Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Wed, 27 Nov 2024 17:03:33 +0100 Subject: [PATCH 13/43] add parent scope vor macros --- .../main/java/com/condation/cms/templates/tags/MacroTag.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/MacroTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/MacroTag.java index 95813b4f5..3d5a3c37a 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/MacroTag.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/MacroTag.java @@ -95,8 +95,8 @@ public Class getReturnType() { @Override public Object invoke(Object obj, Object... params) throws Exception { - ScopeStack scope = new ScopeStack(); - var engineContext = context.createEngineContext(); + ScopeStack scope = new ScopeStack(context.scopes()); + for (int i = 0; i < macro.parameters.size(); i++) { Object value = ""; if ( i < params.length ) { From 67cc6e086565d66f0cfb5cb05d656336ecaad55b Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Wed, 27 Nov 2024 17:05:00 +0100 Subject: [PATCH 14/43] remoe legacy test --- .../java/com/condation/cms/templates/JexlTest.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/JexlTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/JexlTest.java index bdac88b0e..4cacfbb55 100644 --- a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/JexlTest.java +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/JexlTest.java @@ -45,17 +45,6 @@ public class JexlTest { .silent(false) .create(); - @Test - void test_fn () { - - JexlContext context = new MapContext(); - context.set("fn", (Function) (String t) -> "hello " + t); - - var exp = jexl.createExpression("fn('CondationCMS')"); - - exp.evaluate(context); - } - @Test void test_fn_wrapper () { From 372da06d420172825cb64707b55e1902d477d54c Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Wed, 27 Nov 2024 21:10:44 +0100 Subject: [PATCH 15/43] add some tests --- .../cms/templates/TemplateEngineBuilder.java | 61 ++++++++++++++++++ .../templates/exceptions/ParserException.java | 7 +- .../templates/exceptions/TagException.java | 4 ++ .../cms/templates/TemplateFeatureTest.java | 64 +++++++++++++++++++ .../cms/templates/testdata/variable_1.html | 22 +++++++ .../testdata/variable_1_expected.html | 22 +++++++ 6 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngineBuilder.java create mode 100644 cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateFeatureTest.java create mode 100644 cms-sandbox/templates/src/test/resources/com/condation/cms/templates/testdata/variable_1.html create mode 100644 cms-sandbox/templates/src/test/resources/com/condation/cms/templates/testdata/variable_1_expected.html diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngineBuilder.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngineBuilder.java new file mode 100644 index 000000000..c5169c49d --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngineBuilder.java @@ -0,0 +1,61 @@ +package com.condation.cms.templates; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.templates.tags.ElseIfTag; +import com.condation.cms.templates.tags.ElseTag; +import com.condation.cms.templates.tags.EndForTag; +import com.condation.cms.templates.tags.EndIfTag; +import com.condation.cms.templates.tags.EndMacroTag; +import com.condation.cms.templates.tags.ForTag; +import com.condation.cms.templates.tags.IfTag; +import com.condation.cms.templates.tags.IncludeTag; +import com.condation.cms.templates.tags.MacroTag; +import com.condation.cms.templates.tags.SetTag; + +/** + * + * @author t.marx + */ +public class TemplateEngineBuilder { + + public static TemplateEngine buildDefault (TemplateLoader templateLoader) { + TemplateConfiguration config = new TemplateConfiguration(); + + config.registerTag(new IfTag()) + .registerTag(new ElseIfTag()) + .registerTag(new ElseTag()) + .registerTag(new EndIfTag()) + .registerTag(new ForTag()) + .registerTag(new EndForTag()) + .registerTag(new SetTag()) + .registerTag(new MacroTag()) + .registerTag(new EndMacroTag()) + .registerTag(new IncludeTag()) + ; + + config.setTemplateLoader(templateLoader); + + return new TemplateEngine(config); + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/exceptions/ParserException.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/exceptions/ParserException.java index 15604cbc2..79315bba2 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/exceptions/ParserException.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/exceptions/ParserException.java @@ -36,6 +36,9 @@ public ParserException(String message, int line, int column) { this.line = line; this.column = column; } - - + + @Override + public String getLocalizedMessage() { + return "Error: %s (line %d, column %d)".formatted(getMessage(), line, column); + } } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/exceptions/TagException.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/exceptions/TagException.java index 483171502..55b6dffb1 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/exceptions/TagException.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/exceptions/TagException.java @@ -37,5 +37,9 @@ public TagException(String message, int line, int column) { this.column = column; } + @Override + public String getLocalizedMessage() { + return "Error: %s (line %d, column %d)".formatted(getMessage(), line, column); + } } diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateFeatureTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateFeatureTest.java new file mode 100644 index 000000000..f92dd20ec --- /dev/null +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateFeatureTest.java @@ -0,0 +1,64 @@ +package com.condation.cms.templates; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.templates.loaders.StringTemplateLoader; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +/** + * + * @author t.marx + */ +public class TemplateFeatureTest { + + private StringTemplateLoader templateLoader = new StringTemplateLoader(); + + private TemplateEngine SUT = TemplateEngineBuilder.buildDefault(templateLoader); + + @Test + void test_features () throws Exception { + var templateFile = "variable_1.html"; + var templateContent = readContent(templateFile); + var expectedContent = readContent("variable_1_expected.html"); + + templateLoader.add(templateFile, templateContent); + + var template = SUT.getTemplate(templateFile); + + var rendered = template.execute(Map.of("name", "CondationCMS")); + + Assertions.assertThat(rendered).isEqualTo(expectedContent); + } + + private String readContent (String filename) throws IOException { + try (var stream = TemplateFeatureTest.class.getResourceAsStream("testdata/" + filename);) { + return new String(stream.readAllBytes(), StandardCharsets.UTF_8); + } + } +} diff --git a/cms-sandbox/templates/src/test/resources/com/condation/cms/templates/testdata/variable_1.html b/cms-sandbox/templates/src/test/resources/com/condation/cms/templates/testdata/variable_1.html new file mode 100644 index 000000000..29ddc349c --- /dev/null +++ b/cms-sandbox/templates/src/test/resources/com/condation/cms/templates/testdata/variable_1.html @@ -0,0 +1,22 @@ + +{{ name }} diff --git a/cms-sandbox/templates/src/test/resources/com/condation/cms/templates/testdata/variable_1_expected.html b/cms-sandbox/templates/src/test/resources/com/condation/cms/templates/testdata/variable_1_expected.html new file mode 100644 index 000000000..38ba3d1cc --- /dev/null +++ b/cms-sandbox/templates/src/test/resources/com/condation/cms/templates/testdata/variable_1_expected.html @@ -0,0 +1,22 @@ + +CondationCMS From edf146d1b1122d9f9e304a75f52976a436a8d2f1 Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Thu, 28 Nov 2024 13:22:12 +0100 Subject: [PATCH 16/43] extract CharacterSTream from lexer --- .../cms/templates/TemplateEngine.java | 5 +- .../cms/templates/lexer/CharacterStream.java | 70 +++++++++++ .../condation/cms/templates/lexer/Lexer.java | 111 ++++++------------ .../cms/templates/lexer/TokenStreamTest.java | 2 - .../cms/templates/parser/ParserTest.java | 4 +- 5 files changed, 110 insertions(+), 82 deletions(-) create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/CharacterStream.java diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java index be1e110f9..07035d10c 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngine.java @@ -39,12 +39,13 @@ public class TemplateEngine { private final TemplateConfiguration configuration; private final Parser parser; - + private final Lexer lexer; private final Renderer renderer; public TemplateEngine(TemplateConfiguration configuration) { this.configuration = configuration; parser = new Parser(configuration, jexl); + this.lexer = new Lexer(); this.renderer = new Renderer(configuration, this); } @@ -52,7 +53,7 @@ public Template getTemplate (String template) { String templateString = configuration.getTemplateLoader().load(template); - var tokenStream = new Lexer(templateString).tokenize(); + var tokenStream = lexer.tokenize(templateString); var rootNode = parser.parse(tokenStream); diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/CharacterStream.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/CharacterStream.java new file mode 100644 index 000000000..9ae9572b5 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/CharacterStream.java @@ -0,0 +1,70 @@ +package com.condation.cms.templates.lexer; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class CharacterStream { + + private final String source; + + private int position = 0; + + @Getter + private int line = 1; + @Getter + private int column = 1; + + + public boolean hasMore () { + return position < source.length(); + } + + public char charAtCurrentPosition () { + return source.charAt(position); + } + + protected char peek(int offset) { + return (position + offset < source.length()) ? source.charAt(position + offset) : '\0'; + } + + protected String readWhile(java.util.function.Predicate condition) { + StringBuilder result = new StringBuilder(); + while (position < source.length() && condition.test(source.charAt(position))) { + result.append(source.charAt(position)); + + advance(); + } + return result.toString(); + } + + protected String readUntil(String delimiter) { + StringBuilder result = new StringBuilder(); + while (position < source.length() && !source.startsWith(delimiter, position)) { + result.append(source.charAt(position)); + advance(); + } + return result.toString(); + } + + protected void skipWhitespace() { + while (position < source.length() && Character.isWhitespace(source.charAt(position))) { + advance(); + } + } + + protected void skip (int count) { + position += count; + } + + protected void advance() { + if (source.charAt(position) == '\n') { + line++; + column = 1; + } else { + column++; + } + position++; + } + +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java index f6d67b3cd..03a539090 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/Lexer.java @@ -27,109 +27,68 @@ public class Lexer { - private final String input; - private int position = 0; + public Lexer() { + + } - private int line = 1; - private int column = 1;// neuer Zustand + public TokenStream tokenize(String input) { - private final State state = new State(); - - public Lexer(String input) { - this.input = input; - } + CharacterStream charStream = new CharacterStream(input); + final State state = new State(); - public TokenStream tokenize() { List tokens = new ArrayList<>(); - while (position < input.length()) { - char c = input.charAt(position); + while (charStream.hasMore()) { + char c = charStream.charAtCurrentPosition(); + + int line = charStream.getLine(); + int column = charStream.getColumn(); - if (c == '{' && peek(1) == '{') { + if (c == '{' && charStream.peek(1) == '{') { tokens.add(new Token(Token.Type.VARIABLE_START, "{{", line, column)); - position += 2; + charStream.skip(2); state.set(State.Type.VARIABLE); - } else if (c == '{' && peek(1) == '%') { + } else if (c == '{' && charStream.peek(1) == '%') { tokens.add(new Token(Token.Type.TAG_START, "{%", line, column)); - position += 2; + charStream.skip(2); state.set(State.Type.TAG); - readTagContent(tokens); // Inhalte des Tags lesen - } else if (c == '{' && peek(1) == '#') { + readTagContent(tokens, charStream); // Inhalte des Tags lesen + } else if (c == '{' && charStream.peek(1) == '#') { tokens.add(new Token(Token.Type.COMMENT_START, "{*", line, column)); - position += 2; + charStream.skip(2); state.set(State.Type.COMMENT); - } else if (state.is(State.Type.TAG) && c == '%' && peek(1) == '}') { + } else if (state.is(State.Type.TAG) && c == '%' && charStream.peek(1) == '}') { tokens.add(new Token(Token.Type.TAG_END, "%}", line, column)); - position += 2; + charStream.skip(2); state.set(State.Type.NONE); - } else if (state.is(State.Type.VARIABLE) && c == '}' && peek(1) == '}') { + } else if (state.is(State.Type.VARIABLE) && c == '}' && charStream.peek(1) == '}') { tokens.add(new Token(Token.Type.VARIABLE_END, "}}", line, column)); - position += 2; + charStream.skip(2); state.set(State.Type.NONE); - } else if (state.is(State.Type.COMMENT) && c == '#' && peek(1) == '}') { + } else if (state.is(State.Type.COMMENT) && c == '#' && charStream.peek(1) == '}') { tokens.add(new Token(Token.Type.COMMENT_END, "#}", line, column)); - position += 2; + charStream.skip(2); state.set(State.Type.NONE); } else if (state.is(State.Type.VARIABLE, State.Type.TAG) && Character.isLetterOrDigit(c)) { - //tokens.add(new Token(Token.Type.IDENTIFIER, readWhile(Character::isLetter), line, column)); - tokens.add(new Token(Token.Type.IDENTIFIER, readUntil("}}"), line, column)); + tokens.add(new Token(Token.Type.IDENTIFIER, charStream.readUntil("}}"), line, column)); } else if (state.is(State.Type.COMMENT)) { - tokens.add(new Token(Token.Type.COMMENT_VALUE, readUntil("#}"), line, column)); // Alles bis zum nächsten '{' als Text speichern + tokens.add(new Token(Token.Type.COMMENT_VALUE, charStream.readUntil("#}"), line, column)); // Alles bis zum nächsten '{' als Text speichern } else if (!state.is(State.Type.VARIABLE, State.Type.TAG)) { - tokens.add(new Token(Token.Type.TEXT, readUntil("{"), line, column)); // Alles bis zum nächsten '{' als Text speichern + tokens.add(new Token(Token.Type.TEXT, charStream.readUntil("{"), line, column)); // Alles bis zum nächsten '{' als Text speichern } else { - advance(); + charStream.advance(); } } - tokens.add(new Token(Token.Type.END, "", line, column)); + tokens.add(new Token(Token.Type.END, "", charStream.getLine(), charStream.getColumn())); return new TokenStream(tokens); } - private void readTagContent(List tokens) { - skipWhitespace(); + private void readTagContent(List tokens, CharacterStream charStream) { + charStream.skipWhitespace(); - String keyword = readWhile(Character::isLetter); - tokens.add(new Token(Token.Type.IDENTIFIER, keyword, line, column)); - - String condition = readUntil("%}"); - tokens.add(new Token(Token.Type.EXPRESSION, condition, line, column)); - } + String keyword = charStream.readWhile(Character::isLetter); + tokens.add(new Token(Token.Type.IDENTIFIER, keyword, charStream.getLine(), charStream.getColumn())); - private char peek(int offset) { - return (position + offset < input.length()) ? input.charAt(position + offset) : '\0'; - } - - private String readWhile(java.util.function.Predicate condition) { - StringBuilder result = new StringBuilder(); - while (position < input.length() && condition.test(input.charAt(position))) { - result.append(input.charAt(position)); - - advance(); - } - return result.toString(); - } - - private String readUntil(String delimiter) { - StringBuilder result = new StringBuilder(); - while (position < input.length() && !input.startsWith(delimiter, position)) { - result.append(input.charAt(position)); - advance(); - } - return result.toString(); - } - - private void skipWhitespace() { - while (position < input.length() && Character.isWhitespace(input.charAt(position))) { - advance(); - } - } - - private void advance() { - if (input.charAt(position) == '\n') { - line++; - column = 1; - } else { - column++; - } - position++; + String condition = charStream.readUntil("%}"); + tokens.add(new Token(Token.Type.EXPRESSION, condition, charStream.getLine(), charStream.getColumn())); } } diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/lexer/TokenStreamTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/lexer/TokenStreamTest.java index 4dd9b800c..8c3b7d40a 100644 --- a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/lexer/TokenStreamTest.java +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/lexer/TokenStreamTest.java @@ -22,8 +22,6 @@ * #L% */ -import com.condation.cms.templates.lexer.TokenStream; -import com.condation.cms.templates.lexer.Token; import java.util.List; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/ParserTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/ParserTest.java index 7be49044f..31e9b612f 100644 --- a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/ParserTest.java +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/parser/ParserTest.java @@ -49,14 +49,14 @@ public class ParserTest { public static void setup () { var config = new TemplateConfiguration(); parser = new Parser(config, jexl); + lexer = new Lexer(); } @Test public void test_filters() { var input = "{{ content | raw | trim }}"; - lexer = new Lexer(input); - var tokenStream = lexer.tokenize(); + var tokenStream = lexer.tokenize(input); var ast = parser.parse(tokenStream); From c5fab986b17d0a9ca4ee050ae3c995b88bc3e5ce Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Thu, 28 Nov 2024 16:11:28 +0100 Subject: [PATCH 17/43] add variable filter implementation --- .../cms/templates/TemplateConfiguration.java | 10 +++++ .../cms/templates/filter/Filter.java | 6 +++ .../cms/templates/filter/FilterPipeline.java | 39 ++++++++++++++++++ .../cms/templates/filter/FilterRegistry.java | 20 ++++++++++ .../cms/templates/parser/Filter.java | 10 ++++- .../cms/templates/parser/Parser.java | 3 +- .../cms/templates/utils/TemplateUtils.java | 26 ++++++++++++ .../cms/templates/TemplateUtilsTest.java | 40 ++++++++++++------- .../cms/templates/filter/FilterTest.java | 30 ++++++++++++++ 9 files changed, 166 insertions(+), 18 deletions(-) create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/Filter.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/FilterPipeline.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/FilterRegistry.java create mode 100644 cms-sandbox/templates/src/test/java/com/condation/cms/templates/filter/FilterTest.java diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateConfiguration.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateConfiguration.java index 3309e8404..7147b5f92 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateConfiguration.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateConfiguration.java @@ -25,6 +25,10 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; + +import com.condation.cms.templates.filter.Filter; +import com.condation.cms.templates.filter.FilterRegistry; + import lombok.Getter; import lombok.Setter; @@ -35,11 +39,17 @@ public class TemplateConfiguration { private final Map registeredTags = new HashMap<>(); + private final FilterRegistry filterRegistry = new FilterRegistry(); @Getter @Setter private TemplateLoader templateLoader; + public TemplateConfiguration registerFilter (String name, Filter filter) { + filterRegistry.register(name, filter); + return this; + } + public TemplateConfiguration registerTag (Tag tag) { registeredTags.put(tag.getTagName(), tag); diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/Filter.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/Filter.java new file mode 100644 index 000000000..5ad8f2c6e --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/Filter.java @@ -0,0 +1,6 @@ +package com.condation.cms.templates.filter; + +@FunctionalInterface +public interface Filter { + String apply(String input, String... params); +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/FilterPipeline.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/FilterPipeline.java new file mode 100644 index 000000000..b9c934799 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/FilterPipeline.java @@ -0,0 +1,39 @@ +package com.condation.cms.templates.filter; + +import java.util.ArrayList; +import java.util.List; + +public class FilterPipeline { + private final List steps = new ArrayList<>(); + private final FilterRegistry registry; + + public FilterPipeline(FilterRegistry registry) { + this.registry = registry; + } + + public void addStep(String filterName, String... params) { + if (!registry.exists(filterName)) { + throw new IllegalArgumentException("Filter not found: " + filterName); + } + steps.add(new PipelineStep(filterName, params)); + } + + public String execute(String input) { + String result = input; + for (PipelineStep step : steps) { + Filter filter = registry.get(step.filterName); + result = filter.apply(result, step.params); + } + return result; + } + + private static class PipelineStep { + private final String filterName; + private final String[] params; + + public PipelineStep(String filterName, String... params) { + this.filterName = filterName; + this.params = params; + } + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/FilterRegistry.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/FilterRegistry.java new file mode 100644 index 000000000..d1e36a856 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/FilterRegistry.java @@ -0,0 +1,20 @@ +package com.condation.cms.templates.filter; + +import java.util.HashMap; +import java.util.Map; + +public class FilterRegistry { + private final Map filters = new HashMap<>(); + + public void register(String name, Filter filter) { + filters.put(name, filter); + } + + public Filter get(String name) { + return filters.get(name); + } + + public boolean exists(String name) { + return filters.containsKey(name); + } +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Filter.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Filter.java index e35e151e9..1d81d16f5 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Filter.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Filter.java @@ -1,5 +1,9 @@ package com.condation.cms.templates.parser; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + /*- * #%L * templates @@ -26,6 +30,8 @@ * * @author t.marx */ -public record Filter(String name) { - +public record Filter(String name, List parameters) { + public Filter (String name) { + this(name, Collections.emptyList()); + } } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java index fbd1b44e6..db467914c 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/parser/Parser.java @@ -30,7 +30,6 @@ import java.util.Stack; import com.condation.cms.templates.lexer.Token; -import static com.condation.cms.templates.lexer.Token.Type.VARIABLE_START; import lombok.RequiredArgsConstructor; import org.apache.commons.jexl3.JexlEngine; @@ -139,7 +138,7 @@ public ASTNode parse(final TokenStream tokenStream) { variableNode1.setFilters(TemplateUtils.extractFilters(identifier) .stream() - .map(filter -> new Filter(filter)) + .map(TemplateUtils::parseFilter) .toList() ); } else { diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/utils/TemplateUtils.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/utils/TemplateUtils.java index e103f69e1..30a79b988 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/utils/TemplateUtils.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/utils/TemplateUtils.java @@ -26,6 +26,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import com.condation.cms.templates.parser.Filter; + /** * * @author t.marx @@ -57,6 +59,30 @@ public static List extractFilters(String variable) { return filters; } + public static Filter parseFilter(String filterDefinition) { + // Regular expression to match the filter name and parameters + String regex = "^(\\w+)\\((.*?)\\)$"; + Pattern pattern = Pattern.compile(regex); + Matcher matcher = pattern.matcher(filterDefinition); + + if (matcher.matches()) { + String filterName = matcher.group(1); + String paramsString = matcher.group(2); + + // Split the parameters by commas and trim whitespaces + List parameters = new ArrayList<>(); + if (!paramsString.isBlank()) { + for (String param : paramsString.split(",")) { + parameters.add(param.trim()); + } + } + + return new Filter(filterName, parameters); + } else { + throw new IllegalArgumentException("Invalid filter definition: " + filterDefinition); + } + } + public static String extractVariableName(String input) { // Split basierend auf "|" String[] parts = input.split("\\|"); diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateUtilsTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateUtilsTest.java index 80ba6fdd0..d4dc1b152 100644 --- a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateUtilsTest.java +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateUtilsTest.java @@ -31,40 +31,52 @@ * @author t.marx */ public class TemplateUtilsTest { - + @Test public void no_filter() { Assertions.assertThat(TemplateUtils.hasFilters("var")).isFalse(); - + Assertions.assertThat(TemplateUtils.extractFilters("var")).isEmpty(); - + Assertions.assertThat(TemplateUtils.extractVariableName("var")).isEqualTo("var"); } - + @Test public void one_filter() { Assertions.assertThat(TemplateUtils.hasFilters("var | trim")).isTrue(); - + Assertions.assertThat(TemplateUtils.extractFilters("var | trim")).containsExactly("trim"); - + Assertions.assertThat(TemplateUtils.extractVariableName("var")).isEqualTo("var"); } - + @Test public void more_filter() { Assertions.assertThat(TemplateUtils.hasFilters("var | trim | upper | raw")).isTrue(); - - Assertions.assertThat(TemplateUtils.extractFilters("var | trim | upper | raw")).containsExactly("trim", "upper", "raw"); - + + Assertions.assertThat(TemplateUtils.extractFilters("var | trim | upper | raw")).containsExactly("trim", "upper", + "raw"); + Assertions.assertThat(TemplateUtils.extractVariableName("var")).isEqualTo("var"); } - + @Test public void filter_with_params() { Assertions.assertThat(TemplateUtils.hasFilters("var | trim(100) | raw(html)")).isTrue(); - - Assertions.assertThat(TemplateUtils.extractFilters("var | trim(100) | raw(html)")).containsExactly("trim(100)", "raw(html)"); - + + Assertions.assertThat(TemplateUtils.extractFilters("var | trim(100) | raw(html)")).containsExactly("trim(100)", + "raw(html)"); + Assertions.assertThat(TemplateUtils.extractVariableName("var")).isEqualTo("var"); } + + @Test + public void filter_parse() { + var filter = TemplateUtils.parseFilter("truncate(20, 'ellipsis')"); + + Assertions.assertThat(filter.name()).isEqualTo("truncate"); + Assertions.assertThat(filter.parameters()) + .hasSize(2) + .containsExactly("20", "'ellipsis'"); + } } diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/filter/FilterTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/filter/FilterTest.java new file mode 100644 index 000000000..8211b9da8 --- /dev/null +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/filter/FilterTest.java @@ -0,0 +1,30 @@ +package com.condation.cms.templates.filter; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class FilterTest { + + FilterRegistry registry = new FilterRegistry(); + FilterPipeline pipeline = new FilterPipeline(registry); + + @BeforeEach + public void setup() { + // Register filters + registry.register("raw", (input, params) -> input); // Raw does nothing + registry.register("truncate", (input, params) -> { + int length = params.length > 0 ? Integer.parseInt(params[0]) : input.length(); + return input.length() > length ? input.substring(0, length) + "..." : input; + }); + + pipeline.addStep("raw"); + pipeline.addStep("truncate", "20"); + } + + @Test + void test() { + String result = pipeline.execute("Dies ist ein langer Text, der abgeschnitten werden sollte."); + Assertions.assertThat(result).isEqualTo("Dies ist ein langer ..."); + } +} From 961f44f47dc14cc105adde5eb1f8672cdf519c72 Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Thu, 28 Nov 2024 21:56:51 +0100 Subject: [PATCH 18/43] introduce filter pipeline --- .../cms/templates/TemplateConfiguration.java | 1 + .../cms/templates/TemplateEngineBuilder.java | 6 +++ .../cms/templates/filter/Filter.java | 24 +++++++++- .../cms/templates/filter/FilterPipeline.java | 34 +++++++++++--- .../cms/templates/filter/FilterRegistry.java | 22 ++++++++++ .../cms/templates/filter/impl/RawFilter.java | 42 ++++++++++++++++++ .../templates/filter/impl/UpperFilter.java | 40 +++++++++++++++++ .../cms/templates/lexer/CharacterStream.java | 22 ++++++++++ .../cms/templates/renderer/Renderer.java | 15 ++++--- .../renderer/VariableNodeRenderer.java | 44 +++++++++++++------ .../cms/templates/utils/TemplateUtils.java | 10 ++--- .../cms/templates/TemplateFeatureTest.java | 19 +++++++- .../cms/templates/TemplateUtilsTest.java | 11 ++++- .../cms/templates/filter/FilterTest.java | 33 ++++++++++++-- .../testdata/variable_raw_filter.html | 23 ++++++++++ .../variable_raw_filter_expected.html | 22 ++++++++++ 16 files changed, 331 insertions(+), 37 deletions(-) create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/impl/RawFilter.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/impl/UpperFilter.java create mode 100644 cms-sandbox/templates/src/test/resources/com/condation/cms/templates/testdata/variable_raw_filter.html create mode 100644 cms-sandbox/templates/src/test/resources/com/condation/cms/templates/testdata/variable_raw_filter_expected.html diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateConfiguration.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateConfiguration.java index 7147b5f92..afbc42851 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateConfiguration.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateConfiguration.java @@ -39,6 +39,7 @@ public class TemplateConfiguration { private final Map registeredTags = new HashMap<>(); + @Getter private final FilterRegistry filterRegistry = new FilterRegistry(); @Getter diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngineBuilder.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngineBuilder.java index c5169c49d..e1a6d9d5d 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngineBuilder.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngineBuilder.java @@ -1,5 +1,8 @@ package com.condation.cms.templates; +import com.condation.cms.templates.filter.impl.RawFilter; +import com.condation.cms.templates.filter.impl.UpperFilter; + /*- * #%L * templates @@ -52,6 +55,9 @@ public static TemplateEngine buildDefault (TemplateLoader templateLoader) { .registerTag(new MacroTag()) .registerTag(new EndMacroTag()) .registerTag(new IncludeTag()) + + .registerFilter(UpperFilter.NAME, new UpperFilter()) + .registerFilter(RawFilter.NAME, new RawFilter()) ; config.setTemplateLoader(templateLoader); diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/Filter.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/Filter.java index 5ad8f2c6e..24e414b90 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/Filter.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/Filter.java @@ -1,6 +1,28 @@ package com.condation.cms.templates.filter; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + @FunctionalInterface public interface Filter { - String apply(String input, String... params); + Object apply(Object input, Object... params); } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/FilterPipeline.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/FilterPipeline.java index b9c934799..5fa0d3958 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/FilterPipeline.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/FilterPipeline.java @@ -1,5 +1,27 @@ package com.condation.cms.templates.filter; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import java.util.ArrayList; import java.util.List; @@ -11,15 +33,17 @@ public FilterPipeline(FilterRegistry registry) { this.registry = registry; } - public void addStep(String filterName, String... params) { + public FilterPipeline addStep(String filterName, Object... params) { if (!registry.exists(filterName)) { throw new IllegalArgumentException("Filter not found: " + filterName); } steps.add(new PipelineStep(filterName, params)); + + return this; } - public String execute(String input) { - String result = input; + public Object execute(Object input) { + Object result = input; for (PipelineStep step : steps) { Filter filter = registry.get(step.filterName); result = filter.apply(result, step.params); @@ -29,9 +53,9 @@ public String execute(String input) { private static class PipelineStep { private final String filterName; - private final String[] params; + private final Object[] params; - public PipelineStep(String filterName, String... params) { + public PipelineStep(String filterName, Object... params) { this.filterName = filterName; this.params = params; } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/FilterRegistry.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/FilterRegistry.java index d1e36a856..3e1392a46 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/FilterRegistry.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/FilterRegistry.java @@ -1,5 +1,27 @@ package com.condation.cms.templates.filter; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import java.util.HashMap; import java.util.Map; diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/impl/RawFilter.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/impl/RawFilter.java new file mode 100644 index 000000000..c1bb017a9 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/impl/RawFilter.java @@ -0,0 +1,42 @@ +package com.condation.cms.templates.filter.impl; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import org.apache.commons.text.StringEscapeUtils; + +import com.condation.cms.templates.filter.Filter; + +public class RawFilter implements Filter { + + public static final String NAME = "raw"; + + @Override + public Object apply(Object input, Object... params) { + if (input == null && !(input instanceof String)) { + return input; + } + + return StringEscapeUtils.unescapeHtml4((String)input); + } + +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/impl/UpperFilter.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/impl/UpperFilter.java new file mode 100644 index 000000000..d0ae4554c --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/filter/impl/UpperFilter.java @@ -0,0 +1,40 @@ +package com.condation.cms.templates.filter.impl; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.templates.filter.Filter; + +public class UpperFilter implements Filter { + + public static final String NAME = "upper"; + + @Override + public Object apply(Object input, Object... params) { + if (input == null || !(input instanceof String)) { + return input; + } + + return ((String)input).toUpperCase(); + } + +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/CharacterStream.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/CharacterStream.java index 9ae9572b5..569ad1a67 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/CharacterStream.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/lexer/CharacterStream.java @@ -1,5 +1,27 @@ package com.condation.cms.templates.lexer; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import lombok.Getter; import lombok.RequiredArgsConstructor; diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java index e3b8e087b..6d53eaa04 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/Renderer.java @@ -33,13 +33,18 @@ import org.apache.commons.jexl3.JexlEngine; import org.apache.commons.text.StringEscapeUtils; -@RequiredArgsConstructor public class Renderer { private final TemplateConfiguration configuration; private final TemplateEngine templateEngine; - private final VariableNodeRenderer variableNodeRenderer = new VariableNodeRenderer(); + private final VariableNodeRenderer variableNodeRenderer; + + public Renderer(TemplateConfiguration configuration, TemplateEngine templateEngine) { + this.configuration = configuration; + this.templateEngine = templateEngine; + this.variableNodeRenderer = new VariableNodeRenderer(configuration); + } public static record Context( JexlEngine engine, @@ -60,12 +65,10 @@ public String render(ASTNode node, final JexlEngine engine, final ScopeStack sco private void renderNode(ASTNode node, Context context, StringBuilder output) { - var scopeContext = context.createEngineContext(); - if (node instanceof TextNode textNode) { output.append(textNode.text); } else if (node instanceof VariableNode vnode) { - renderVariable(vnode, scopeContext, output); + renderVariable(vnode, context, output); } else if (node instanceof TagNode tagNode) { var tag = configuration.getTag(tagNode.getName()); if (tag.isPresent()) { @@ -78,7 +81,7 @@ private void renderNode(ASTNode node, Context context, StringBuilder output) { } } - private void renderVariable (VariableNode node, ScopeContext context, StringBuilder output) { + private void renderVariable (VariableNode node, Context context, StringBuilder output) { variableNodeRenderer.render(node, context, output); } } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/VariableNodeRenderer.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/VariableNodeRenderer.java index ee4859651..02eabd150 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/VariableNodeRenderer.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/renderer/VariableNodeRenderer.java @@ -24,6 +24,10 @@ import com.condation.cms.templates.parser.Filter; import com.condation.cms.templates.parser.VariableNode; +import com.condation.cms.templates.renderer.Renderer.Context; +import com.condation.cms.templates.TemplateConfiguration; +import com.condation.cms.templates.filter.FilterPipeline; + import java.util.List; import lombok.RequiredArgsConstructor; import org.apache.commons.text.StringEscapeUtils; @@ -35,32 +39,44 @@ @RequiredArgsConstructor class VariableNodeRenderer { - protected void render(VariableNode node, ScopeContext context, StringBuilder output) { - Object variableValue = node.getExpression().evaluate(context); + private final TemplateConfiguration templateConfiguration; + + protected void render(VariableNode node, Context context, StringBuilder output) { + Object variableValue = node.getExpression().evaluate(context.createEngineContext()); if (variableValue != null && variableValue instanceof String stringValue) { - output.append(evaluateStringFilters(stringValue, node.getFilters())); + output.append(evaluateStringFilters(stringValue, node.getFilters(), context)); } else { output.append(variableValue != null ? variableValue : ""); } } - protected String evaluateStringFilters(String value, List filters) { + protected String evaluateStringFilters(String value, List filters, Context context) { var returnValue = StringEscapeUtils.ESCAPE_HTML4.translate(value); if (filters != null && !filters.isEmpty()) { - for (var filter : filters) { - returnValue = switch (filter.name()) { - case "raw" -> - StringEscapeUtils.UNESCAPE_HTML4.translate(value); - case "trim" -> - returnValue.trim(); - default -> - returnValue; - }; - } + var filterPipeline = createPipeline(filters, context); + + returnValue = (String)filterPipeline.execute(returnValue); } return returnValue; } + + private FilterPipeline createPipeline(List filters, Context context) { + var filterPipeline = new FilterPipeline(templateConfiguration.getFilterRegistry()); + + var engineScope = context.createEngineContext(); + for (Filter filter : filters) { + var params = filter.parameters() + .stream() + .map(param -> { + var exp = context.engine().createExpression(param); + return exp.evaluate(engineScope); + }).toArray(); + filterPipeline.addStep(filter.name(), params); + } + + return filterPipeline; + } } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/utils/TemplateUtils.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/utils/TemplateUtils.java index 30a79b988..0af8d565f 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/utils/TemplateUtils.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/utils/TemplateUtils.java @@ -61,17 +61,17 @@ public static List extractFilters(String variable) { public static Filter parseFilter(String filterDefinition) { // Regular expression to match the filter name and parameters - String regex = "^(\\w+)\\((.*?)\\)$"; + String regex = "^(\\w+)(?:\\((.*?)\\))?$"; Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(filterDefinition); if (matcher.matches()) { - String filterName = matcher.group(1); - String paramsString = matcher.group(2); + String filterName = matcher.group(1); // Filter name + String paramsString = matcher.group(2); // Optional parameters - // Split the parameters by commas and trim whitespaces + // Split the parameters if present, otherwise return an empty list List parameters = new ArrayList<>(); - if (!paramsString.isBlank()) { + if (paramsString != null && !paramsString.isBlank()) { for (String param : paramsString.split(",")) { parameters.add(param.trim()); } diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateFeatureTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateFeatureTest.java index f92dd20ec..c0ae878a0 100644 --- a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateFeatureTest.java +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateFeatureTest.java @@ -42,7 +42,7 @@ public class TemplateFeatureTest { private TemplateEngine SUT = TemplateEngineBuilder.buildDefault(templateLoader); @Test - void test_features () throws Exception { + void test_variable_replacement () throws Exception { var templateFile = "variable_1.html"; var templateContent = readContent(templateFile); var expectedContent = readContent("variable_1_expected.html"); @@ -55,6 +55,23 @@ void test_features () throws Exception { Assertions.assertThat(rendered).isEqualTo(expectedContent); } + + @ParameterizedTest + @CsvSource({ + "variable_raw_filter.html,variable_raw_filter_expected.html" + }) + void test_features (String templateFile, String expectedFile) throws Exception { + var templateContent = readContent(templateFile); + var expectedContent = readContent(expectedFile); + + templateLoader.add(templateFile, templateContent); + + var template = SUT.getTemplate(templateFile); + + var rendered = template.execute(); + + Assertions.assertThat(rendered).isEqualToIgnoringWhitespace(expectedContent); + } private String readContent (String filename) throws IOException { try (var stream = TemplateFeatureTest.class.getResourceAsStream("testdata/" + filename);) { diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateUtilsTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateUtilsTest.java index d4dc1b152..5b2fa0279 100644 --- a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateUtilsTest.java +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateUtilsTest.java @@ -71,7 +71,7 @@ public void filter_with_params() { } @Test - public void filter_parse() { + public void filter_parse_with_params() { var filter = TemplateUtils.parseFilter("truncate(20, 'ellipsis')"); Assertions.assertThat(filter.name()).isEqualTo("truncate"); @@ -79,4 +79,13 @@ public void filter_parse() { .hasSize(2) .containsExactly("20", "'ellipsis'"); } + + @Test + public void filter_parse_without_params() { + var filter = TemplateUtils.parseFilter("raw"); + + Assertions.assertThat(filter.name()).isEqualTo("raw"); + Assertions.assertThat(filter.parameters()) + .isEmpty();; + } } diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/filter/FilterTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/filter/FilterTest.java index 8211b9da8..7187ea738 100644 --- a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/filter/FilterTest.java +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/filter/FilterTest.java @@ -1,5 +1,27 @@ package com.condation.cms.templates.filter; +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -14,17 +36,20 @@ public void setup() { // Register filters registry.register("raw", (input, params) -> input); // Raw does nothing registry.register("truncate", (input, params) -> { - int length = params.length > 0 ? Integer.parseInt(params[0]) : input.length(); - return input.length() > length ? input.substring(0, length) + "..." : input; + if (input instanceof String stringValue) { + int length = params.length > 0 ? (Integer)params[0] : stringValue.length(); + return stringValue.length() > length ? stringValue.substring(0, length) + "..." : input; + } + return input; }); pipeline.addStep("raw"); - pipeline.addStep("truncate", "20"); + pipeline.addStep("truncate", 20); } @Test void test() { - String result = pipeline.execute("Dies ist ein langer Text, der abgeschnitten werden sollte."); + Object result = pipeline.execute("Dies ist ein langer Text, der abgeschnitten werden sollte."); Assertions.assertThat(result).isEqualTo("Dies ist ein langer ..."); } } diff --git a/cms-sandbox/templates/src/test/resources/com/condation/cms/templates/testdata/variable_raw_filter.html b/cms-sandbox/templates/src/test/resources/com/condation/cms/templates/testdata/variable_raw_filter.html new file mode 100644 index 000000000..b704b79df --- /dev/null +++ b/cms-sandbox/templates/src/test/resources/com/condation/cms/templates/testdata/variable_raw_filter.html @@ -0,0 +1,23 @@ + +{% set content = "

    This is a paragraph

    " %} +{{ content | raw }} diff --git a/cms-sandbox/templates/src/test/resources/com/condation/cms/templates/testdata/variable_raw_filter_expected.html b/cms-sandbox/templates/src/test/resources/com/condation/cms/templates/testdata/variable_raw_filter_expected.html new file mode 100644 index 000000000..05858a72d --- /dev/null +++ b/cms-sandbox/templates/src/test/resources/com/condation/cms/templates/testdata/variable_raw_filter_expected.html @@ -0,0 +1,22 @@ + +

    This is a paragraph

    From 342a05f44d52ad7b8c9a13fcfdcef43de8942b3b Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Fri, 29 Nov 2024 12:47:23 +0100 Subject: [PATCH 19/43] add import statement --- .../cms/templates/TemplateEngineBuilder.java | 2 + .../cms/templates/tags/ImportTag.java | 127 ++++++++++++++++++ .../cms/templates/tags/IncludeTag.java | 13 +- .../cms/templates/tags/MacroTag.java | 6 +- .../templates/TemplateEngineIncludeTest.java | 27 ++-- .../templates/TemplateEngineMacroTest.java | 59 +++++--- 6 files changed, 189 insertions(+), 45 deletions(-) create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ImportTag.java diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngineBuilder.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngineBuilder.java index e1a6d9d5d..09d4b68f0 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngineBuilder.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateEngineBuilder.java @@ -32,6 +32,7 @@ import com.condation.cms.templates.tags.EndMacroTag; import com.condation.cms.templates.tags.ForTag; import com.condation.cms.templates.tags.IfTag; +import com.condation.cms.templates.tags.ImportTag; import com.condation.cms.templates.tags.IncludeTag; import com.condation.cms.templates.tags.MacroTag; import com.condation.cms.templates.tags.SetTag; @@ -55,6 +56,7 @@ public static TemplateEngine buildDefault (TemplateLoader templateLoader) { .registerTag(new MacroTag()) .registerTag(new EndMacroTag()) .registerTag(new IncludeTag()) + .registerTag(new ImportTag()) .registerFilter(UpperFilter.NAME, new UpperFilter()) .registerFilter(RawFilter.NAME, new RawFilter()) diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ImportTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ImportTag.java new file mode 100644 index 000000000..36021bb15 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/ImportTag.java @@ -0,0 +1,127 @@ +package com.condation.cms.templates.tags; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ +import com.condation.cms.templates.DefaultTemplate; +import com.condation.cms.templates.Tag; +import com.condation.cms.templates.exceptions.TagException; +import com.condation.cms.templates.parser.TagNode; +import com.condation.cms.templates.renderer.Renderer; +import com.condation.cms.templates.renderer.ScopeStack; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import lombok.extern.slf4j.Slf4j; + +/** + * + * @author t.marx + */ +@Slf4j +public class ImportTag implements Tag { + + // Regular expression to match the template and optional namespace + private static final String REGEX = "^((?:'([^']+)'|\"([^\"]+)\"|[a-zA-Z0-9._-]+))(?:\\s+as\\s+(\\w+))?$"; + private static final Pattern PATTERN = Pattern.compile(REGEX); + + @Override + public String getTagName() { + return "import"; + } + + @Override + public void render(TagNode node, Renderer.Context context, StringBuilder sb) { + try { + + var importDefinition = parseImport(node.getCondition(), node); + + var scope = context.createEngineContext(); + var templateString = (String) context.engine().createExpression(importDefinition.template).evaluate(scope); + + var template = (DefaultTemplate) context.templateEngine().getTemplate(templateString); + if (template != null) { + CustomScopeStack scopeStack = new CustomScopeStack(); + template.evaluate(scopeStack); + + var namespace = new HashMap(); + scopeStack.macros().forEach(macro -> { + if (importDefinition.namespace.isPresent()) { + namespace.put(macro.getMacro().getName(), macro); + } else { + context.scopes().setVariable(macro.getMacro().getName(), macro); + } + }); + if (importDefinition.namespace.isPresent()) { + context.scopes().setVariable(importDefinition.namespace.get(), namespace); + } + + } + } catch (Exception e) { + throw new TagException("error importing template", node.getLine(), node.getColumn()); + } + } + + private class CustomScopeStack extends ScopeStack { + + private List macros = new ArrayList<>(); + + public Stream macros() { + return macros.stream() + .map(macro -> getVariable(macro)) + .filter(Optional::isPresent) + .map(Optional::get) + .map(MacroTag.MacroFunction.class::cast); + } + + @Override + public void setVariable(String name, Object value) { + super.setVariable(name, value); + + if (value instanceof MacroTag.MacroFunction) { + macros.add(name); + } + } + } + + // Method to parse the import statement + public static ImportDefinition parseImport(String importStatement, TagNode node) { + + Matcher matcher = PATTERN.matcher(importStatement.trim()); + + if (matcher.matches()) { + String template = matcher.group(1); + String namespace = matcher.group(4); + return new ImportDefinition(template, Optional.ofNullable(namespace)); + } else { + throw new TagException("Invalid import definition: " + importStatement, node.getLine(), node.getColumn()); + } + } + + private record ImportDefinition(String template, Optional namespace) { + + } +; +} diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IncludeTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IncludeTag.java index 2b903f7a4..83d770174 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IncludeTag.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/IncludeTag.java @@ -44,7 +44,7 @@ public String getTagName() { @Override public void render(TagNode node, Renderer.Context context, StringBuilder sb) { try { - var templateString = getTemplate(node); + var templateString = getTemplate(node, context); var template = (DefaultTemplate)context.templateEngine().getTemplate(templateString); if (template != null) { @@ -56,15 +56,10 @@ public void render(TagNode node, Renderer.Context context, StringBuilder sb) { } } - private String getTemplate (TagNode node) { + private String getTemplate (TagNode node, Renderer.Context context) { var template = node.getCondition().trim(); - if (template.startsWith("\"") || template.startsWith("'")) { - template = template.substring(1); - } - if (template.endsWith("\"") || template.endsWith("'")) { - template = template.substring(0, template.length() - 1); - } - return template; + var scope = context.createEngineContext(); + return (String)context.engine().createExpression(template).evaluate(scope); } } diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/MacroTag.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/MacroTag.java index 3d5a3c37a..04c6840bf 100644 --- a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/MacroTag.java +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/tags/MacroTag.java @@ -28,8 +28,6 @@ import com.condation.cms.templates.renderer.Renderer; import com.condation.cms.templates.renderer.ScopeStack; import com.condation.cms.templates.utils.MacroUtils; -import java.awt.image.renderable.RenderContext; -import java.util.ArrayList; import java.util.List; import java.util.Optional; import lombok.Getter; @@ -71,7 +69,8 @@ public void render(TagNode node, Renderer.Context context, StringBuilder sb) { @RequiredArgsConstructor public static class Macro { - + + @Getter private final String name; private final List parameters; @@ -84,6 +83,7 @@ public static class Macro { @RequiredArgsConstructor public static class MacroFunction implements JexlMethod { + @Getter private final Macro macro; private final Renderer.Context context; diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineIncludeTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineIncludeTest.java index 07b05210e..6f169dc72 100644 --- a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineIncludeTest.java +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineIncludeTest.java @@ -22,12 +22,7 @@ * #L% */ import com.condation.cms.templates.loaders.StringTemplateLoader; -import com.condation.cms.templates.tags.ElseIfTag; -import com.condation.cms.templates.tags.ElseTag; -import com.condation.cms.templates.tags.EndIfTag; -import com.condation.cms.templates.tags.IfTag; import com.condation.cms.templates.tags.IncludeTag; -import com.condation.cms.templates.tags.SetTag; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -42,26 +37,25 @@ public class TemplateEngineIncludeTest { @BeforeEach void setupTemplateEngine() { - TemplateConfiguration config = new TemplateConfiguration(); - config - .registerTag(new IncludeTag()); - - config.setTemplateLoader(new StringTemplateLoader() + var templateLoader = new StringTemplateLoader() .add("simple1", """ {% include "temp1" %} """) .add("simple2", """ {% include "nested/temp2" %} + """) + .add("expression", """ + {% set template = "temp1"%} + {% include template %} """) .add("temp1", """ This is from template1 """) .add("nested/temp2", """ This is from template2 - """) - ); + """); - this.templateEngine = new TemplateEngine(config); + this.templateEngine = TemplateEngineBuilder.buildDefault(templateLoader); } @Test @@ -77,4 +71,11 @@ public void test_template2() { Assertions.assertThat(simpleTemplate).isNotNull(); Assertions.assertThat(simpleTemplate.execute()).isEqualToIgnoringWhitespace("This is from template2"); } + + @Test + public void test_expression() { + Template simpleTemplate = templateEngine.getTemplate("expression"); + Assertions.assertThat(simpleTemplate).isNotNull(); + Assertions.assertThat(simpleTemplate.execute()).isEqualToIgnoringWhitespace("This is from template1"); + } } diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineMacroTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineMacroTest.java index e5da7e374..68e4427fe 100644 --- a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineMacroTest.java +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineMacroTest.java @@ -21,15 +21,7 @@ * . * #L% */ - import com.condation.cms.templates.loaders.StringTemplateLoader; -import com.condation.cms.templates.tags.ElseIfTag; -import com.condation.cms.templates.tags.ElseTag; -import com.condation.cms.templates.tags.EndIfTag; -import com.condation.cms.templates.tags.EndMacroTag; -import com.condation.cms.templates.tags.IfTag; -import com.condation.cms.templates.tags.MacroTag; -import com.condation.cms.templates.tags.SetTag; import java.util.Map; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -45,13 +37,7 @@ public class TemplateEngineMacroTest { @BeforeEach void setupTemplateEngine() { - TemplateConfiguration config = new TemplateConfiguration(); - config - .registerTag(new MacroTag()) - .registerTag(new EndMacroTag()) - ; - - config.setTemplateLoader(new StringTemplateLoader() + var loader = new StringTemplateLoader() .add("simple", """ {% macro hello(name) %} Hello {{ name }}! @@ -64,22 +50,55 @@ void setupTemplateEngine() { {% endmacro %} {{ hello(name) }} """) - ); - - this.templateEngine = new TemplateEngine(config); + .add("import", """ + {% import "simple" %} + {{ hello(name) }} + """) + .add("namespace", """ + {% import "simple" as fn %} + {{ fn.hello(name) }} + """) + .add("namespace_expression", """ + {% set template = "simple" %} + {% import template as fn %} + {{ fn.hello(name) }} + """); + + this.templateEngine = TemplateEngineBuilder.buildDefault(loader); } - + @Test public void test_simple() { Template simpleTemplate = templateEngine.getTemplate("simple"); Assertions.assertThat(simpleTemplate).isNotNull(); Assertions.assertThat(simpleTemplate.execute()).isEqualToIgnoringWhitespace("Hello CondationCMS!"); } - + @Test public void test_param() { Template simpleTemplate = templateEngine.getTemplate("param"); Assertions.assertThat(simpleTemplate).isNotNull(); Assertions.assertThat(simpleTemplate.execute(Map.of("name", "Developer"))).isEqualToIgnoringWhitespace("Hello Developer!"); } + + @Test + public void test_import() { + Template simpleTemplate = templateEngine.getTemplate("import"); + Assertions.assertThat(simpleTemplate).isNotNull(); + Assertions.assertThat(simpleTemplate.execute(Map.of("name", "CondationCMS"))).isEqualToIgnoringWhitespace("Hello CondationCMS!"); + } + + @Test + public void test_namespace() { + Template simpleTemplate = templateEngine.getTemplate("namespace"); + Assertions.assertThat(simpleTemplate).isNotNull(); + Assertions.assertThat(simpleTemplate.execute(Map.of("name", "CondationCMS"))).isEqualToIgnoringWhitespace("Hello CondationCMS!"); + } + + @Test + public void test_namespace_expression() { + Template simpleTemplate = templateEngine.getTemplate("namespace_expression"); + Assertions.assertThat(simpleTemplate).isNotNull(); + Assertions.assertThat(simpleTemplate.execute(Map.of("name", "CondationCMS"))).isEqualToIgnoringWhitespace("Hello CondationCMS!"); + } } From 351e5469a0fe6a399b668faa6439f6fa5eb220a0 Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Fri, 29 Nov 2024 12:58:16 +0100 Subject: [PATCH 20/43] fix test --- .../cms/templates/TemplateEngineTest.java | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineTest.java b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineTest.java index fe711ff82..08fbb7bac 100644 --- a/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineTest.java +++ b/cms-sandbox/templates/src/test/java/com/condation/cms/templates/TemplateEngineTest.java @@ -45,21 +45,13 @@ public class TemplateEngineTest { @BeforeEach void setupTemplateEngine() { - TemplateConfiguration config = new TemplateConfiguration(); - config - .registerTag(new IfTag()) - .registerTag(new ElseIfTag()) - .registerTag(new ElseTag()) - .registerTag(new EndIfTag()); - - config.setTemplateLoader(new StringTemplateLoader() + var loader = new StringTemplateLoader() .add("simple", "Hallo {{ name }}") .add("map", "Hallo {{ person.name }}") .add("text", "{{ content }}") - .add("text_raw", "{{ content | raw }}") - ); + .add("text_raw", "{{ content | raw }}"); - this.templateEngine = new TemplateEngine(config); + this.templateEngine = TemplateEngineBuilder.buildDefault(loader); } @RepeatedTest(5) From 386380c99c566eac2387921def12eadf80bff3ba Mon Sep 17 00:00:00 2001 From: Thorsten Marx Date: Fri, 29 Nov 2024 16:10:48 +0100 Subject: [PATCH 21/43] file loader --- .../cms/core/cache/LocalCacheProvider.java | 1 - cms-sandbox/templates/pom.xml | 10 ++++ .../cms/templates/TemplateCache.java | 53 +++++++++++++++++++ .../cms/templates/TemplateConfiguration.java | 11 ++++ .../cms/templates/TemplateEngine.java | 22 ++++++-- .../cms/templates/TemplateEngineBuilder.java | 8 +++ .../exceptions/TemplateNotFoundException.java | 40 ++++++++++++++ .../templates/loaders/FileTemplateLoader.java | 28 ++++++++++ .../templates/AbstractTemplateEngineTest.java | 50 +++++++++++++++++ .../cms/templates/TemplateEngineFORTest.java | 35 +++--------- .../cms/templates/TemplateEngineIFTest.java | 28 +++------- .../templates/TemplateEngineIncludeTest.java | 18 +++---- .../templates/TemplateEngineMacroTest.java | 21 ++++---- .../cms/templates/TemplateEngineSetTest.java | 26 +++------ .../cms/templates/TemplateEngineTest.java | 19 +++---- .../cms/templates/TemplateFeatureTest.java | 7 ++- 16 files changed, 268 insertions(+), 109 deletions(-) create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateCache.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/exceptions/TemplateNotFoundException.java create mode 100644 cms-sandbox/templates/src/main/java/com/condation/cms/templates/loaders/FileTemplateLoader.java create mode 100644 cms-sandbox/templates/src/test/java/com/condation/cms/templates/AbstractTemplateEngineTest.java diff --git a/cms-core/src/main/java/com/condation/cms/core/cache/LocalCacheProvider.java b/cms-core/src/main/java/com/condation/cms/core/cache/LocalCacheProvider.java index 8c8f2f49c..883fbcb84 100644 --- a/cms-core/src/main/java/com/condation/cms/core/cache/LocalCacheProvider.java +++ b/cms-core/src/main/java/com/condation/cms/core/cache/LocalCacheProvider.java @@ -26,7 +26,6 @@ import com.condation.cms.api.cache.CacheManager; import com.condation.cms.api.cache.CacheProvider; import com.condation.cms.api.cache.ICache; -import java.io.Serializable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Function; diff --git a/cms-sandbox/templates/pom.xml b/cms-sandbox/templates/pom.xml index 54575f89d..dee5aea1f 100644 --- a/cms-sandbox/templates/pom.xml +++ b/cms-sandbox/templates/pom.xml @@ -20,6 +20,16 @@ + + com.condation.cms + cms-api + + + com.condation.cms + cms-core + test + + org.apache.commons commons-jexl3 diff --git a/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateCache.java b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateCache.java new file mode 100644 index 000000000..9fe0a25c3 --- /dev/null +++ b/cms-sandbox/templates/src/main/java/com/condation/cms/templates/TemplateCache.java @@ -0,0 +1,53 @@ +package com.condation.cms.templates; + +/*- + * #%L + * templates + * %% + * Copyright (C) 2023 - 2024 CondationCMS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * . + * #L% + */ + +import com.condation.cms.api.cache.ICache; +import java.util.Optional; +import lombok.RequiredArgsConstructor; + +/** + * + * @author t.marx + */ +@RequiredArgsConstructor +public class TemplateCache { + + private final ICache templates; + + protected void invalidate () { + templates.invalidate(); + } + + public Optional