text-template is a small but powerful (as it is often in lisp) template library written in scheme. It can be used to generate any type of text - html, sql, code. Currently it supports SISC and PLT-Scheme scheme implementations, but it could be easily ported to other implementations. This is an open source software licensed under GPL.



Here are some examples of how to use templates

Prepare to work

At first implementation-specific code should be loaded, which load several libraries that text-template requires.
(load "text-template-plt.scm") ;; use text-template-sisc.scm under sisc
(load "text-template.scm")
(define context (create-context))

Simple value substitution

(context/set-generic context "name" "Nikita")
(context/set-generic context "age" "23")
((compile-template "Your name is ${name} and you are ${age} years old") context)
-->Your name is Nikita and you are 23 years old

User-defined function

(context/set-generic context 
                     (lambda (context value)
                       (create-value/string (string-upcase (value/string value)))))
((compile-template "Here is your name in capitals: ${(upcase name)}") context)
-->Here is your name in capitals: NIKITA


Child access

(context/set-generic context "product" (alist->hash-table '(("name" . "apple") ("type" . "fruit"))))
((compile-template "Product info:\nname: ${product/name}\ntype: ${product/type}") context)
-->Product info:
name: apple
type: fruit

Anonymous template

Anonymous template is a sort of template lambda expression. They are used mainly in loops (see further) over iteratable values.
((compile-template "${({a b| subtemplate body ${a} ${b}} 'arg1' 'arg2')}") context)
--> subtemplate body arg1 arg2
Which is similar to the following expression:
((lambda (a b) 
   (display " subtemplate body ")
   (display a)
   (display " ")
   (display b))
Anonymous templates can be used as arguments for other templates just like lambdas in lisp:
((compile-template "${({func| here is the (func 'hello'): ${(func 'hello')}} {arg| ${arg}+${arg}})}") context)
-->here is the (func 'hello'):  hello+hello


To apply template to elements of iteratable value the foreach function is used.
(context/set-generic context "fruits" '("apple" "orange" "banana"))
((compile-template "Fruit list:\n${(foreach fruits {fruit| - ${fruit}\n})}") context)
-->Fruit list:
- apple
- orange
- banana
The context in which loop body template is executed contains two counter values: i and i_0, 1 and 0 based respectively. They might be useful to create numbered fruit list:
((compile-template "Numbered fruit list:\n${(foreach fruits {fruit| ${i} ${fruit}\n})}") context)
-->Numbered fruit list:
 1 apple
 2 orange
 3 banana


Template expressions may use several special forms: if, not, or, and. Every context that is created with (create-context) is a child of default-context which has several buil-in functions. One of those was already mentioned - foreach. There are also functions that return boolean value and they can be used in conditionals. These are: =, <, >, set?, even?.
(context/set-generic context "a" 1)
(context/set-generic context "b" "whatever")
((compile-template "
'a' is: ${a}
a>0 : ${(if (> a '0') 'yes' 'no')}
b is set: ${(if (set? 'b') ({|yes and its value is: ${b}}) 'no')}
c is set: ${(if (set? 'c') ({|yes and its value is: ${c}}) 'no')}") context)
-->'a' is: 1
a>0 : yes
b is set: yes and its value is: whatever
c is set: no

Appliyng anonymous template with no arguments syntax

In the example above "({|yes and its value is: ${b}})" means: apply anonymous template with no arguments. It is just like using ((lambda() (do1)(do2))) to have several things be done sequentially. Scheme has begin for this purpose and so text-template has #marker#body#marker#. marker can be anything (even nothing) and it defines where template body ends. So our previous example can be rewritten as follows:
((compile-template "b is set: ${(if (set? 'b') ##yes and its value is: ${b}## 'no')}") context)
-->b is set: yes and its value is: whatever
or like that:
((compile-template "b is set: ${(if (set? 'b') #1#yes and its value is: ${b}, by the way we can use ## here #1# 'no')}") context)
-->b is set: yes and its value is: whatever, by the way we can use ## here 


Context is a map of name - value, where value is any object with several access functions: (->string value), (->iterator value), (get-child value name). Actually value is a vector: (vector value ->string get-child ->iterator). If string, number, list or hashtable is passed to the function context/set-generic it will create appropriate value. If you want to use other types of objects you should create value yourself.
(context/set-generic context "symbol" (create-value 'symbol-value symbol->string #f #f))
((compile-template "value: ${symbol}") context)
-->value: symbol-value
Iterator function must return function of one argument, which when called with 'has-next should return #t/#f and next element if called with other argument. Here is how list value can be implemented:
(context/set-generic context "numbers" (create-value '("one" "two" "three")
                                                     (lambda (value)
                                                       (define list value)
                                                       (lambda (arg)
                                                         (if (equal? arg 'has-next)
                                                             (not (null? list))
                                                             (let ((next (first list)))
                                                               (set! list (cdr list))
                                                               (create-value/string next)))))))
((compile-template "${(foreach numbers {number|${i} ${number} })}") context)
-->1 one 2 two 3 three 


Besides R5RS functionality text-template relies on 1, 11, 69 SRFIs and with-output-to-string macro. Provided that it can be used in any other scheme implementation.


text-template can be downloaded here


Comments, suggestions, bugs are all welcomed at the project's forum. Logo