- Notifications
You must be signed in to change notification settings - Fork9
Text templating processor for SWI-Prolog.
License
rla/simple-template
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Text (HTML) template processor for SWI-Prolog. Worksbest for cases when you have mainly static HTML.
Visual Code syntax support:https://marketplace.visualstudio.com/items?itemName=rlaanemets.simple-template-syntax
Input markup (test.html
file):
<h1>{{= title }}</h1>{{ each items, item }} <h2>{{= item.title }}</h2> <div>{{- item.content }}</div>{{ end }}
rendering with data:
use_module(library(st/st_render)).current_output(Out),st_render_file(test, _{ title: 'Hello', items: [ _{ title: 'Item 1', content: 'Abc 1' }, _{ title: 'Item 1', content: 'Abc 2' } ]}, Out, []).
output:
<h1>Hello</h1><h2>Item 1</h2><div>Abc 1</div><h2>Item 1</h2><div>Abc 2</div>
If you find your tools work better with a template tag syntax thatmore closely resembles other web templating engines, such as Django(Python) / Djula (Common Lisp) / Twig (PHP), you can also use analternate frontend syntax called semblance:
Input markup (test.html
file):
<h1>{{ title }}</h1>{% each items, item %} <h2>{{ item.title }}</h2> <div>{% unescape item.content %}</div>{% end %}
rendering with data:
use_module(library(st/st_render)).current_output(Out),st_render_file(test, _{ title: 'Hello', items: [ _{ title: 'Item 1', content: 'Abc 1' }, _{ title: 'Item 1', content: 'Abc 2' } ]}, Out, _{ frontend: semblance }).
output:
<h1>Hello</h1><h2>Item 1</h2><div>Abc 1</div><h2>Item 1</h2><div>Abc 2</div>
It is not possible to mix two syntaxes through a single render call.
Alternatively, you can specify your own templating delimiter syntax.This is useful to prevent template collision, in similar cases to semblance,but is more flexible.
Input markup (test.html
file):
<h1><%= title %></h1><% each items, item %> <h2><%= item.title %></h2> <div><%~ item.content %></div><% end %>
rendering with data:
use_module(library(st/st_render)).current_output(Out),st_render_file(test, _{ title: 'Hello', items: [ _{ title: 'Item 1', content: 'Abc 1' }, _{ title: 'Item 1', content: 'Abc 2' } ]}, Out, _{ frontend: syntax_tokens( comment("<%#", "%>"), out("<%=", "%>"), out_unescaped("<%~", "%>"), statement("<%", "%>") )}).
output:
<h1>Hello</h1><h2>Item 1</h2><div>Abc 1</div><h2>Item 1</h2><div>Abc 2</div>
If you'd like to use an unescape keyword, you can use the helperkeyword_unescape_start(Token)
in place of a string, whereToken
is the starting Token to unify:
use_module(library(st/st_render)).current_output(Out),st_render_file(test, _{ title: 'Hello', items: [ _{ title: 'Item 1', content: 'Abc 1' }, _{ title: 'Item 1', content: 'Abc 2' } ]}, Out, _{ frontend: syntax_tokens( comment("<%#", "%>"), out("<%=", "%>"), out_unescaped(keyword_unescape_start("<%="), "%>"), statement("<%", "%>") )}).
With input markup (test.html
file):
<h1><%= title %></h1><% each items, item %> <h2><%= item.title %></h2> <div><%= unescape item.content %></div><% end %>
output:
<h1>Hello</h1><h2>Item 1</h2><div>Abc 1</div><h2>Item 1</h2><div>Abc 2</div>
Render template from string:
st_render_string(String, Data, Stream, File, Options).
Render template from file:
st_render_file(File, Data, Stream, Options).
Render template from codes:
st_render_codes(Codes, Data, Stream, File, Options).
Options accept the following:
- encoding - default
utf8
. - extension - file name extension, default
html
. - cache - whether to use cache, default
false
. - strip - whether to try to strip whitespace from output, default
false
. - frontend - which syntax to use, currently
simple
(default) orsemblance
. - undefined - throw error on undefinedvariables -
error
(default) orfalse
(evaluates to false).
Processing instructions start with double opening curly braces ({{
) andend with double closing curly braces (}}
). Semblance syntax variationsare described below.
There are 4 types of processing instructions.
There are two output instructions.{{= expression }}
outputs the expressionvalue and escapes the special HTML characters.{{- expression }}
outputs theexpression value but does not escape the output. There must be no spacebetween{{
and=
or-
.
Includes use the syntax{{ include path/to/file }}
. The path should be relative.The relative path is resolved against the current file location and the currentlyset file extension is added. All values from the current scope are available tothe included file. There is also{{ include path/to/file, expression }}
thatsets the scope of the included file to the value of the expression (must be a dict).
Dynamic includes use the syntax{{ dynamic_include expression }}
. The value ofthe expression must be a file path specifier. There is also a variant with thescope expression.
Includes are currently processed at runtime. You should enable caching if you includeinside a loop otherwise the included file is parsed over and over again.
Conditional instructions have the following syntax:
{{ if cond_expression1 }}...{{ else if cond_expression2 }}...{{ else }}...{{ end }}
else if
andelse
parts are optional.
The each loop allows to process lists. The syntax for each loop:
{{ each expression, item_var, index_var, len_var }}...{{ end }}
The value of expression must be a list.item_var
,index_var
andlen_var
refer to current item, current item index and the length of the list.index_var
andlen_var
are optional.
Blocks allow to compose templates while passing around the contained template asa value. This allows to wrap templated content with another template.
General syntax (block usage):
{{ block path/to/file }} Block content.{{ end }}
And to render the distributed block content:
{{ slot }}
Example 1: wrapping content into a panel element.
Input file:
<div></div>{{ block panel }} <span>This will be wrapped in panel.</span>{{ end }}<div></div>
Panel file:
<div>{{ slot }}</div>
Rendering result:
<div></div><div><span>This will be wrapped in panel.</span></div><div></div>
Variable scoping inside block content is lexical. Variable scope insideblock is either the current scope or selectable with an expression similarto the{{ include file, expr }}
instruction.
Example 2: layouts.
Layout file (layout.html
):
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8"> <title>{{= title }}</title> </head> <body> <h1>{{= title }}</h1> {{ slot }} </body></html>
Concrete page file:
{{ block layout }} <span>This is the page file.</span>{{ end }}
Data:
_{ title: "A page title" }
Rendering result:
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8"> <title>A page title</title> </head> <body> <h1>A page title</h1> <span>This is the page file.</span> </body></html>
The semblance syntax has the following differences.
Output instruction:{{ variable }}
.
Unescaped output:{% unescape rawvar %}
.
Conditional instructions:
{% if cond_expression1 %}...{% else if cond_expression2 %}...{% else %}...{% end %}
Each loop:
{% each expression, item_var, index_var, len_var %}...{% end %}
Comments:{# comment #}
.
Expressions are ground Prolog terms with the interpretationdescribed below.
In most contexts, atoms are interpreted as scope entries ("variables").An error is thrown when there is no suitable entry.
The following arithmetic operators and functions are supported:-, +, *, /, mod, rem, //, div, abs, sign, max, min, random, round, truncate, floor, ceiling, **, ^
.
Boolean expressions include modified unification using the=
operator. Theoperator provides coercion from atom to string when either of operands is an atomand the other is a string. For other types, no coercion is applied. Similarily worksthe\=
operator. Supported boolean operators are:,
(logical and),;
(logical or)and\+
(logical negation). Supported comparison operator are:>, <, >=, =<,
.
Conditional expressionif(expression, true_expr, false_expr)
value is the value oftrue_expr
when theexpression
evaluates to anything but 0 orfalse
. Otherwise thevalue is the value offalse_expr
.
The+
operator is overloaded to provide string or atom concatenation whenthe left side of the operator is an atom or string. The result is string.
The.
operator is used for accessing values from dicts. Internally the./3
predicate is used which means that user-defined functions on dictswill work as intended.
Compound terms that match none of the operators and constructs described above aretreated as user-defined (see below) function calls. An error is thrown when there is no such functiondefined.
A special expression isatom(something)
. Its value is atomsomething
. Thisis added to differentiate them from other atoms which are otherwise interpretedas scope entries.
Literal lists work as expected with the elements having the interpretation asthe expressions above.
The default behaviour when a variable that is not in the scope (template data dict) is encountered is to throw ano_entry
error. This behaviour can be modified to set the variable value tofalse
(default iserror
) by setting theundefined
option tofalse
when rendering templates. For example:
st_render_file(test, _{ title: 'Hello', items: [ _{ title: 'Item 1', content: 'Abc 1' }, _{ title: 'Item 1', content: 'Abc 2' } ]}, Out, _{ undefined: false }).
This is useful in situations where a developer may want to include a block in the template based on the existence of a variable. For example:
{% if page_header %} <h2>{{page_header}}</h2>{% end %}
There are three single-argument functions to encode URI parts:encode_path
,encode_query_value
andencode_fragment
. These calluri_encoded/3 withthe respective first argument.
Global constants can be set with thest_set_global/2
predicate by importingthest_expr
module. When a scope contains anentry with the same name as a global then the local value is preferred.
User-defined functions can be set with thest_set_function(Name, Arity, Goal)
predicate in thest_expr
module. Arity refers to the function arity. Goal musthave arity Arity + 1.
Comment blocks start with{{%
and end with}}
. Comments do not appearin the output.
Template caching is enabled by setting optioncache: true
during rendering. This makesthe system cache parsed templates. This is particulary useful when using includes in loops.To purge the current cache contents, use thest_cache_invalidate
predicate.
Some (but not all) whitespace is removed by setting optionstrip: true
during rendering. Line indents and some duplicate line endsare removed from the output. Whitespace removal is parse-time and does notincur any runtime penalty.
Encoding for template files can be specified by setting optionencoding
during rendering. Acceptedvalues are all that are accepted by theencoding
option of theread_file_to_codes/3
predicate.
This package requires SWI-Prolog 7.x.
pack_install(simple_template).
Seehttp://packs.rlaanemets.com/simple-template/doc/.
In the package root, insert into swipl:
[tests/tests].run_tests.
Or if you cloned the repo:
make test
Please send bug reports/feature request through the GitHubprojectpage.
- 2020-02-03 version 1.3.1. Add user-defined templating syntax.
- 2017-11-03 version 1.2.0. Option to deal with undefined variables.
- 2015-12-28 version 1.1.0. Add alternate syntax: semblance.
- 2015-11-07 version 1.0.0. Removal of global options. Backwards-incompatible.
- 2014-05-09 version 0.3.0. Provide st_cache_invalidate/0, = operator, comments.
- 2014-05-07 version 0.2.0. Literal lists, encode_* functions.
- 2014-03-02 version 0.1.0. Provided st_set_encoding/1.
- 2014-01-30 version 0.0.1
The MIT License. See LICENSE file.
About
Text templating processor for SWI-Prolog.