Template Engine Pebble (Java) --Guide

Documentation for Java template engine Pebble with Twig or Django-like syntax. It seems that you can customize it quite a bit. We will explain the basic usage of Pebble (from the first introduction to advanced users who want to customize). The explanation of tags, filters, functions, tests, and operators that can be used by default is written in ↓. Template engine Pebble \ (Java ) -Tags, filters, functions, tests, operators -Qiita

This article is created by adding the following documents as translations and supplements. http://www.mitchellbosecke.com/pebble/documentation I will not take responsibility even if the description is incorrect, but I would appreciate it if you could tell me.

Installation & configuration

Installation

Since it is published in Maven Central Repository, just add the following to pom.xml and you're done.

pom.xml


<!-- https://mvnrepository.com/artifact/io.pebbletemplates/pebble -->
<dependency>
    <groupId>io.pebbletemplates</groupId>
    <artifactId>pebble</artifactId>
    <version>2.6.2</version>
</dependency>

Note: If you want to use version 2.4.0 or lower, groupId seems to be different.

pom.xml


<!-- https://mvnrepository.com/artifact/com.mitchellbosecke/pebble -->
<dependency>
    <groupId>com.mitchellbosecke</groupId>
    <artifactId>pebble</artifactId>
    <version>2.4.0</version>
</dependency>

#### **`pom.xml`**
```xml


## setup
 If you want to integrate with Spring MVC [here](link with #Spring)

 To get started with PebbleEngine
 Let's edit the template and set the template in Pebble.
 This is a sample that compiles the template and outputs the result as a character string.

```java
PebbleEngine engine = new PebbleEngine.Builder().build();
PebbleTemplate compiledTemplate = engine.getTemplate("templateName");
Writer writer = new StringWriter();

Map<String, Object> context = new HashMap<>();
context.put("name", "Mitchell");

compiledTemplate.evaluate(writer, context);

String output = writer.toString();

Template Loader Pebble Engine allows you to specify how to load the template. If you do not specify a loader, the ClasspathLoader and FileLoader are automatically set internally.

When using servletContext


//Specify the template loading method with the argument of loader
PebbleEngine engine = new PebbleEngine.Builder()
    .loader(new ServletLoader(servletContext))
    .build();
Loader Description Default
ClasspathLoader Search by classpath ON
FileLoader Search by file path ON
ServletLoader Search from the servlet context. This is recommended when using an application server. OFF
StringLoader For debugging. It works by regarding the template name as the template content. OFF
DelegatingLoader Used when specifying multiple Loader in the collection. ON

Pebble Engine Settings

Setting Description Default
cache Use guava to cache the template. If null is set, the cache will be invalid and engine.Compile every time you call getTemplate. 200 templates cached
defaultLocale The default locale passed to each compiled template. Templates use this locale for functions such as i18n. Templates can also have a unique locale during evaluation. Locale.getDefault()
executorService Used when using the parallel tag. For multithreading. null
loader I explained earlier ... Classpath Loader and File Loader are enabled in Delegating Loader.
strictVariables If set to true, an exception will be thrown when accessing a variable or attribute that does not exist, or when trying to access the attribute of a null variable. If false, the variable does not exist, and if it is an attribute, it is skipped and processed. false

Works with Spring

sample

A fully working sample project is available on github for reference. When you build the project, you can build it with mvn install and deploy the output war. Setup Pebble supports 3.x and 4.x, Spring Boot. They will be separate libraries. Please add pom.xml below. This will provide the required ViewResolver and View classes.

pom.xml


<dependency>
	<groupId>com.mitchellbosecke</groupId>
	<artifactId>pebble-spring{version}</artifactId>
	<version>${pebble.version}</version>
</dependency>

For Spring Boot

pom.xml


<!-- https://mvnrepository.com/artifact/com.mitchellbosecke/pebble-spring-boot-starter -->
<dependency>
    <groupId>com.mitchellbosecke</groupId>
    <artifactId>pebble-spring-boot-starter</artifactId>
    <version>2.4.0</version>
</dependency>

Note: It seems that it also supports the spark framework of the micro framework. Is there any other FW that supports it? Sample source * GitHub

pom.xml


<!-- https://mvnrepository.com/artifact/com.sparkjava/spark-template-pebble -->
<dependency>
    <groupId>com.sparkjava</groupId>
    <artifactId>spark-template-pebble</artifactId>
    <version>2.5.5</version>
</dependency>

Next, make sure that the template file is on the classpath (eg / WEB-INF / templates /) Define PebbleEngine and PebbleViewResolver.

MvcConfig.java


@Configuration
@ComponentScan(basePackages = { "com.example.controller", "com.example.service" })
@EnableWebMvc
public class MvcConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private ServletContext servletContext;

    @Bean
    public Loader templateLoader(){
        return new ServletLoader(servletContext);
    }
    
    @Bean
    public SpringExtension springExtension() {
        return new SpringExtension();
    }

    @Bean 
    public PebbleEngine pebbleEngine() {
         return new PebbleEngine.Builder()
                .loader(this.templateLoader())
                .extension(springExtension())
                .build();
    }

    @Bean
    public ViewResolver viewResolver() {
        PebbleViewResolver viewResolver = new PebbleViewResolver();
        viewResolver.setPrefix("/WEB-INF/templates/");
        viewResolver.setSuffix(".html");
        viewResolver.setPebbleEngine(pebbleEngine());
        return viewResolver;
    }

}

Classes with @Controller annotation should return the template name as usual when using jsp.

ProfileController.java


@Controller
@RequestMapping(value = "/profile")
public class ProfileController {

	@Autowired
	private UserService userService;

	@RequestMapping
	public ModelAndView getUserProfile(@RequestParam("id") long id) {
		ModelAndView mav = new ModelAndView();
		mav.addObject("user", userService.getUser(id));
		mav.setViewName("profile");
		return mav;
	}

}

The above example renders \ WEB-INF \ templates \ profile.html. And the user object can be used in the template.

Some features of Pebble

About access to Spring beans

Of course you can access Spring beans in the template.

{{ beans.beanName }}

You can also access http requests, http responses, and http sessions.

{{ request.contextPath }}
{{ response.contentType }}
{{ session.maxInactiveInterval }}

Extensions for spring

Href function Automatically assigns a context path

<a href="{{ href('/foobar') }}">Example</a>

Message function It behaves like the i18n function. A little different is to use the configured Spring messageSource (usually ResourceBundleMessageSource)

Label = {{ message('label.test') }}
Label with params = {{ message('label.test.params', 'params1', 'params2') }}

Spring validations and error messages

To check if there's any error:

If you want to know if there is an error

{{ hasErrors('formName' }}

{{ hasGlobalErrors('formName' }}

{{ hasFieldErrors('formName', 'fieldName' }}

When handling multiple errors

{% for err in getAllErrors('formName') %}
    <p>{{ err }}</p>
{% endfor %}

{% for err in getGlobalErrors('formName') %}
    <p>{{ err }}</p>
{% endfor %}

{% for err in getFieldErrors('formName', 'fieldName') %}
    <p>{{ err }}</p>
{% endfor %}

Timer You can use PebbleView to output the time taken to process the template. Just add it to log4j.xml.

log4j.xml


<Logger name="com.mitchellbosecke.pebble.spring.PebbleView.timer" level="DEBUG" additivity="false">
      <AppenderRef ref="STDOUT" />
</Logger> 

Basic usage

Introduction

Pebble templates can output anything as text. HTML is assumed for general usage, but it can also be used for CSS, XML, JS, etc. (Supplement: I use it for the SQL template engine). The template will contain both the syntax of the language you want to output and the syntax of Pebble. Here's a little HTML template example.

<html>
	<head>
		<title>{{ websiteTitle }}</title>
	</head>
	<body>
		{{ content }}
	</body>
</html>

When evaluating this template (that is, calling the evaluate method) You need a map object with the above websiteTitle and content as keys.

The following code is required to create Pebble engine objects and compile templates.

PebbleEngine engine = new PebbleEngine.Builder().build();

The compilation by the Pebble engine is as follows

PebbleTemplate compiledTemplate = engine.getTemplate("templates/home.html");

Finally, prepare a java.io.Writer object and a Map object.

Writer writer = new StringWriter();

Map<String, Object> context = new HashMap<>();
context.put("websiteTitle", "My First Website");
context.put("content", "My Interesting Content");

compiledTemplate.evaluate(writer, context);

String output = writer.toString();

syntax

formula Description
{{ ... }} Returns the result of the expression. Please specify the variable name etc.
{% ... %} Controls the flow of the template. For example{% if... %},{% extends... %},{% block... %}there is.

variable

This is an example of outputting variables directly to the template. If the map object contains a variable called foo with the value "bar", it prints "bar".

{{ foo }}

You can also access the attributes of variables using the dot (.). If the attribute value is indefinite, you can also use [] for associative array ticks.

{{ foo.bar }}
{{ foo["bar"] }}

If you write foo.bar, the following access will be attempted as an internal operation.

If the value is null, an empty string is output (by default).

Type safety

Pebble has a dynamic type. And until it's actually evaluated, there are no type safety issues. Note: It seems to mean that variable names that do not actually exist in the template can be described. However, Pebble allows you to choose how to handle type safety issues in the strict Variables setting. The default is false.

{{ foo.bar }}

For example, in this example, if foo does not have the attribute bar and strictVariables is set to true, An Exception is thrown when evaluating the template. If strictVariables is false, it is also nullsafe, so even if the following foo and bar are null, an empty string is output.

{{ foo.bar.baz }}

The default filter may be best in this situation. Note: The default filter outputs the default wording when the value is null or the empty string / empty list.

filter

Filter is a function that allows you to further modify the output from the output. Use a pipe (|) to separate the variable name, filter name (and its arguments). If you use multiple filters, you can write them together. in this way….

{{ "If life gives you lemons, eat lemons." | upper | abbreviate(13) }}

In the above example, the output will be as follows.

IF LIFE GI...

function

The filter was just to modify the output, Functions create new functionality. It's the same as other languages. Call it using ().

{{ max(user.score, highscore) }}

Control syntax

Pebble has several control syntaxes. Mainly for and if.

{% for article in articles %}
    <h3>{{ article.title }}</h3>
    <p>{{ article.content }}</p>
{% else %}
    <p> There are no articles. </p>
{% endfor %}
{% if category == "news" %}
    {{ news }}
{% elseif category == "sports" %}
    {{ sports }}
{% else %}
    <p>Please select a category</p>
{% endif %}

Include from other templates

You can use the include tag to bring output from other templates.

<div class="sidebar">
	{% include "advertisement.html" %}
</div>

Template inheritance

This is one of Pebble's strengths. You can have an Overrideable section in your child template. Write the block tag in the parent template. First, take a look at this parent template.

<html>
<head>
	<title>{% block title %}My Website{% endblock %}</title>
</head>
<body>
	<div id="content">
		{% block content %}{% endblock %}
	</div>
	<div id="footer">
		{% block footer %}
			Copyright 2013
		{% endblock %}
	</div>
</body>
</html>

In the above, the block tag part can be overridden with a child template.

Child template.

{% extends "./parent.html" %}

{% block title %} Home {% endblock %}

{% block content %}
	<h1> Home </h1>
	<p> Welcome to my home page.</p>
{% endblock %}

There is a ʻextends` tag. First of all, it is useless without this tag. Only one can be used. (Supplement: It's similar to Java).

If you evaluate this child template, you will get output like this:


<html>
<head>
	<title>Home</title>
</head>
<body>
	<div id="content">
		<h1> Home </h1>
		<p> Welcome to my home page.</p>
	</div>
	<div id="footer">
		Copyright 2013
	</div>
</body>
</html>

In this example, the footer block is not overridden in the child template. In that case, the one described in the parent template is used.

You can also use dynamic inheritance.

{% extends ajax ? 'ajax.html' : 'base.html' %}

macro

Macros can reuse template parts. Use the macro tag. Note: You can do something similar by writing Java code that extends the function, I wonder if it is used when you do not want to write HTML code in java, or when there is no particular arithmetic processing and template description is sufficient.

{% macro input(type, name) %}
	<input type="{{ type }}" name="{{ name }}" />
{% endmacro %}

It can be called and used like a function.

{{ input("text", "name", "Mitchell") }}

Child templates can use the macros defined in the parent template. If you want to use a macro in a different file, use the ʻimport` tag. Macros cannot access the Map object (context). You can only access the arguments.

Named arguments

You can use named arguments in filters, functions, tests, and macros. If you do not change from the default value, you do not need to specify the argument.

{{ stringDate | date(existingFormat="yyyy-MMMM-d", format="yyyy/MMMM/d") }}

Positioned arguments and named arguments can be mixed up, Positioned arguments must come before named arguments.

{{ stringDate | date("yyyy/MMMM/d", existingFormat="yyyy-MMMM-d") }}

This is especially useful when using macros. That's because there are often unused arguments.

{% macro input(type="text", name, value) %}
	<input type="{{ type }}" name="{{ name }}" value="{{ value }}" />
{% endmacro %}

{{ input(name="country") }}

{# will output: <input type="text" name="country" value="" /> #}

escape

XSS vulnerabilities are typical of web applications. To avoid that, you need to escape potentially insecure data. Pebble has the auto-escape feature enabled by default. Auto-escaping can also be disabled. Pebble allows you to set finer escape settings.

This is an example of how the value set in the variable is escaped.

{% set danger = "<br>" %}
{{ danger }}

{# will output: &lt;br&gt; #}

If you have auto-escaping disabled, escape it this way.

{% set danger = "<br>" %}
{{ danger | escape }}

{# will output: &lt;br&gt; #}

By default, it is set to escape HTML. You can choose the escaping strategy (supplement: the internal logic seems to be a strategy pattern).

{% set danger = "alert(...)" %}
<script>var username="{{ danger | escape(strategy="js") }}"</script>

See also the escape guide. There is more information. There are contents such as how to disable it and how to switch the escaping strategy.

Blank

The first line break immediately after the Pebble tag is ignored by default. Other than that, it outputs as usual.

Pebble also has the ability to trim whitespace before and after. Write like this. Use the delimiter {{-...-}}.

<p> 		{{- "no whitespace" -}}		</p>
{# output: "<p>no whitespace</p>" #}

If you write like this, only one will be trimmed.

<p> 		{{- "no leading whitespace" }}		</p>
{# output: "<p>no whitespace		</p>" #}

comment

You can also add comments to the template parts. It is a delimiter called {# ... #}. This is not output.

{# THIS IS A COMMENT #}
{% for article in articles %}
	<h3>{{ article.title }}</h3>
	<p>{{ article.content }}</p>
{% endfor %}

formula

The expressions that appear in Pebble's templates are very similar to those that appear in Java.

literal

Literals are very simple expressions, aren't they? You can use Java String and integer types.

collection

You can use lists and maps directly in your templates.

arithmetic

It also supports basic arithmetic operations. We support these.

logic

You can combine two expressions with a logical operation. You can use these.

Comparison operation

These are supported.

==, ! =, <, >, > = And <=

{% if user.age >= 18 %}
	...
{% endif %}

test

The ʻis` operator runs the test. This test function can be used to determine specific values and so on. The operand to the right of is is the name of the test.

{% if 3 is odd %}
	...
{% endif %}

The test function can be negated by the not operator.

{% if name is not null %}
	...
{% endif %}

Ternary operator

You can also use the ternary operator.

{{ foo ? "yes" : "no" }}

Operator precedence

The top is the highest priority.

Pebble extension

Overview

Pibble is designed to be flexible for a variety of projects. You can also create and add tags, functions, operators, filters, test classes and global variables yourself. These implementations are almost easy and quick. First, let's create a class that implements the Extension interface. There is an easy way to do that. It is to create a class that inherits AbstractExtension that has the Extension interface implemented. And once implemented, let's register with Pebble Engine before compiling the template.

PebbleEngine engine = new PebbleEngine.Builder().extension(new CustomExtension()).build();

filter

To create a custom filter, first implement the getFilters () method of the Extension class described earlier. Create this method to return a Map object that is an instance of key = filter name and value = filter class. Once implemented, the next step is to implement the Filter interface. The Filter class implements two methods, getArgumentNames () and apply (). The getArgumentNames method is implemented to return a List that defines the name and order of the formal parameters. (It's like a keyword argument in a scripting language.) Supplement: Implement to return null if no definition is required.

The apply method implements the actual Filtering process. The first argument is the data you want to filter. The second argument is the filter argument. (Supplement: {{'Mitchell' | slice (1,3)}} ← This is the argument 1,3) Since Pebble is dynamically typed, please downcast from Object type. This is a sample filter.

public UpperFilter implements Filter {

  	@Override
	public List<String> getArgumentNames() {
		return null;
	}

	@Override
	public Object apply(Object input, Map<String, Object> args){
		if(input == null){
			return null;
		}
		String str = (String) input;
		return str.toUpperCase();
	}

}

The args map contains two elements by default.

_self: An instance of PebbleTemplate. You can get the template name and so on. _context: An instance of EvaluationContext. You can get the locale and so on.

Test class

It can be implemented in much the same way as a filter. Just implement getTests () which returns a map of the test name and the corresponding test object. And the test class implements Testʻinterface. Test has an interface similar to Filter. The apply method of Filter returned an arbitrary object, but the Test` class now returns boolean.

[even](http: //) Test implementation example.

public EvenTest implements Test {

	@Override
	public List<String> getArgumentNames() {
		return null;
	}

	@Override
	public boolean apply(Object input, Map<String,Object> args){
		Integer in = (Integer) input;
		return (in % 2 == 0);
	}

}

Functions functions are also similar to Filter. However, it is delicate to decide which one to implement, so let's understand it properly. In the case of Filter, it is intended to modify the existing content and output new content.

Now let's implement functions. Implements getFunctions () which returns a map of the function name and the corresponding function object. Then implement the function class that implements the Function interface. Similar to Filter and Test. Here is an example implementation of the fibonacciString function (not in the pebble library).

public FibonnaciStringFunction implements Function() {

	@Override
	public List<String> getArgumentNames() {
		List<String> names = new ArrayList<>();
		names.put("length");
		return names;
	}

	@Override
	public Object execute(Map<String,Object> args) {
		Integer length = (Integer)args.get("length");
		Integer prev1 = 0;
		Integer prev2 = 1;

		StringBuilder result = new StringBuilder();

		result.append("01");

		for(int i = 2; i < length; i++){
			Integer next = prev1 + prev2;
			result.append(next);
			prev1 = prev2;
			prev2 = next;
		}
		return result.toString();

	}
}

The args map contains two elements by default.

_self: An instance of PebbleTemplate. You can get the template name and so on. _context: An instance of EvaluationContext. You can get the locale and so on.

About position and argument name

Filter, Tests and Functions need to implement the getArgumentNames method even if they return null. Returning a List of strings allows template authors to use named parameters. This is an example of using the fibonacciString function earlier. There are two ways to call it.

{{ fibonacci(10) }}
{{ fibonacci(length=10) }}

If the template creator omits the name in the parameter and writes the argument in place When calling the Function, it will be mapped with the original name (supplement: the name defined in getArgumentNames). So, did the template author call it with a named parameter when implementing it? You don't have to worry about calling without the name. In short, if filter / function / test has multiple parameters, if the template creator does not use named parameters, You need to tell the order of the arguments.

minFunction and maxFunction can have an unlimited number of arguments. This type of function cannot use named arguments. The implementation of the getArgumentNames method in this case should return a null or empty list. In that case, the map with the key automatically assigned by Pebble is passed to the ʻexecute` method. (Supplement: A map is created with keys such as "1", "2" ...)

Global variables

It's also easy to create global variables that can be accessed from all templates. Simply implement the custom Extension getGlobalVariables () to return a Map <String, Object>. When merging with a template (supplement: when calling template.evaluate (writer, context)), it will be merged with each context.

operator

Operators are more complex to implement than filters and tests. Implement one or both of the getBinaryOperators () and getUnaryOperators () methods of your custom extension. It should return List and List respectively.

To implement BinaryOperator, you need the following:

--Priority: Prioritize operators with int type

--Symbol: The symbol of the actual operator. It's usually one letter, but it's okay if it's not.

--Expression Class: Returns the BinaryExpression class. This class implements the actual operator handling.

--The direction in which the operator acts (right or left) (Supplement: In terms of implementation, if left is specified, the priority is given.

The implementation of unary operator is the same as Binary Operator except that it does not require inheritance of ʻUnaryExpression` and definition of associativity.

A list of precedence.

or: 10
and: 15
is: 20
is not: 20
==: 30
!=: 30
>: 30
<: 30
>=: 30
<=: 30
+: 40
-: 40
not: 50 (Unary)
*: 60
/: 60
%: 60
|: 100
+: 500 (Unary)
-: 500 (Unary)

This is an implementation example of the + operator.

public AdditionOperator implements BinaryOperator {

	public int getPrecedence(){
		return 30;
	}

	public String getSymbol(){
		return "+";
	}

	public Class<? extends BinaryExpression<?>> getNodeClass(){
		return AdditionExpression.class;
	}

	public Associativity getAssociativity(){
		return Associativity.LEFT;
	}

}

You also need to implement the BinaryExpression class, which describes the actual processing of the operator. This is an implementation example of AdditionExpression referenced by the AdditionOperator in the sample above.

public AdditionExpression extends BinaryExpression<Object> {

	@Override
	public Object evaluate(PebbleTemplateImpl self, EvaluationContext context){
		Integer left = (Integer)getLeftExpression().evaluate(self, context);
		Integer right = (Integer)getRightExpression().evaluate(self, context);

		return left + right;
	}

}

The exceptions above are leftExpression and rightExpression. It is an operand (Supplement: In the case of 5 * 4, "5" and "4" are operands). In the above sample, we cast to Integer, but the cast does not always work. In the case of the original addition operator, it is necessary to assume other than Integer, which is actually a more complicated implementation.

tag

Creating new tags is one of Pebble's most powerful features. First, you need to implement the getTokenParsers () method. Make sure the com.mitchellbosecke.pebble.tokenParser.TokenParser interface returns the appropriate RenderableNode. The token argument of the parse method has information such as operators, whitespace, variable names, delimiters, etc. RenderableNode is a type for output.

This is an implementation example of the set tag.

public class SetTokenParser extends AbstractTokenParser {

    @Override
    public RenderableNode parse(Token token, Parser parser) throws ParserException {
        TokenStream stream = parser.getStream();
        int lineNumber = token.getLineNumber();

        // skip the 'set' token
        stream.next();

        String name = parser.getExpressionParser().parseNewVariableName();

        stream.expect(Token.Type.PUNCTUATION, "=");

        Expression<?> value = parser.getExpressionParser().parseExpression();

        stream.expect(Token.Type.EXECUTE_END);

        return new SetNode(lineNumber, name, value);
    }

    @Override
    public String getTag() {
        return "set";
    }
}

Make sure the getTag () method returns the tag name. Pebble's main parser then delegates processing to a custom parser.

The parse method is called every time Pebble's primary parser finds a custom tag. This method should return a RenderableNode instance to write to the Writer object. If RenderableNode contains child elements, it must be handled in the render method in the node class. We recommend looking at the following sources to learn how to parse. TokenParser Parser SetTokenParser ForTokenParser IfNode SetNode

Recommended Posts

Template Engine Pebble (Java) --Guide
Hello world with Java template engine Thymeleaf
[Template] MySQL connection with Java
Sample code that uses the Mustache template engine JMustache in Java
Multithreaded to fit in [Java] template
[Java] Button template class by Graphics