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.
Xml-based templates have to be valid xml-documents itself and template expressions are usually represented by tags. This leads to bloated templates. In the above example there is code duplication caused by because it is not possible to inline condition into tag attribute "class", because you can't Actually, text-template was developed to replace templates like presented above with this:]]>
${(if game/icon ####)} ${(html game/name)} })}]]>
You can also extend templates with your functions. Suppose you register function html which escapes html special charecters like &, <, >. After this you can use it in your templates like this: ${(html user_comment)} and be safe from XSS attacks.
Here are some examples of how to use templates
(load "text-template-plt.scm") ;; use text-template-sisc.scm under sisc (load "text-template.scm") (define context (create-context))
(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
(context/set-generic context "upcase" (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
(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
((compile-template "${({a b| subtemplate body ${a} ${b}} 'arg1' 'arg2')}") context) --> subtemplate body arg1 arg2Which is similar to the following expression:
((lambda (a b) (display " subtemplate body ") (display a) (display " ") (display b)) "arg1" "arg2")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
(context/set-generic context "fruits" '("apple" "orange" "banana")) ((compile-template "Fruit list:\n${(foreach fruits {fruit| - ${fruit}\n})}") context) -->Fruit list: - apple - orange - bananaThe 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
(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
((compile-template "b is set: ${(if (set? 'b') ##yes and its value is: ${b}## 'no')}") context) -->b is set: yes and its value is: whateveror 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/set-generic context "symbol" (create-value 'symbol-value symbol->string #f #f)) ((compile-template "value: ${symbol}") context) -->value: symbol-valueIterator 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") #f #f (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
text-template can be downloaded here
Comments, suggestions, bugs are all welcomed at the project's forum.