Die FreshMarker Template-Engine, kann bislang nur konstante Inhalte ausgeben. Damit die Template-Engine nützlich wird, müssen variable Inhalte möglich werden. Ausdrücke der Form ${expression}
werden von der FreshMarker Grammatik als Variableninterplolationen erkannt und durch das Ergebnis des Ausdrucks expression
ersetzt.
Für die erste Umsetzung sind die folgenden Regeln der Grammatik beachtet worden.
Block : (SCAN 2 (Text | Interpolation))*; Interpolation : <INTERPOLATE>Expression<CLOSE_BRACE>; PrimaryExpression : BaseExpression (BuiltIn | Exists)*; BaseExpression : <IDENTIFIER> | NumberLiteral | StringLiteral | BooleanLiteral | NullLiteral | Parenthesis; BuiltIn : <BUILT_IN><IDENTIFIER>[<OPEN_PAREN>[ArgsList]<CLOSE_PAREN>]; Exists : <EXISTS_OPERATOR>; ArgsList #void : (LOOKAHEAD(<IDENTIFIER><EQUALS>) NamedArgsList | PositionalArgsList); PositionalArgsList : Expression ([<COMMA>]Expression)*;
Ein Block
enthält neben Text
auch Interpolation
Elemente. Die wiederum beginnen mit dem INTERPOLATE
Token (${
) beinhalten ein Expression
Element und enden mit }
. Zu Anfang beschränkt sich die Expression
in der Variableninterpolation auf PrimaryExpression
, die aus einem Variablennamen IDENTIFIER
oder einem Literal eines Basistyps und optionalen BuildIn
oder Exists
Elementen besteht.
Bei BuildIn
Elementen handelt es sich um Funktionen, die auf diesen Basistypen ausgeführt werden können. Für den Typ String
existieren beispielsweise die BuildIns upper_case
und lower_case
mit denen die String
Werte in Groß- bzw. Kleinbuchstaben umgewandelt werden. Das Exists
Element prüft ob eine Variable existiert und liefert in der Variableninterpolation true
oder false
zurück.
Damit die FreshMarker die Variableninterpolationen beachtet, muss der FragmentBuilder
um eine entsprechende visit
Methode ergänzt werden
@Override public BlockFragment visit(Interpolation ftl, BlockFragment input) { logger.debug("interpolation: {}", ftl); TemplateObject interpolation = ftl.getChild(1).accept(interpolationBuilder, null); input.addFragment(new InterpolationFragment(interpolation)); return input; }
Diese Visitor Methode unterscheidet sich von den bisherigen FragmentBuilder
Methoden darin, dass sie die Verarbeitung der Expression an einen anderen Visitor delegiert. Dieser InterpolationBuilder
ist eine Implementierung des ExpressionVisitors
, der nur auf Elementen der Grammatik arbeitet, die Bestandteil von FreshMarker Ausdrücken sind.
public class InterpolationBuilder implements ExpressionVisitor<Object, TemplateObject> { @Override public TemplateObject visit(PrimaryExpression expression, Object input) { TemplateObject base = children.get(0).accept(this, input); for (int i = 1; i < children.size(); i++) { base = children.get(i).accept(this, base); } return base; } @Override public TemplateObject visit(Token expression, Object input) { } @Override public TemplateObject visit(BuiltIn expression, Object input) { } }
Die erste visit
Methode delegiert die Verarbeitung des ersten Elementes an eine der anderen visit
Methoden und in der darauffolgenden Schleife werden mögliche Exists
und BuiltIn
Elemente auf das bereits erzeugte Ergebnis angewendet.
public class InterpolationBuilder implements ExpressionVisitor<Object, TemplateObject> { @Override public TemplateObject visit(PrimaryExpression expression, Object input) { } @Override public TemplateObject visit(Token expression, Object input) { String image = expression.getImage(); switch (expression.getType()) { case TRUE: return TemplateBoolean.TRUE; case FALSE: return TemplateBoolean.FALSE; case INTEGER: return new TemplateNumber(Integer.valueOf(image), Type.INTEGER); case DECIMAL: return new TemplateNumber(Double.valueOf(image), Type.DOUBLE); case STRING_LITERAL: return new TemplateString(image.substring(1, image.length() - 1)); case RAW_STRING: return new TemplateString(image.substring(2, image.length() - 1)); case IDENTIFIER: return new TemplateVariable(expression.getImage()); case EXISTS_OPERATOR: return new TemplateExists((TemplateObject) input); default: throw new ProcessException("invalid token type: " + expression.getType()); } } @Override public TemplateObject visit(BuiltIn expression, Object input) { } }
Die zweite visit
methode liefert für jedes unterstützte Token ein entsprechendes TemplateObjekt
zurück. Für die Token-Typen TRUE
, FALSE
, INTEGER
, DECIMAL
, STRING
_LITERAL und RAW_STRING
die entsprechenden Basistypen und für IDENTIFIER
und EXISTS_OPERATOR
zwei spezielle TemplateObject
Typen. Der Typ TemplateVariable
bezieht bei der Templateprozessierung den tatsächlichen Wert anhand des übergebenen Namens aus dem aktuellen Environment
. Der Typ TemplateExists
überprüft das übergebene TemplateObject
, also den Basisausdruck der Variableninterpolation, während der Templateprozessierung. Die Überprüfung liefert nur dann true
zurück, wenn die Auswertung des TemplateObject
nicht TemplateNull.NULL
ergibt.
public class InterpolationBuilder implements ExpressionVisitor<Object, TemplateObject> { @Override public TemplateObject visit(PrimaryExpression expression, Object input) { } @Override public TemplateObject visit(Token expression, Object input) { } @Override public TemplateObject visit(BuiltIn expression, Object input) { Token buildInName = (Token) expression.getChild(1); List<TemplateObject> parameter = new ArrayList<>(); if (expression.getChildCount() < 3) { return new TemplateFunction(buildInName.getImage(), (TemplateObject) input, List.of()); } Node child = expression.getChild(3); if (child instanceof PositionalArgsList) { child.accept(new ParameterListBuilder(), parameter); } else { parameter.add(child.accept(this, null)); } logger.info("parameters: {}", parameter); return new TemplateFunction(buildInName.getImage(), (TemplateObject) input, parameter); } }
Die dritte visit
Methode behandelt BuiltIn
Elemente. Sie ist etwas komplexer, da ein BuiltIn
auch Parameter besitzen kann. Im einfachen Fall wird eine TemplateFunction
Instanz mit leerer Parameterliste erzeugt und im komplexeren Fall wird die Parameterliste mit Hilfe eines weiteren ExpressionVisitor
vom Typ ParameterListBuilder
erzeugt.
Die TemplateFunction
ist, wie TemplateExists
und TemplateVariable
, ein spezielles TemplateObject
, das während der Templateprozessierung dynamisch ausgewertet wird. Während die primitiven Typen wie TemplateBoolean
, TemplateString
und TemplateNumber
statische Werte besitzen, können die Werte der anderen erst bestimmt werden, wenn das aktuelle Environment
bereitsteht.
@AllArgsConstructor public class TemplateFunction implements TemplateExpression { private final String name; private final TemplateObject expression; private final List<TemplateObject> parameter; @Override public TemplateObject evaluateToObject(Environment environment) { TemplateObject result = expression.evaluateToObject(environment); return environment.getBuiltIn(result.getClass(), name).apply(result, parameter, environment); } }
Bei der Auswertung der TemplateFunction
wird für dem ausgewerteten Ausdruck ein BuiltIn
aus dem Environment
geholt. Notwendig hierfür ist der Typ des ausgewerteten Ausdruck und der Name des BuiltIn. wenn ein entsprechendes BuiltIn
existiert, dann wird mit dem Wert und den Parametern aufgerufen und sein Ergebnis zurück geliefert.
Wie die BuildIn
im Environment
bereitgestellt werden und wie sie funktionieren ist Inhalt des nächsten FreshMarker Beitrags.