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.
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 |
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.
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 }}
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>
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();
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. |
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).
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 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...
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) }}
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 %}
You can use the include tag to bring output from other templates.
<div class="sidebar">
{% include "advertisement.html" %}
</div>
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' %}
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.
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="" /> #}
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: <br> #}
If you have auto-escaping disabled, escape it this way.
{% set danger = "<br>" %}
{{ danger | escape }}
{# will output: <br> #}
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.
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>" #}
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 %}
The expressions that appear in Pebble's templates are very similar to those that appear in Java.
Literals are very simple expressions, aren't they? You can use Java String and integer types.
" Hello World "
: A string enclosed in single or double quotes. You can use backslash escape.100 * 2.5
: Integers and decimals can be used. Similar to Java.true
/ false
: Boolean. This is similar to Java.null
: This is similar to Java. none
is an alias for null.You can use lists and maps directly in your templates.
["apple", "banana", "pear"]
: A list of strings{"apple":"red", "banana":"yellow", "pear":"green"}
: A map of stringsIt also supports basic arithmetic operations. We support these.
+
: Addition-
: Subtraction/
: Division%
: Modulus*
: MultiplicationYou can combine two expressions with a logical operation. You can use these.
and
: Returns true if both operands are trueor
: Returns true if either operand is truenot
: Negates an expression(...)
: Groups expressions togetherThese are supported.
==
, ! =
, <
, >
, > =
And <=
{% if user.age >= 18 %}
...
{% endif %}
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 %}
You can also use the ternary operator.
{{ foo ? "yes" : "no" }}
The top is the highest priority.
.
|
%
, /
, *
-
, +
==
, !=
, >
, <
, >=
, <=
is
, is not
and
or
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();
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.
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.
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.
min
Function and max
Function 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" ...)
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.
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
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.
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