
org.rapidoid.render.TemplateParser Maven / Gradle / Ivy
package org.rapidoid.render;
import org.rapidoid.RapidoidThing;
import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.u.U;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/*
* #%L
* rapidoid-render
* %%
* Copyright (C) 2014 - 2016 Nikolche Mihajlovski and contributors
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
@Authors("Nikolche Mihajlovski")
@Since("5.1.0")
public class TemplateParser extends RapidoidThing {
private static final String NL = "(\\r?\\n?)";
private static final String SP = "([\\t ]*)";
static final String EXPR = "(\\$|@)\\{([^\\}]+?)\\}";
static final String STAT = NL + SP + "\\{\\{([\\^!#\\?/>][^\\}]+?)\\}\\}" + SP + NL;
static final String TAG = "?\\s*(\\w+(-\\w+)*)(\\s+\\w+=\"[^\"]+?\")*?\\s*/?>";
static final Pattern TOKENIZER = Pattern.compile("(?s)(.*?)(?:" + U.join("|", EXPR, STAT) + ")");
public static XNode parse(String s) {
U.notNull(s, "template source");
Stack stack = new Stack();
XNode root = new XNode(XNode.OP.OP_ROOT, null);
stack.push(root);
Matcher m = TOKENIZER.matcher(s);
int pos = 0;
while (m.find()) {
String whole = m.group(0);
String g1 = m.group(1);
if (!U.isEmpty(g1)) {
addText(stack, g1);
}
String g2 = m.group(2);
String g4 = m.group(6);
if (!U.isEmpty(g2)) {
char prefix = g2.charAt(0);
String txt = m.group(3);
expression(stack, prefix, txt, whole);
} else if (!U.isEmpty(g4)) {
char prefix = g4.charAt(0);
String txt = g4.substring(1);
String nl1 = m.group(4);
assert nl1.matches(NL);
String sp1 = m.group(5);
assert sp1.matches(SP);
String sp2 = m.group(7);
assert sp2.matches(SP);
String nl2 = m.group(8);
assert nl2.matches(NL);
if (prefix == XNode.OP.OP_INCLUDE.code) {
addText(stack, nl1);
addText(stack, sp1);
expression(stack, prefix, txt, whole);
addText(stack, sp2);
addText(stack, nl2);
} else {
boolean hasNl1 = !nl1.isEmpty();
if (!hasNl1) {
if (m.start() > 0) {
char ch = s.charAt(m.start() - 1);
if (ch == 10 || ch == 13) {
hasNl1 = true;
}
} else {
hasNl1 = true;
}
}
boolean hasNl2 = !nl2.isEmpty();
if (!hasNl2) {
if (m.end() + 1 < s.length()) {
char ch = s.charAt(m.end() + 1);
if (ch == 10 || ch == 13) {
hasNl2 = true;
}
}
}
boolean wholeLine = hasNl1 && hasNl2;
addText(stack, nl1);
if (!wholeLine) {
addText(stack, sp1);
}
block(stack, prefix, txt, whole);
if (!wholeLine) {
addText(stack, sp2);
}
if (!hasNl1 && hasNl2) {
addText(stack, nl2);
}
}
} else {
throw U.rte("Unknown tag!");
}
pos = m.end();
}
addText(stack, s.substring(pos));
XNode last = stack.pop();
U.must(last == root, "The tag '%s' was not closed!", last.text);
return root;
}
private static void addText(Stack stack, String g1) {
stack.peek().children.add(new XNode(XNode.OP.OP_TEXT, g1));
}
private static void expression(Stack stack, char prefix, String expr, String whole) {
U.must(!expr.isEmpty(), "Empty expression!");
switch (prefix) {
case '$':
stack.peek().children.add(new XNode(XNode.OP.OP_PRINT, expr));
break;
case '@':
stack.peek().children.add(new XNode(XNode.OP.OP_PRINT_RAW, expr));
break;
case '>':
stack.peek().children.add(new XNode(XNode.OP.OP_INCLUDE, expr));
break;
default:
throw U.rte("Invalid expression: %s", expr);
}
}
private static void block(Stack stack, char prefix, String stat, String whole) {
U.must(!stat.isEmpty(), "Empty block!");
switch (prefix) {
case '#':
stack.push(new XNode(XNode.OP.OP_FOREACH, stat));
break;
case '?':
stack.push(new XNode(XNode.OP.OP_IF, stat));
break;
case '^':
case '!':
stack.push(new XNode(XNode.OP.OP_IF_NOT, stat));
break;
case '/':
close(stack, stat);
break;
default:
throw U.rte("Invalid block: %s", stat);
}
}
private static void close(Stack stack, String text) {
U.must(!stack.isEmpty(), "Empty stack!");
XNode x = stack.pop();
U.must(x.op != XNode.OP.OP_ROOT, "Cannot close a tag that wasn't open: %s", text);
if (!U.eq(x.text, text)) {
throw U.rte("Expected block: %s, but found: %s", x.text, text);
}
stack.peek().children.add(x);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy