diff --git a/cms-api/src/main/java/com/condation/cms/api/configuration/ConfigurationManagement.java b/cms-api/src/main/java/com/condation/cms/api/configuration/ConfigurationManagement.java
index 898424d29..edab3e92a 100644
--- a/cms-api/src/main/java/com/condation/cms/api/configuration/ConfigurationManagement.java
+++ b/cms-api/src/main/java/com/condation/cms/api/configuration/ConfigurationManagement.java
@@ -114,7 +114,6 @@ private List getConfigurations () {
public void update(CronJobContext jobContext) {
- System.out.println("update");
log.trace("check for modified configurations {}", db.getFileSystem().resolve(".").toString());
getConfigurations().forEach(config -> {
try {
diff --git a/cms-auth/src/main/java/com/condation/cms/auth/AuthShortCodes.java b/cms-auth/src/main/java/com/condation/cms/auth/AuthShortCodes.java
index 616792e7a..7f14f8e12 100644
--- a/cms-auth/src/main/java/com/condation/cms/auth/AuthShortCodes.java
+++ b/cms-auth/src/main/java/com/condation/cms/auth/AuthShortCodes.java
@@ -41,7 +41,8 @@ public class AuthShortCodes extends RegisterShortCodesExtensionPoint {
@Override
public Map> shortCodes() {
return Map.of(
- "username", this::getUserName
+ "username", this::getUserName,
+ "cms:username", this::getUserName
);
}
diff --git a/cms-content/pom.xml b/cms-content/pom.xml
index bb28508d1..c18e9e518 100644
--- a/cms-content/pom.xml
+++ b/cms-content/pom.xml
@@ -35,6 +35,10 @@
org.jsoup
jsoup
+
+ org.apache.commons
+ commons-jexl3
+
org.freemarker
freemarker
diff --git a/cms-content/src/main/java/com/condation/cms/content/DefaultContentRenderer.java b/cms-content/src/main/java/com/condation/cms/content/DefaultContentRenderer.java
index e40c7a57d..d0e22bf58 100644
--- a/cms-content/src/main/java/com/condation/cms/content/DefaultContentRenderer.java
+++ b/cms-content/src/main/java/com/condation/cms/content/DefaultContentRenderer.java
@@ -120,15 +120,7 @@ public String renderView(final ReadOnlyFile viewFile, final View view, final Con
});
}
- private Object getFeatureValueOrDefault(RequestContext context,
- Class feature, Function valueFunction, Object defaultValue) {
- if (context.has(feature)) {
- return valueFunction.apply(context.get(feature));
- }
- return defaultValue;
- }
-
- private String renderMarkdown(final String rawContent, final RequestContext context, final TemplateEngine.Model model) {
+ private String renderContent(final String rawContent, final RequestContext context, final TemplateEngine.Model model) {
var pipeline = ContentPipelineFactory.create(context, model);
return pipeline.process(rawContent);
@@ -190,7 +182,7 @@ public String render(final ReadOnlyFile contentFile, final RequestContext contex
extendModel(model);
model.values.put("content",
- renderMarkdown(rawContent, context, model)
+ renderContent(rawContent, context, model)
);
return templates.get().render((String) meta.get("template"), model);
diff --git a/cms-content/src/main/java/com/condation/cms/content/markdown/rules/block/ShortCodeBlockRule.java b/cms-content/src/main/java/com/condation/cms/content/markdown/rules/block/ShortCodeBlockRule.java
index 2f1502632..eaab38343 100644
--- a/cms-content/src/main/java/com/condation/cms/content/markdown/rules/block/ShortCodeBlockRule.java
+++ b/cms-content/src/main/java/com/condation/cms/content/markdown/rules/block/ShortCodeBlockRule.java
@@ -21,14 +21,12 @@
* .
* #L%
*/
-
-
import com.condation.cms.content.markdown.Block;
import com.condation.cms.content.markdown.BlockElementRule;
import com.condation.cms.content.markdown.InlineRenderer;
-import com.condation.cms.content.shortcodes.ShortCodeParser;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import com.condation.cms.content.shortcodes.TagMap;
+import com.condation.cms.content.shortcodes.TagParser;
+import java.util.List;
/**
*
@@ -36,55 +34,77 @@
*/
public class ShortCodeBlockRule implements BlockElementRule {
- public static final Pattern TAG_PARAMS_PATTERN_SHORT = Pattern.compile("^(\\[{2})(?[a-z_A-Z0-9]+)( (?.*?))?\\p{Blank}*/\\]{2}",
- Pattern.MULTILINE | Pattern.DOTALL | Pattern.UNIX_LINES);
- public static final Pattern TAG_PARAMS_PATTERN_LONG = Pattern.compile("^(\\[{2})(?[a-z_A-Z0-9]+)( (?.*?))?\\]{2}(?.*)\\[{2}/\\k\\]{2}",
- Pattern.MULTILINE | Pattern.DOTALL | Pattern.UNIX_LINES);
-
- public static final Pattern SHORTCODE_PATTERN = Pattern.compile("^" + ShortCodeParser.SHORTCODE_REGEX,
- Pattern.DOTALL | Pattern.MULTILINE);
-
+ private static final TagParser tagParser = new TagParser(null);
+
@Override
public Block next(final String md) {
- /*
- Matcher matcher = TAG_PARAMS_PATTERN_SHORT.matcher(md);
- if (matcher.find()) {
- return new ShortCodeBlock(matcher.start(), matcher.end(),
- matcher.group("tag"), matcher.group("params"), ""
- );
- }
- matcher = TAG_PARAMS_PATTERN_LONG.matcher(md);
- if (matcher.find()) {
- return new ShortCodeBlock(matcher.start(), matcher.end(),
- matcher.group("tag"), matcher.group("params"), matcher.group("content")
- );
- }
- */
- Matcher matcher = SHORTCODE_PATTERN.matcher(md);
- if (matcher.matches()) {
- String name = matcher.group(1) != null ? matcher.group(1) : matcher.group(4);
- String params = matcher.group(2) != null ? matcher.group(2).trim() : matcher.group(5).trim();
- String content = matcher.group(3) != null ? matcher.group(3).trim() : "";
- ShortCodeParser.Match match = new ShortCodeParser.Match(name, matcher.start(), matcher.end());
- match.setContent(content);
- match.getParameters().put("content", content);
-
- return new ShortCodeBlock(matcher.start(), matcher.end(), name, params, content);
+ List tags = tagParser.findTags(md, new TagMap() {
+ @Override
+ public boolean has(String codeName) {
+ return true;
+ }
+ }).stream()
+ .filter(tag -> isStandaloneInLine(md, tag))
+ .toList();
+ if (tags.isEmpty()) {
+ return null;
}
-
- return null;
+ var tag = tags.getFirst();
+ return new ShortCodeBlock(
+ tag.startIndex(),
+ tag.endIndex(),
+ tag);
+
}
-
- public static record ShortCodeBlock (int start, int end, String tag, String params, String content) implements Block {
+ public static record ShortCodeBlock(int start, int end, TagParser.TagInfo tagInfo) implements Block {
@Override
public String render(InlineRenderer inlineRenderer) {
- return "[[%s %s]]%s[[/%s]]".formatted(tag, params, content, tag);
+ List params = tagInfo.rawAttributes()
+ .entrySet().stream()
+ .filter(entry -> !entry.getKey().equals("_content"))
+ .sorted((entry1, entry2) -> entry1.getKey().compareTo(entry2.getKey()))
+ .map(entry -> {
+ return "%s=%s".formatted(entry.getKey(), parseValue((String) entry.getValue()));
+ }).toList();
+ return "[[%s %s]]%s[[/%s]]"
+ .formatted(
+ tagInfo.name(),
+ String.join(" ", params),
+ tagInfo.rawAttributes().getOrDefault("_content", ""),
+ tagInfo.name()
+ );
+ }
+ }
+
+ private static Object parseValue(String value) {
+ if (value.matches("\\d+")) {
+ return value;
+ } else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
+ return value;
+ }
+ return "\"" + value + "\"";
+ }
+
+ public boolean isStandaloneInLine(String text, TagParser.TagInfo tag) {
+ var startIndex = tag.startIndex();
+ var endIndex = tag.endIndex();
+ // Prüfe, ob die Indizes gültig sind
+ if (startIndex < 0 || endIndex > text.length() || startIndex >= endIndex) {
+ throw new IllegalArgumentException("Ungültige Indizes");
}
-
-
+
+ // Finde die Position des Textausschnitts
+ String before = text.substring(0, startIndex);
+ String after = text.substring(endIndex);
+
+ // Prüfe, ob vor und nach dem Ausschnitt ein Zeilenumbruch oder nichts steht
+ boolean beforeIsLineBreak = before.isEmpty() || before.endsWith("\n") || before.endsWith("\r\n");
+ boolean afterIsLineBreak = after.isEmpty() || after.startsWith("\n") || after.startsWith("\r\n");
+
+ return beforeIsLineBreak && afterIsLineBreak;
}
-
+
}
diff --git a/cms-content/src/main/java/com/condation/cms/content/markdown/rules/inline/ShortCodeInlineBlockRule.java b/cms-content/src/main/java/com/condation/cms/content/markdown/rules/inline/ShortCodeInlineBlockRule.java
index e09f20a96..62080ce1f 100644
--- a/cms-content/src/main/java/com/condation/cms/content/markdown/rules/inline/ShortCodeInlineBlockRule.java
+++ b/cms-content/src/main/java/com/condation/cms/content/markdown/rules/inline/ShortCodeInlineBlockRule.java
@@ -21,14 +21,11 @@
* .
* #L%
*/
-
-
import com.condation.cms.content.markdown.InlineBlock;
import com.condation.cms.content.markdown.InlineElementRule;
-import com.condation.cms.content.shortcodes.ShortCodeParser;
-import static com.condation.cms.content.shortcodes.ShortCodeParser.PARAM_PATTERN;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import com.condation.cms.content.shortcodes.TagMap;
+import com.condation.cms.content.shortcodes.TagParser;
+import java.util.List;
/**
*
@@ -36,65 +33,55 @@
*/
public class ShortCodeInlineBlockRule implements InlineElementRule {
- public static final Pattern TAG_PARAMS_PATTERN_SHORT = Pattern.compile("(\\[{2})(?[a-z_A-Z0-9]+)( (?.*?))?\\p{Blank}*/\\]{2}",
- Pattern.MULTILINE | Pattern.DOTALL | Pattern.UNIX_LINES);
- public static final Pattern TAG_PARAMS_PATTERN_LONG = Pattern.compile("^(\\[{2})(?[a-z_A-Z0-9]+)( (?.*?))?\\]{2}(?.*)\\[{2}/\\k\\]{2}",
- Pattern.MULTILINE | Pattern.DOTALL | Pattern.UNIX_LINES);
+ private static final TagParser tagParser = new TagParser(null);
@Override
public InlineBlock next(final String md) {
- /*
- Matcher matcher = TAG_PARAMS_PATTERN_SHORT.matcher(md);
- if (matcher.find()) {
- return new ShortCodeInlineBlock(matcher.start(), matcher.end(),
- matcher.group("tag"), matcher.group("params"), ""
- );
- }
- matcher = TAG_PARAMS_PATTERN_LONG.matcher(md);
- if (matcher.find()) {
- return new ShortCodeInlineBlock(matcher.start(), matcher.end(),
- matcher.group("tag"), matcher.group("params"), matcher.group("content")
- );
- }
- */
-
- Matcher matcher = ShortCodeParser.SHORTCODE_PATTERN.matcher(md);
- if (matcher.find()) {
- String name = matcher.group(1) != null ? matcher.group(1) : matcher.group(4);
- String params = matcher.group(2) != null ? matcher.group(2).trim() : matcher.group(5).trim();
- String content = matcher.group(3) != null ? matcher.group(3).trim() : "";
-
- ShortCodeParser.Match match = new ShortCodeParser.Match(name, matcher.start(), matcher.end());
- match.setContent(content);
- match.getParameters().put("content", content);
-
- /*
- Matcher paramMatcher = PARAM_PATTERN.matcher(params);
- while (paramMatcher.find()) {
- String key = paramMatcher.group(1);
- String value = paramMatcher.group(2);
- // Remove the surrounding quotes
- value = value.substring(1, value.length() - 1);
- match.getParameters().put(key, value);
+ List tags = tagParser.findTags(md, new TagMap() {
+ @Override
+ public boolean has(String codeName) {
+ return true;
}
- */
- return new ShortCodeInlineBlock(matcher.start(), matcher.end(), name, params, content);
+ }).stream().toList();
+ if (tags.isEmpty()) {
+ return null;
}
-
- return null;
+ var tag = tags.getFirst();
+ return new ShortCodeInlineBlock(
+ tag.startIndex(),
+ tag.endIndex(),
+ tag);
}
-
- public static record ShortCodeInlineBlock (int start, int end, String tag, String params, String content) implements InlineBlock {
+ public static record ShortCodeInlineBlock(int start, int end, TagParser.TagInfo tagInfo) implements InlineBlock {
@Override
public String render() {
- return "[[%s %s]]%s[[/%s]]".formatted(tag, params, content, tag);
+ List params = tagInfo.rawAttributes()
+ .entrySet().stream()
+ .filter(entry -> !entry.getKey().equals("_content"))
+ .sorted((entry1, entry2) -> entry1.getKey().compareTo(entry2.getKey()))
+ .map(entry -> {
+ return "%s=%s".formatted(entry.getKey(), parseValue((String) entry.getValue()));
+ }).toList();
+ return "[[%s %s]]%s[[/%s]]"
+ .formatted(
+ tagInfo.name(),
+ String.join(" ", params),
+ tagInfo.rawAttributes().getOrDefault("_content", ""),
+ tagInfo.name()
+ );
}
-
-
-
+
+ }
+
+ private static Object parseValue(String value) {
+ if (value.matches("\\d+")) {
+ return value;
+ } else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
+ return value;
+ }
+ return "\"" + value + "\"";
}
-
}
diff --git a/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipeline.java b/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipeline.java
index 852b03346..edfa3066f 100644
--- a/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipeline.java
+++ b/cms-content/src/main/java/com/condation/cms/content/pipeline/ContentPipeline.java
@@ -44,15 +44,14 @@
* @author thmar
*/
@Slf4j
-@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
+@RequiredArgsConstructor
public class ContentPipeline {
private final HookSystem hookSystem;
private final RequestContext requestContext;
-
private final TemplateEngine.Model model;
- void init() {
+ protected void init() {
List pipeline = requestContext.get(ConfigurationFeature.class)
.configuration().get(SiteConfiguration.class)
@@ -78,7 +77,7 @@ private String processMarkdown(FilterContext context) {
}
private String processShortCodes(FilterContext context) {
- return requestContext.get(RenderContext.class).shortCodes().replace(context.value());
+ return requestContext.get(RenderContext.class).shortCodes().replace(context.value(), model.values);
}
private String processTemplate(FilterContext context) {
@@ -90,12 +89,4 @@ private String processTemplate(FilterContext context) {
return context.value();
}
}
-
- private Object getFeatureValueOrDefault(RequestContext context,
- Class feature, Function valueFunction, Object defaultValue) {
- if (context.has(feature)) {
- return valueFunction.apply(context.get(feature));
- }
- return defaultValue;
- }
}
diff --git a/cms-content/src/main/java/com/condation/cms/content/shortcodes/ShortCodeParser.java b/cms-content/src/main/java/com/condation/cms/content/shortcodes/ShortCodeParser.java
index c960a8dc2..be7c57566 100644
--- a/cms-content/src/main/java/com/condation/cms/content/shortcodes/ShortCodeParser.java
+++ b/cms-content/src/main/java/com/condation/cms/content/shortcodes/ShortCodeParser.java
@@ -22,6 +22,7 @@
* #L%
*/
import com.condation.cms.api.model.Parameter;
+import java.lang.reflect.Array;
import java.util.*;
import java.util.function.Function;
import java.util.regex.*;
@@ -29,20 +30,27 @@
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.jexl3.JexlContext;
+import org.apache.commons.jexl3.JexlEngine;
+import org.apache.commons.jexl3.JexlExpression;
+import org.apache.commons.jexl3.MapContext;
@Slf4j
public class ShortCodeParser {
public static final String SHORTCODE_REGEX = "\\[\\[(\\w+)([^\\]]*)\\]\\](.*?)\\[\\[\\/\\1\\]\\]|\\[\\[(\\w+)([^\\]]*)\\s*\\/\\]\\]";
public static final Pattern SHORTCODE_PATTERN = Pattern.compile(SHORTCODE_REGEX, Pattern.DOTALL);
- public static final Pattern PARAM_PATTERN = Pattern.compile("(\\w+)=(\"[^\"]*\"|'[^']*')");
+ //public static final Pattern PARAM_PATTERN = Pattern.compile("(\\w+)=(\"[^\"]*\"|'[^']*')");
+ public static final Pattern PARAM_PATTERN = Pattern.compile("(\\w+)=((\"[^\"]*\"|'[^']*'|\\[[^\\]]*\\]))");
- public static List parseShortcodes(String text) {
+ public ShortCodeParser() {
+ }
+
+ public List parseShortcodes(String text) {
List shortcodes = new ArrayList<>();
Matcher matcher = SHORTCODE_PATTERN.matcher(text);
while (matcher.find()) {
-
String name = matcher.group(1) != null ? matcher.group(1) : matcher.group(4);
String params = matcher.group(2) != null ? matcher.group(2).trim() : matcher.group(5).trim();
String content = matcher.group(3) != null ? matcher.group(3).trim() : "";
@@ -56,8 +64,7 @@ public static List parseShortcodes(String text) {
while (paramMatcher.find()) {
String key = paramMatcher.group(1);
String value = paramMatcher.group(2);
- // Remove the surrounding quotes
- value = value.substring(1, value.length() - 1);
+ value = value.substring(1, value.length() - 1); // Entfernt die Anführungszeichen oder Klammern bei Arrays
match.getParameters().put(key, value);
}
@@ -67,17 +74,16 @@ public static List parseShortcodes(String text) {
return shortcodes;
}
- public static String replace(String content, Codes codes) {
- String newContent = "";
-
+ public String replace(String content, Codes codes) {
+ StringBuilder newContent = new StringBuilder();
int lastPosition = 0;
var matches = parseShortcodes(content);
- for (var match : matches) {
- newContent += content.substring(lastPosition, match.getStart());
+ for (var match : matches) {
+ newContent.append(content, lastPosition, match.getStart());
try {
- newContent += codes.get(match.getName()).apply(match.getParameters());
+ newContent.append(codes.get(match.getName()).apply(match.getParameters()));
} catch (Exception e) {
log.error("error executing shortcode", e);
}
@@ -86,10 +92,10 @@ public static String replace(String content, Codes codes) {
}
if (content.length() > lastPosition) {
- newContent += content.substring(lastPosition);
+ newContent.append(content.substring(lastPosition));
}
- return newContent;
+ return newContent.toString();
}
@RequiredArgsConstructor
diff --git a/cms-content/src/main/java/com/condation/cms/content/shortcodes/ShortCodes.java b/cms-content/src/main/java/com/condation/cms/content/shortcodes/ShortCodes.java
index 0a70b039d..33470aed2 100644
--- a/cms-content/src/main/java/com/condation/cms/content/shortcodes/ShortCodes.java
+++ b/cms-content/src/main/java/com/condation/cms/content/shortcodes/ShortCodes.java
@@ -24,6 +24,7 @@
import com.condation.cms.api.model.Parameter;
+import java.util.Collections;
import java.util.Map;
import java.util.function.Function;
import lombok.RequiredArgsConstructor;
@@ -37,19 +38,25 @@
@RequiredArgsConstructor
public class ShortCodes {
- private final ShortCodeParser.Codes codes;
+ private final TagMap tagMap;
+ private final TagParser parser;
- public ShortCodes (Map> codes) {
- this.codes = new ShortCodeParser.Codes();
- this.codes.addAll(codes);
+ public ShortCodes (Map> codes, TagParser tagParser) {
+ this.parser = tagParser;
+ this.tagMap = new TagMap();
+ this.tagMap.putAll(codes);
}
public String replace (final String content) {
- return ShortCodeParser.replace(content, codes);
+ return replace(content, Collections.emptyMap());
+ }
+
+ public String replace (final String content, Map contextModel) {
+ return parser.parse(content, tagMap, contextModel);
}
public String execute (String name, Map parameters) {
- if (codes.get(name) == null) {
+ if (!tagMap.has(name)) {
return "";
}
try {
@@ -59,7 +66,7 @@ public String execute (String name, Map parameters) {
} else {
params = new Parameter();
}
- return codes.get(name).apply(params);
+ return tagMap.get(name).apply(params);
} catch (Exception e) {
log.error("",e);
}
diff --git a/cms-content/src/main/java/com/condation/cms/content/shortcodes/TagMap.java b/cms-content/src/main/java/com/condation/cms/content/shortcodes/TagMap.java
new file mode 100644
index 000000000..e503f067a
--- /dev/null
+++ b/cms-content/src/main/java/com/condation/cms/content/shortcodes/TagMap.java
@@ -0,0 +1,53 @@
+package com.condation.cms.content.shortcodes;
+
+/*-
+ * #%L
+ * cms-content
+ * %%
+ * 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.model.Parameter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ *
+ * @author t.marx
+ */
+public class TagMap {
+
+ private final Map> tags = new HashMap<>();
+
+ public void put(String codeName, Function function) {
+ tags.put(codeName, function);
+ }
+
+ public void putAll(Map> tags) {
+ this.tags.putAll(tags);
+ }
+
+ public boolean has(String codeName) {
+ return tags.containsKey(codeName);
+ }
+
+ public Function get(String codeName) {
+ return tags.getOrDefault(codeName, (params) -> "");
+ }
+}
diff --git a/cms-content/src/main/java/com/condation/cms/content/shortcodes/TagParser.java b/cms-content/src/main/java/com/condation/cms/content/shortcodes/TagParser.java
new file mode 100644
index 000000000..af357e1e6
--- /dev/null
+++ b/cms-content/src/main/java/com/condation/cms/content/shortcodes/TagParser.java
@@ -0,0 +1,192 @@
+package com.condation.cms.content.shortcodes;
+
+/*-
+ * #%L
+ * cms-content
+ * %%
+ * 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.model.Parameter;
+import org.apache.commons.jexl3.JexlEngine;
+import org.apache.commons.jexl3.MapContext;
+
+import java.util.*;
+import java.util.function.Function;
+
+public class TagParser {
+
+ private final JexlEngine engine;
+
+ public TagParser(JexlEngine engine) {
+ this.engine = engine;
+ }
+
+ // Klasse zur Speicherung der Tag-Informationen
+ public static record TagInfo (String name, Parameter rawAttributes, int startIndex, int endIndex) {}
+
+ // Erster Schritt: Alle Tags ermitteln und deren Positionen sowie Roh-Attribute speichern
+ public List findTags(String text, TagMap tagHandlers) {
+ List tags = new ArrayList<>();
+ int i = 0;
+
+ while (i < text.length()) {
+ if (text.charAt(i) == '[' && i + 1 < text.length() && text.charAt(i + 1) == '[') {
+ int tagStart = i;
+ int endTagIndex = findTagEnd(text, i);
+ if (endTagIndex != -1) {
+ String tagContent = text.substring(i + 2, endTagIndex).trim();
+ boolean isSelfClosing = tagContent.endsWith("/");
+
+ if (isSelfClosing) {
+ tagContent = tagContent.substring(0, tagContent.length() - 1).trim();
+ }
+
+ int spaceIndex = tagContent.indexOf(' ');
+ String tagName = spaceIndex == -1 ? tagContent : tagContent.substring(0, spaceIndex);
+ Parameter rawAttributes = spaceIndex == -1
+ ? new Parameter()
+ : parseRawAttributes(tagContent.substring(spaceIndex + 1));
+
+ int closingTagIndex = -1;
+ if (!isSelfClosing) {
+ closingTagIndex = text.indexOf("[[/" + tagName + "]]", endTagIndex + 2);
+ if (closingTagIndex != -1) {
+ String content = text.substring(endTagIndex + 2, closingTagIndex);
+ rawAttributes.put("_content", content);
+ endTagIndex = closingTagIndex + ("[[/" + tagName + "]]").length() - 2;
+ }
+ }
+
+ if (tagHandlers.has(tagName)) {
+ tags.add(new TagInfo(tagName, rawAttributes, tagStart, endTagIndex + 2));
+ i = endTagIndex + 2; // Zum nächsten Tag springen
+ } else {
+ i++;
+ }
+ } else {
+ i++;
+ }
+ } else {
+ i++;
+ }
+ }
+ return tags;
+ }
+
+ public String parse(String text, TagMap tagHandlers) {
+ return parse(text, tagHandlers, Collections.emptyMap());
+ }
+
+ // Zweiter Schritt: Tags basierend auf den gespeicherten Positionen ersetzen
+ public String parse(String text, TagMap tagHandlers, Map contextModel) {
+ // Erster Schritt: Finde alle Tags
+ List tags = findTags(text, tagHandlers);
+
+ // Zweiter Schritt: Ersetze alle Tags im Text
+ StringBuilder result = new StringBuilder();
+ int lastIndex = 0;
+ for (TagInfo tag : tags) {
+ result.append(text, lastIndex, tag.startIndex); // Unveränderten Teil des Textes hinzufügen
+ Function handler = tagHandlers.get(tag.name);
+
+ // Im zweiten Schritt: Attribute auswerten
+ Parameter evaluatedAttributes = evaluateAttributes(tag.rawAttributes, contextModel);
+
+ result.append(handler.apply(evaluatedAttributes)); // Tag-Ersetzung
+ lastIndex = tag.endIndex; // Aktualisiere den Startpunkt für den nächsten Tag
+ }
+ result.append(text.substring(lastIndex)); // Füge den restlichen Text hinzu
+
+ return result.toString();
+ }
+
+ // Methode zum Finden des Endes eines Tags
+ private int findTagEnd(String text, int startIndex) {
+ for (int i = startIndex; i < text.length() - 1; i++) {
+ if (text.charAt(i) == ']' && text.charAt(i + 1) == ']') {
+ return i;
+ }
+ }
+ return -1; // Kein schließendes ']]' gefunden
+ }
+
+ // Methode zur Attribut-Analyse im ersten Schritt (Rohwerte als Strings speichern)
+ private Parameter parseRawAttributes(String attributesString) {
+ Parameter attributes = new Parameter();
+ StringBuilder key = new StringBuilder();
+ StringBuilder value = new StringBuilder();
+ boolean inQuotes = false;
+ boolean readingKey = true;
+
+ for (int i = 0; i < attributesString.length(); i++) {
+ char c = attributesString.charAt(i);
+ if (c == '"' || c == '\'') {
+ inQuotes = !inQuotes;
+ } else if (!inQuotes && (c == '=' || c == ' ')) {
+ if (readingKey) {
+ readingKey = false;
+ } else {
+ attributes.put(key.toString().trim(), value.toString().trim()); // Rohwert speichern
+ key.setLength(0);
+ value.setLength(0);
+ readingKey = true;
+ }
+ } else {
+ if (readingKey) {
+ key.append(c);
+ } else {
+ value.append(c);
+ }
+ }
+ }
+
+ // Letztes Attribut verarbeiten
+ if (key.length() > 0 && value.length() > 0) {
+ attributes.put(key.toString().trim(), value.toString().trim()); // Rohwert speichern
+ }
+
+ return attributes;
+ }
+
+ // Zweiter Schritt: Attribute auswerten
+ private Parameter evaluateAttributes(Parameter rawAttributes, Map contextModel) {
+ Parameter evaluatedAttributes = new Parameter();
+ for (Map.Entry entry : rawAttributes.entrySet()) {
+ String key = entry.getKey();
+ String rawValue = (String) entry.getValue(); // Rohwert als String
+ evaluatedAttributes.put(key, parseValue(rawValue, contextModel)); // Wert erst jetzt parsen
+ }
+ return evaluatedAttributes;
+ }
+
+ // Methode zur Auswertung von Attributwerten im zweiten Schritt
+ private Object parseValue(String value, Map contextModel) {
+ if (value.matches("\\d+")) {
+ return Integer.valueOf(value);
+ } else if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
+ return Boolean.valueOf(value);
+ } else if (value.startsWith("${") && value.endsWith("}")) {
+ String expressionString = value.substring(2, value.length() - 1);
+
+ var expression = engine.createExpression(expressionString);
+ return expression.evaluate(new MapContext(contextModel));
+ }
+ return value;
+ }
+}
diff --git a/cms-content/src/test/java/com/condation/cms/content/ContentBaseTest.java b/cms-content/src/test/java/com/condation/cms/content/ContentBaseTest.java
new file mode 100644
index 000000000..a86307a41
--- /dev/null
+++ b/cms-content/src/test/java/com/condation/cms/content/ContentBaseTest.java
@@ -0,0 +1,57 @@
+package com.condation.cms.content;
+
+/*-
+ * #%L
+ * cms-content
+ * %%
+ * 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.content.shortcodes.ShortCodeParser;
+import com.condation.cms.content.shortcodes.TagParser;
+import org.apache.commons.jexl3.JexlBuilder;
+
+/**
+ *
+ * @author t.marx
+ */
+public abstract class ContentBaseTest {
+
+ private ShortCodeParser shortCodeParser;
+
+ private TagParser tagParser;
+
+ public TagParser getTagParser () {
+ if (tagParser == null) {
+ tagParser = new TagParser(
+ new JexlBuilder().cache(512).strict(true).silent(false).create()
+ );
+ }
+
+ return tagParser;
+ }
+
+ public ShortCodeParser getShortCodeParser () {
+ if (shortCodeParser == null) {
+ shortCodeParser = new ShortCodeParser(
+ );
+ }
+
+ return shortCodeParser;
+ }
+}
diff --git a/cms-content/src/test/java/com/condation/cms/content/markdown/rules/block/ShortCodeBlockRuleTest.java b/cms-content/src/test/java/com/condation/cms/content/markdown/rules/block/ShortCodeBlockRuleTest.java
index 09058224c..facd66f74 100644
--- a/cms-content/src/test/java/com/condation/cms/content/markdown/rules/block/ShortCodeBlockRuleTest.java
+++ b/cms-content/src/test/java/com/condation/cms/content/markdown/rules/block/ShortCodeBlockRuleTest.java
@@ -24,6 +24,7 @@
import com.condation.cms.content.markdown.Block;
+import java.util.Map;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
@@ -45,13 +46,16 @@ void long_form() {
Assertions.assertThat(next)
.isNotNull()
- .isInstanceOf(ShortCodeBlockRule.ShortCodeBlock.class)
- .asInstanceOf(InstanceOfAssertFactories.type(ShortCodeBlockRule.ShortCodeBlock.class))
- .hasFieldOrPropertyWithValue("tag", "link")
- .hasFieldOrPropertyWithValue("params", "url=\"https://google.de/\"")
- .hasFieldOrPropertyWithValue("content", "Google")
- ;
-
+ .isInstanceOf(ShortCodeBlockRule.ShortCodeBlock.class);
+
+ var tag = (ShortCodeBlockRule.ShortCodeBlock)next;
+ Assertions.assertThat(tag.tagInfo())
+ .hasFieldOrPropertyWithValue("name", "link")
+ .hasFieldOrPropertyWithValue("rawAttributes", Map.of(
+ "url", "https://google.de/",
+ "_content", "Google"
+ ));
+
Assertions.assertThat(next.render((content) -> content)).isEqualTo("[[link url=\"https://google.de/\"]]Google[[/link]]");
}
@@ -64,12 +68,15 @@ void short_form() {
Assertions.assertThat(next)
.isNotNull()
- .isInstanceOf(ShortCodeBlockRule.ShortCodeBlock.class)
- .asInstanceOf(InstanceOfAssertFactories.type(ShortCodeBlockRule.ShortCodeBlock.class))
- .hasFieldOrPropertyWithValue("tag", "link")
- .hasFieldOrPropertyWithValue("params", "url=\"https://google.de/\"")
- ;
-
+ .isInstanceOf(ShortCodeBlockRule.ShortCodeBlock.class);
+
+ var tag = (ShortCodeBlockRule.ShortCodeBlock)next;
+ Assertions.assertThat(tag.tagInfo())
+ .hasFieldOrPropertyWithValue("name", "link")
+ .hasFieldOrPropertyWithValue("rawAttributes", Map.of(
+ "url", "https://google.de/"
+ ));
+
Assertions.assertThat(next.render((content) -> content)).isEqualTo("[[link url=\"https://google.de/\"]][[/link]]");
}
@@ -82,13 +89,19 @@ void test_issue () {
Assertions.assertThat(next)
.isNotNull()
.isInstanceOf(ShortCodeBlockRule.ShortCodeBlock.class)
- .asInstanceOf(InstanceOfAssertFactories.type(ShortCodeBlockRule.ShortCodeBlock.class))
- .hasFieldOrPropertyWithValue("tag", "video")
- .hasFieldOrPropertyWithValue("params", "type=\"youtube\" id=\"y0sF5xhGreA\" title=\"Everybody loves little cats\"")
;
+ var tag = (ShortCodeBlockRule.ShortCodeBlock)next;
+ Assertions.assertThat(tag.tagInfo())
+ .hasFieldOrPropertyWithValue("name", "video")
+ .hasFieldOrPropertyWithValue("rawAttributes", Map.of(
+ "type", "youtube",
+ "id", "y0sF5xhGreA",
+ "title", "Everybody loves little cats"
+ ));
+
Assertions.assertThat(next.render((content) -> content))
- .isEqualTo("[[video type=\"youtube\" id=\"y0sF5xhGreA\" title=\"Everybody loves little cats\"]][[/video]]");
+ .isEqualTo("[[video id=\"y0sF5xhGreA\" title=\"Everybody loves little cats\" type=\"youtube\"]][[/video]]");
}
}
diff --git a/cms-content/src/test/java/com/condation/cms/content/markdown/rules/inline/ShortCodeInlineBlockRuleTest.java b/cms-content/src/test/java/com/condation/cms/content/markdown/rules/inline/ShortCodeInlineBlockRuleTest.java
index 942046ac8..24bc99b31 100644
--- a/cms-content/src/test/java/com/condation/cms/content/markdown/rules/inline/ShortCodeInlineBlockRuleTest.java
+++ b/cms-content/src/test/java/com/condation/cms/content/markdown/rules/inline/ShortCodeInlineBlockRuleTest.java
@@ -25,6 +25,7 @@
import com.condation.cms.content.markdown.InlineBlock;
+import java.util.Map;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;
@@ -46,12 +47,15 @@ void long_form() {
Assertions.assertThat(next)
.isNotNull()
- .isInstanceOf(ShortCodeInlineBlockRule.ShortCodeInlineBlock.class)
- .asInstanceOf(InstanceOfAssertFactories.type(ShortCodeInlineBlockRule.ShortCodeInlineBlock.class))
- .hasFieldOrPropertyWithValue("tag", "link")
- .hasFieldOrPropertyWithValue("params", "url=\"https://google.de/\"")
- .hasFieldOrPropertyWithValue("content", "Google")
- ;
+ .isInstanceOf(ShortCodeInlineBlockRule.ShortCodeInlineBlock.class);
+
+ var tag = (ShortCodeInlineBlockRule.ShortCodeInlineBlock)next;
+ Assertions.assertThat(tag.tagInfo())
+ .hasFieldOrPropertyWithValue("name", "link")
+ .hasFieldOrPropertyWithValue("rawAttributes", Map.of(
+ "url", "https://google.de/",
+ "_content", "Google"
+ ));
Assertions.assertThat(next.render()).isEqualTo("[[link url=\"https://google.de/\"]]Google[[/link]]");
}
@@ -65,12 +69,15 @@ void short_form() {
Assertions.assertThat(next)
.isNotNull()
- .isInstanceOf(ShortCodeInlineBlockRule.ShortCodeInlineBlock.class)
- .asInstanceOf(InstanceOfAssertFactories.type(ShortCodeInlineBlockRule.ShortCodeInlineBlock.class))
- .hasFieldOrPropertyWithValue("tag", "link")
- .hasFieldOrPropertyWithValue("params", "url=\"https://google.de/\"")
- ;
+ .isInstanceOf(ShortCodeInlineBlockRule.ShortCodeInlineBlock.class);
+ var tag = (ShortCodeInlineBlockRule.ShortCodeInlineBlock)next;
+ Assertions.assertThat(tag.tagInfo())
+ .hasFieldOrPropertyWithValue("name", "link")
+ .hasFieldOrPropertyWithValue("rawAttributes", Map.of(
+ "url", "https://google.de/"
+ ));
+
Assertions.assertThat(next.render()).isEqualTo("[[link url=\"https://google.de/\"]][[/link]]");
}
diff --git a/cms-content/src/test/java/com/condation/cms/content/shortcodes/ShortCodeParserReplaceTest.java b/cms-content/src/test/java/com/condation/cms/content/shortcodes/ShortCodeParserReplaceTest.java
index 133d5f070..743b6955b 100644
--- a/cms-content/src/test/java/com/condation/cms/content/shortcodes/ShortCodeParserReplaceTest.java
+++ b/cms-content/src/test/java/com/condation/cms/content/shortcodes/ShortCodeParserReplaceTest.java
@@ -23,20 +23,22 @@
*/
+import com.condation.cms.content.ContentBaseTest;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
*
* @author t.marx
*/
-public class ShortCodeParserReplaceTest {
+public class ShortCodeParserReplaceTest extends ContentBaseTest {
static ShortCodeParser.Codes tags;
- @BeforeAll
- public static void init () {
+ @BeforeEach
+ public void init () {
tags = new ShortCodeParser.Codes();
@@ -61,16 +63,16 @@ public static void init () {
@Test
void simpleTest () {
- var result = ShortCodeParser.replace("[[youtube /]]", tags);
+ var result = getShortCodeParser().replace("[[youtube /]]", tags);
Assertions.assertThat(result).isEqualTo("");
- result = ShortCodeParser.replace("[[youtube/]]", tags);
+ result = getShortCodeParser().replace("[[youtube/]]", tags);
Assertions.assertThat(result).isEqualTo("");
}
@Test
void simple_with_text_before_and_After () {
- var result = ShortCodeParser.replace("before [[youtube /]] after", tags);
+ var result = getShortCodeParser().replace("before [[youtube /]] after", tags);
Assertions.assertThat(result).isEqualTo("before after");
}
@@ -85,7 +87,7 @@ void complexTest () {
some text after
""";
- var result = ShortCodeParser.replace(content, tags);
+ var result = getShortCodeParser().replace(content, tags);
var expected = """
some text before
@@ -100,32 +102,32 @@ void complexTest () {
@Test
void unknown_tag () {
- var result = ShortCodeParser.replace("before [[vimeo id='TEST' /]] after", tags);
+ var result = getShortCodeParser().replace("before [[vimeo id='TEST' /]] after", tags);
Assertions.assertThat(result).isEqualToIgnoringWhitespace("before after");
}
@Test
void hello_from () {
- var result = ShortCodeParser.replace("[[hello_from name='Thorsten' from='Bochum' /]]", tags);
+ var result = getShortCodeParser().replace("[[hello_from name='Thorsten' from='Bochum' /]]", tags);
Assertions.assertThat(result).isEqualTo("Thorsten
from Bochum
");
- result = ShortCodeParser.replace("[[hello_from name='Thorsten' from='Bochum' /]]", tags);
+ result = getShortCodeParser().replace("[[hello_from name='Thorsten' from='Bochum' /]]", tags);
Assertions.assertThat(result).isEqualTo("Thorsten
from Bochum");
- result = ShortCodeParser.replace("[[hello_from name='Thorsten' from='Bochum' /]]", tags);
+ result = getShortCodeParser().replace("[[hello_from name='Thorsten' from='Bochum' /]]", tags);
Assertions.assertThat(result).isEqualTo("Thorsten
from Bochum");
}
@Test
void test_long () {
- var result = ShortCodeParser.replace("[[mark]]Important[[/mark]]", tags);
+ var result = getShortCodeParser().replace("[[mark]]Important[[/mark]]", tags);
Assertions.assertThat(result).isEqualTo("Important");
}
@Test
void test_long_with_params () {
- var result = ShortCodeParser.replace("[[mark2 class='test-class']]Important[[/mark2]]", tags);
+ var result = getShortCodeParser().replace("[[mark2 class='test-class']]Important[[/mark2]]", tags);
Assertions.assertThat(result).isEqualTo("Important");
}
@@ -141,7 +143,7 @@ void long_complex () {
some text after
""";
- var result = ShortCodeParser.replace(content,tags);
+ var result = getShortCodeParser().replace(content,tags);
var expected = """
some text before
@@ -162,7 +164,7 @@ void multiple_hello () {
var expected = """
Thorsten
from BochumThorsten
from Bochum
""";
- var result = ShortCodeParser.replace(input, tags);
+ var result = getShortCodeParser().replace(input, tags);
Assertions.assertThat(result).isEqualTo(expected);
input = """
@@ -171,7 +173,7 @@ void multiple_hello () {
expected = """
Thorsten
from BochumThorsten
from Bochum
""";
- result = ShortCodeParser.replace(input, tags);
+ result = getShortCodeParser().replace(input, tags);
Assertions.assertThat(result).isEqualTo(expected);
}
}
diff --git a/cms-content/src/test/java/com/condation/cms/content/shortcodes/ShortCodeParserTest.java b/cms-content/src/test/java/com/condation/cms/content/shortcodes/ShortCodeParserTest.java
index 157f5d8ac..30ea03983 100644
--- a/cms-content/src/test/java/com/condation/cms/content/shortcodes/ShortCodeParserTest.java
+++ b/cms-content/src/test/java/com/condation/cms/content/shortcodes/ShortCodeParserTest.java
@@ -23,17 +23,18 @@
*/
+import com.condation.cms.content.ContentBaseTest;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
-public class ShortCodeParserTest {
+public class ShortCodeParserTest extends ContentBaseTest {
@Test
public void testParseShortcodes_singleShortcodeWithContent() {
String text = "This is a text with a shortcode [[code1 param1=\"value1\" param2=\"value2\"]]This is content[[/code1]].";
- List shortcodes = ShortCodeParser.parseShortcodes(text);
+ List shortcodes = getShortCodeParser().parseShortcodes(text);
assertEquals(1, shortcodes.size());
@@ -47,7 +48,7 @@ public void testParseShortcodes_singleShortcodeWithContent() {
@Test
public void testParseShortcodes_multipleShortcodes() {
String text = "This is a text with a shortcode [[code1 param1=\"value1\" param2=\"value2\"]]This is content[[/code1]] and another one [[code2 param1=\"value1\" param3=\"value3\" /]].";
- List shortcodes = ShortCodeParser.parseShortcodes(text);
+ List shortcodes = getShortCodeParser().parseShortcodes(text);
assertEquals(2, shortcodes.size());
@@ -67,7 +68,7 @@ public void testParseShortcodes_multipleShortcodes() {
@Test
public void testParseShortcodes_multipleShortcodes2() {
String text = "This is a text with a shortcode [[code1 param1=\"value1\" param2=\"value2\" ]]This is content[[/code1]] and another one [[code2 param1=\"value1\" param3=\"value3\"/]].";
- List shortcodes = ShortCodeParser.parseShortcodes(text);
+ List shortcodes = getShortCodeParser().parseShortcodes(text);
assertEquals(2, shortcodes.size());
@@ -87,7 +88,7 @@ public void testParseShortcodes_multipleShortcodes2() {
@Test
public void testParseShortcodes_noShortcodes() {
String text = "This text has no shortcodes.";
- List shortcodes = ShortCodeParser.parseShortcodes(text);
+ List shortcodes = getShortCodeParser().parseShortcodes(text);
assertEquals(0, shortcodes.size());
}
@@ -95,7 +96,7 @@ public void testParseShortcodes_noShortcodes() {
@Test
public void testParseShortcodes_emptyParameters() {
String text = "This is a text with a shortcode [[code1]][[/code1]] and another one [[code2 /]].";
- List shortcodes = ShortCodeParser.parseShortcodes(text);
+ List shortcodes = getShortCodeParser().parseShortcodes(text);
assertEquals(2, shortcodes.size());
@@ -111,7 +112,7 @@ public void testParseShortcodes_emptyParameters() {
@Test
public void testParseShortcodes_malformedShortcodes() {
String text = "This is a text with a malformed shortcode [[code1 param1=\"value1\" param2=\"value2\" .";
- List shortcodes = ShortCodeParser.parseShortcodes(text);
+ List shortcodes = getShortCodeParser().parseShortcodes(text);
assertEquals(0, shortcodes.size());
}
diff --git a/cms-content/src/test/java/com/condation/cms/content/shortcodes/ShortCodesTest.java b/cms-content/src/test/java/com/condation/cms/content/shortcodes/ShortCodesTest.java
index 2be9200af..33de04a80 100644
--- a/cms-content/src/test/java/com/condation/cms/content/shortcodes/ShortCodesTest.java
+++ b/cms-content/src/test/java/com/condation/cms/content/shortcodes/ShortCodesTest.java
@@ -24,23 +24,24 @@
import com.condation.cms.api.model.Parameter;
+import com.condation.cms.content.ContentBaseTest;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.assertj.core.api.Assertions;
-import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
*
* @author t.marx
*/
-public class ShortCodesTest {
+public class ShortCodesTest extends ContentBaseTest {
static ShortCodes shortCodes;
- @BeforeAll
- public static void init () {
+ @BeforeEach
+ public void init () {
Map> tags = new HashMap<>();
tags.put(
"youtube",
@@ -51,15 +52,20 @@ public static void init () {
tags.put(
"mark",
- params -> "%s".formatted(params.get("content"))
+ params -> "%s".formatted(params.get("_content"))
);
tags.put(
"mark2",
- params -> "%s".formatted(params.get("class"), params.get("content"))
+ params -> "%s".formatted(params.get("class"), params.get("_content"))
);
- shortCodes = new ShortCodes(tags);
+ tags.put(
+ "exp",
+ params -> "%s".formatted(params.get("expression"))
+ );
+
+ shortCodes = new ShortCodes(tags, getTagParser());
}
@@ -105,18 +111,18 @@ void complexTest () {
@Test
void unknown_tag () {
var result = shortCodes.replace("before [[vimeo id='TEST' /]] after");
- Assertions.assertThat(result).isEqualToIgnoringWhitespace("before after");
+ Assertions.assertThat(result).isEqualToIgnoringWhitespace("before [[vimeo id='TEST' /]] after");
}
@Test
void hello_from () {
- var result = shortCodes.replace("[[hello_from name='Thorsten',from='Bochum' /]]");
+ var result = shortCodes.replace("[[hello_from name=\"Thorsten\" from=\"Bochum\" /]]");
Assertions.assertThat(result).isEqualTo("Thorsten
from Bochum");
- result = shortCodes.replace("[[hello_from name='Thorsten',from='Bochum' /]]");
+ result = shortCodes.replace("[[hello_from name='Thorsten' from='Bochum' /]]");
Assertions.assertThat(result).isEqualTo("Thorsten
from Bochum");
- result = shortCodes.replace("[[hello_from name='Thorsten', from='Bochum' /]]");
+ result = shortCodes.replace("[[hello_from name='Thorsten' from='Bochum' /]]");
Assertions.assertThat(result).isEqualTo("Thorsten
from Bochum");
}
@@ -161,7 +167,7 @@ void long_complex () {
@Test
void multiple_hello () {
var input = """
- [[hello_from name='Thorsten',from='Bochum']][[/hello_from]][[hello_from name='Thorsten',from='Bochum']][[/hello_from]]
+ [[hello_from name='Thorsten' from='Bochum']][[/hello_from]][[hello_from name='Thorsten' from='Bochum']][[/hello_from]]
""";
var expected = """
Thorsten
from BochumThorsten
from Bochum
@@ -170,7 +176,7 @@ void multiple_hello () {
Assertions.assertThat(result).isEqualTo(expected);
input = """
- [[hello_from name='Thorsten',from='Bochum'/]][[hello_from name='Thorsten',from='Bochum'/]]
+ [[hello_from name='Thorsten' from='Bochum'/]][[hello_from name='Thorsten' from='Bochum'/]]
""";
expected = """
Thorsten
from BochumThorsten
from Bochum
@@ -178,4 +184,22 @@ void multiple_hello () {
result = shortCodes.replace(input);
Assertions.assertThat(result).isEqualTo(expected);
}
+
+ @Test
+ void test_mismach() {
+ var result = shortCodes.replace("[[mark1 class='test-class']]Important[[/mark2]]");
+
+ Assertions.assertThat(result).isEqualTo("[[mark1 class='test-class']]Important[[/mark2]]");
+ }
+
+ @Test
+ void test_expression() {
+ var result = shortCodes.replace("[[exp expression='${meta.title}' /]]",
+ Map.of(
+ "meta", Map.of("title", "CondationCMS")
+ )
+ );
+
+ Assertions.assertThat(result).isEqualTo("CondationCMS");
+ }
}
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
new file mode 100644
index 000000000..4ebb118c1
--- /dev/null
+++ b/cms-content/src/test/java/com/condation/cms/content/shortcodes/TagParserTest.java
@@ -0,0 +1,146 @@
+package com.condation.cms.content.shortcodes;
+
+/*-
+ * #%L
+ * cms-content
+ * %%
+ * 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.jexl3.JexlBuilder;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.BeforeEach;
+
+/**
+ *
+ * @author t.marx
+ */
+public class TagParserTest {
+
+ TagParser tagParser;
+
+ TagMap tagMap;
+
+ @BeforeEach
+ void setup() {
+ tagMap = new TagMap();
+ tagMap.put("code", params -> {
+ // Verarbeitung der Parameter hier
+ return "Ausgabe des Shortcodes";
+ });
+ tagMap.put("content", params -> {
+ return (String)params.get("_content");
+ });
+
+ tagMap.put("exp", params -> {
+ return "expression: " + params.get("value");
+ });
+
+ tagMap.put("param", params -> {
+ return "param: " + params.get("param1");
+ });
+
+ tagMap.put("ns1:print", params -> {
+ return "message: " + params.get("message");
+ });
+
+ this.tagParser = new TagParser(new JexlBuilder().create());
+ }
+
+ @Test
+ public void no_shortcode() {
+ String result = tagParser.parse("Dein Shortcode-Text hier", tagMap);
+ Assertions.assertThat(result).isEqualTo("Dein Shortcode-Text hier");
+ }
+
+ @Test
+ public void self_closing_tag() {
+ String result = tagParser.parse("[[code/]]", tagMap);
+ Assertions.assertThat(result).isEqualTo("Ausgabe des Shortcodes");
+ }
+
+ @Test
+ public void self_closing_tag_with_space() {
+ String result = tagParser.parse("[[code /]]", tagMap);
+ Assertions.assertThat(result).isEqualTo("Ausgabe des Shortcodes");
+ }
+
+ @Test
+ public void end_closing_tag() {
+ String result = tagParser.parse("[[code]][[/code]]", tagMap);
+ Assertions.assertThat(result).isEqualTo("Ausgabe des Shortcodes");
+ }
+
+ @Test
+ public void tag_with_content() {
+ String result = tagParser.parse("[[content]]Hello CondationCMS[[/content]]", tagMap);
+ Assertions.assertThat(result).isEqualTo("Hello CondationCMS");
+ }
+
+ @Test
+ public void expressions() {
+ String result = tagParser.parse("[[exp value=\"${5+4}\"/]]", tagMap);
+ Assertions.assertThat(result).isEqualTo("expression: 9");
+ }
+
+ @Test
+ public void parameters_string() {
+ String result = tagParser.parse("[[param param1=\"5\"/]]", tagMap);
+ Assertions.assertThat(result).isEqualTo("param: 5");
+ }
+
+ @Test
+ public void parameters_number() {
+ String result = tagParser.parse("[[param param1=5 /]]", tagMap);
+ Assertions.assertThat(result).isEqualTo("param: 5");
+ }
+
+ @Test
+ public void parameters_boolean_true() {
+ String result = tagParser.parse("[[param param1=true /]]", tagMap);
+ Assertions.assertThat(result).isEqualTo("param: true");
+ }
+
+ @Test
+ public void parameters_boolean_false() {
+ String result = tagParser.parse("[[param param1=false /]]", tagMap);
+ Assertions.assertThat(result).isEqualTo("param: false");
+ }
+
+ @Test
+ public void parameters_with_content() {
+ String result = tagParser.parse("[[param param1=\"5\"]]Hello[[/param]]", tagMap);
+ Assertions.assertThat(result).isEqualTo("param: 5");
+ }
+
+ @Test
+ public void shortCode_in_text() {
+ String result = tagParser.parse("Hello [[content]]CondationCMS[[/content]]!", tagMap);
+ Assertions.assertThat(result).isEqualTo("Hello CondationCMS!");
+ }
+
+ @Test
+ public void namespace() {
+ String result = tagParser.parse("[[ns1:print message='Hello CondationCMS']][[/ns1:print]]", tagMap);
+ Assertions.assertThat(result).isEqualTo("message: Hello CondationCMS");
+
+ result = tagParser.parse("[[ns1:print message='Hello CondationCMS' /]]", tagMap);
+ Assertions.assertThat(result).isEqualTo("message: Hello CondationCMS");
+ }
+}
diff --git a/cms-content/src/test/java/com/condation/cms/content/template/shortcode/FreeMarkerShortCodeTemplateFunctionTest.java b/cms-content/src/test/java/com/condation/cms/content/template/shortcode/FreeMarkerShortCodeTemplateFunctionTest.java
index 18bf04e1e..5a47cf8c6 100644
--- a/cms-content/src/test/java/com/condation/cms/content/template/shortcode/FreeMarkerShortCodeTemplateFunctionTest.java
+++ b/cms-content/src/test/java/com/condation/cms/content/template/shortcode/FreeMarkerShortCodeTemplateFunctionTest.java
@@ -23,6 +23,7 @@
*/
+import com.condation.cms.content.ContentBaseTest;
import com.condation.cms.content.template.functions.shortcode.ShortCodeTemplateFunction;
import com.condation.cms.content.shortcodes.ShortCodes;
import freemarker.template.Configuration;
@@ -34,29 +35,33 @@
import java.util.Map;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
*
* @author t.marx
*/
-public class FreeMarkerShortCodeTemplateFunctionTest {
+public class FreeMarkerShortCodeTemplateFunctionTest extends ContentBaseTest {
static Configuration cfg;
- static ShortCodes shortCodes;
+ ShortCodes shortCodes;
@BeforeAll
public static void setup() {
cfg = new Configuration(Configuration.VERSION_2_3_33);
cfg.setDefaultEncoding("UTF-8");
-
+ }
+
+ @BeforeEach
+ public void setupShortCodes() {
shortCodes = new ShortCodes(Map.of(
"echo", (params) -> "Hello world",
"greet", (params) -> "Hello " + params.get("name")
- ));
+ ), getTagParser());
}
-
+
@Test
public void testSomeMethod() throws Exception {
String templateString = "${shortCode.call('echo')}";
diff --git a/cms-content/src/test/java/com/condation/cms/content/template/shortcode/PebbleShortCodeTemplateFunctionTest.java b/cms-content/src/test/java/com/condation/cms/content/template/shortcode/PebbleShortCodeTemplateFunctionTest.java
index d0c291b73..250c856f7 100644
--- a/cms-content/src/test/java/com/condation/cms/content/template/shortcode/PebbleShortCodeTemplateFunctionTest.java
+++ b/cms-content/src/test/java/com/condation/cms/content/template/shortcode/PebbleShortCodeTemplateFunctionTest.java
@@ -23,6 +23,7 @@
*/
+import com.condation.cms.content.ContentBaseTest;
import com.condation.cms.content.template.functions.shortcode.ShortCodeTemplateFunction;
import com.condation.cms.content.shortcodes.ShortCodes;
import io.pebbletemplates.pebble.PebbleEngine;
@@ -34,15 +35,16 @@
import java.util.Map;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
*
* @author t.marx
*/
-public class PebbleShortCodeTemplateFunctionTest {
+public class PebbleShortCodeTemplateFunctionTest extends ContentBaseTest {
- static ShortCodes shortCodes;
+ ShortCodes shortCodes;
static PebbleEngine engine;
@@ -51,11 +53,13 @@ public static void setup() {
engine = new PebbleEngine.Builder()
.loader(new StringLoader())
.build();
-
+ }
+ @BeforeEach
+ public void setupShortCodes() {
shortCodes = new ShortCodes(Map.of(
"echo", (params) -> "Hello world",
"greet", (params) -> "Hello " + params.get("name")
- ));
+ ), getTagParser());
}
@Test
diff --git a/cms-content/src/test/java/com/condation/cms/content/template/shortcode/ThymeleafShortCodeTemplateFunctionTest.java b/cms-content/src/test/java/com/condation/cms/content/template/shortcode/ThymeleafShortCodeTemplateFunctionTest.java
index 34b5e4ea0..31f902ae1 100644
--- a/cms-content/src/test/java/com/condation/cms/content/template/shortcode/ThymeleafShortCodeTemplateFunctionTest.java
+++ b/cms-content/src/test/java/com/condation/cms/content/template/shortcode/ThymeleafShortCodeTemplateFunctionTest.java
@@ -23,11 +23,13 @@
*/
+import com.condation.cms.content.ContentBaseTest;
import com.condation.cms.content.template.functions.shortcode.ShortCodeTemplateFunction;
import com.condation.cms.content.shortcodes.ShortCodes;
import java.util.Map;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
@@ -37,9 +39,9 @@
*
* @author t.marx
*/
-public class ThymeleafShortCodeTemplateFunctionTest {
+public class ThymeleafShortCodeTemplateFunctionTest extends ContentBaseTest {
- static ShortCodes shortCodes;
+ ShortCodes shortCodes;
static TemplateEngine templateEngine;
@@ -51,11 +53,14 @@ public static void setup() {
templateEngine = new TemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
-
+ }
+
+ @BeforeEach
+ public void setupShortCodes() {
shortCodes = new ShortCodes(Map.of(
"echo", (params) -> "Hello world",
"greet", (params) -> "Hello " + params.get("name")
- ));
+ ), getTagParser());
}
@Test
diff --git a/cms-content/src/test/resources/com/condation/cms/content/markdown/features.shortcodes.html b/cms-content/src/test/resources/com/condation/cms/content/markdown/features.shortcodes.html
index 275a6b8e4..e780cd780 100644
--- a/cms-content/src/test/resources/com/condation/cms/content/markdown/features.shortcodes.html
+++ b/cms-content/src/test/resources/com/condation/cms/content/markdown/features.shortcodes.html
@@ -22,7 +22,11 @@
task list test
a paragraph with [[link id="google" title="Google"]][[/link]]
-
next
-
[[link id="apache" title="Apache"]][[/link]]
+
+ [[link id="apache" title="Apache"]][[/link]]
+
+
with namespace
+
+ [[ns:link id="apache" title="Apache"]][[/ns:link]]
diff --git a/cms-content/src/test/resources/com/condation/cms/content/markdown/features.shortcodes.md b/cms-content/src/test/resources/com/condation/cms/content/markdown/features.shortcodes.md
index abc97612b..345653e15 100644
--- a/cms-content/src/test/resources/com/condation/cms/content/markdown/features.shortcodes.md
+++ b/cms-content/src/test/resources/com/condation/cms/content/markdown/features.shortcodes.md
@@ -4,4 +4,8 @@ a paragraph with [[link id="google" title="Google" /]]
# next
-[[link id="apache" title="Apache" /]]
\ No newline at end of file
+[[link id="apache" title="Apache" /]]
+
+# with namespace
+
+[[ns:link id="apache" title="Apache" /]]
\ No newline at end of file
diff --git a/cms-sandbox/pom.xml b/cms-sandbox/pom.xml
index 9ee1fb765..c65a99fa9 100644
--- a/cms-sandbox/pom.xml
+++ b/cms-sandbox/pom.xml
@@ -9,6 +9,6 @@
cms-sandbox
pom
-
+ tests
\ No newline at end of file
diff --git a/cms-sandbox/tests/pom.xml b/cms-sandbox/tests/pom.xml
new file mode 100644
index 000000000..ac3f80325
--- /dev/null
+++ b/cms-sandbox/tests/pom.xml
@@ -0,0 +1,54 @@
+
+
+ 4.0.0
+
+ com.condation.cms
+ cms-sandbox
+ 6.4.0
+
+ tests
+ jar
+
+ com.condation.cms.tests.Tests
+
+
+
+
+ org.apache.commons
+ commons-jexl3
+
+
+ org.antlr
+ antlr4-runtime
+ 4.13.2
+
+
+ org.projectlombok
+ lombok
+ provided
+
+
+
+
+
+
+ org.antlr
+ antlr4-maven-plugin
+ 4.13.2
+
+ src/main/resources
+ target/generated-sources/antlr4
+
+
+
+
+ antlr4
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cms-sandbox/tests/src/main/java/com/condation/cms/content/shortcodes/ShortCodes.java b/cms-sandbox/tests/src/main/java/com/condation/cms/content/shortcodes/ShortCodes.java
new file mode 100644
index 000000000..612ac81bf
--- /dev/null
+++ b/cms-sandbox/tests/src/main/java/com/condation/cms/content/shortcodes/ShortCodes.java
@@ -0,0 +1,169 @@
+package com.condation.cms.content.shortcodes;
+
+/*-
+ * #%L
+ * tests
+ * %%
+ * 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.antlr.v4.runtime.*;
+import org.antlr.v4.runtime.tree.*;
+import org.apache.commons.jexl3.*;
+import java.util.*;
+import java.util.function.Function;
+
+public class ShortCodes {
+
+ private static final JexlEngine jexl = new JexlBuilder().create(); // Initialisierung des JEXL-Engines
+
+ public static String parseShortcodes(String text, Codes codes) {
+ // ANTLR Setup
+ CharStream input = CharStreams.fromString(text);
+ ShortCodeLexer lexer = new ShortCodeLexer(input);
+ CommonTokenStream tokens = new CommonTokenStream(lexer);
+ ShortCodeParser parser = new ShortCodeParser(tokens);
+ ParseTree tree = parser.shortcodes(); // Parse the input
+
+ // Listener Setup
+ ShortCodeListenerImpl listener = new ShortCodeListenerImpl(codes);
+ ParseTreeWalker walker = new ParseTreeWalker();
+ walker.walk(listener, tree); // Walk the tree using the listener
+
+ // Return the modified text after processing all shortcodes
+ return listener.getResult();
+ }
+
+ // Listener class to process shortcodes
+ public static class ShortCodeListenerImpl extends ShortCodeParserBaseListener {
+ private final Codes codes;
+ private final StringBuilder result = new StringBuilder(); // Stores the final output
+
+ public ShortCodeListenerImpl(Codes codes) {
+ this.codes = codes;
+ }
+
+ @Override
+ public void enterText(ShortCodeParser.TextContext ctx) {
+ result.append(ctx.getText());
+ }
+
+// @Override
+// public void enterSelfClosingTag(ShortCodeParser.SelfClosingTagContext ctx) {
+// String name = ctx.TAG_NAME().getText();
+// Map parameters = parseParams(ctx.params());
+//
+// // Apply shortcode function if exists
+// if (codes.hasCode(name)) {
+// result.append(codes.get(name).apply(parameters));
+// } else {
+// result.append(ctx.getText()); // No shortcode function found, append raw text
+// }
+// }
+
+ @Override
+ public void enterShortcodeWithContent(ShortCodeParser.ShortcodeWithContentContext ctx) {
+ System.out.println("enterShortcodeWithContent: " + ctx.openingTag().TAG_NAME());
+
+ String name = ctx.openingTag().TAG_NAME().getText();
+ Map parameters = parseParams(ctx.openingTag().params());
+ String content = ctx.content() != null ? ctx.content().getText() : "";
+
+ // Apply shortcode function if exists
+ if (codes.hasCode(name)) {
+ parameters.put("content", content); // Pass content as parameter
+ result.append(codes.get(name).apply(parameters));
+ } else {
+ result.append(ctx.getText()); // No shortcode function found, append raw text
+ }
+ }
+
+ @Override
+ public void enterSelfClosingShortcode(ShortCodeParser.SelfClosingShortcodeContext ctx) {
+ String name = ctx.selfClosingTag().TAG_NAME().getText();
+ Map parameters = parseParams(ctx.selfClosingTag().params());
+
+ // Apply shortcode function if exists
+ if (codes.hasCode(name)) {
+ result.append(codes.get(name).apply(parameters));
+ } else {
+ result.append(ctx.getText()); // No shortcode function found, append raw text
+ }
+ }
+
+ // Methode, um Parameter zu extrahieren
+ private Map parseParams(ShortCodeParser.ParamsContext ctx) {
+ Map params = new HashMap<>();
+ if (ctx != null) {
+ for (ShortCodeParser.ParamContext paramCtx : ctx.param()) {
+ String key = paramCtx.TAG_NAME().getText();
+ String rawValue = paramCtx.value().getText();
+ Object value = evaluateIfExpression(rawValue);
+ params.put(key, value);
+ }
+ }
+ return params;
+ }
+
+ // Methode, um Ausdrücke innerhalb von ${} zu erkennen und mit JEXL auszuwerten
+ private Object evaluateIfExpression(String rawValue) {
+ // Prüfen, ob der Wert im Format ${expression} vorliegt
+ var testValue = rawValue.replace("\"", "");
+ if (testValue.startsWith("${") && testValue.endsWith("}")) {
+ String expression = testValue.substring(2, testValue.length() - 1);
+ return evaluateExpression(expression);
+ } else {
+ // Normaler Text
+ return testValue;// Entfernt eventuell vorhandene Anführungszeichen
+ }
+ }
+
+ // Methode, um einen JEXL-Ausdruck auszuwerten
+ private Object evaluateExpression(String expression) {
+ try {
+ JexlExpression jexlExpression = jexl.createExpression(expression);
+ JexlContext context = new MapContext(); // Leerer Kontext für einfache Auswertung
+ return jexlExpression.evaluate(context); // Ausdruck auswerten
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null; // Wenn die Auswertung fehlschlägt, gib null zurück
+ }
+ }
+
+ public String getResult() {
+ return result.toString(); // Gibt das finale Ergebnis nach dem Parsen zurück
+ }
+ }
+
+ // Codes class to store shortcode functions
+ public static class Codes {
+ private final Map, String>> codes = new HashMap<>();
+
+ public void add(String codeName, Function