A simple html template parser for TCL (inspired from Python Django)
Converts a HTML template like this
<!-- File ex2.tpl -->
<html>
<body>
{% for addr in address_book %}
<p>{{loop.count}}. <b>{{addr.name}}</b></p>
<i>[Professional]</i>
<ul>
<li>Firstname : {{addr.name.0}}</li>
<li>Lastname : {{addr.name.1}}</li>
<li>Place : {{addr.place}}</li>
<li>Phone : {{addr.phone}}</li>
</ul>
{% if addr.personal %}
<i>[Personal]</i>
<ul>
<li>Phone : {{addr.personal.phone}}</li>
<li>Email : {{addr.personal.email}}</li>
</ul>
{% else %}
<i>[Personal info not available]</i>
{% endif %}
{% endfor %}
</body>
</html>
when provided with the view data structure as
puts [::SimpleTemplater::render "/home/user/templates/ex2.tpl" {
address_book {
{
name "John Doe"
place "USA"
phone "001"
personal {
phone "001-123-12345"
email "john.doe@e-mail.com"
}
}
{
name "David Beck"
place "England"
phone "002"
personal ""
}
{
name "Sam Philip"
place "Australia"
phone "003"
personal {
phone "007-134-4567"
email "sam.philip@e-mail.com"
}
}
}
}]
into
<html>
<body>
<p>1. <b>John Doe</b></p>
<i>[Professional]</i>
<ul>
<li>Firstname : John</li>
<li>Lastname : Doe</li>
<li>Place : USA</li>
<li>Phone : 1369664972</li>
</ul>
<i>[Personal]</i>
<ul>
<li>Phone : 001-123-12345</li>
<li>Email : john.doe@e-mail.com</li>
</ul>
<p>2. <b>David Beck</b></p>
<i>[Professional]</i>
<ul>
<li>Firstname : David</li>
<li>Lastname : Beck</li>
<li>Place : England</li>
<li>Phone : 1469664972</li>
</ul>
<i>[Personal info not available]</i>
<p>3. <b>Sam Philip</b></p>
<i>[Professional]</i>
<ul>
<li>Firstname : Sam</li>
<li>Lastname : Philip</li>
<li>Place : Australia</li>
<li>Phone : 1569664972</li>
</ul>
<i>[Personal]</i>
<ul>
<li>Phone : 007-134-4567</li>
<li>Email : sam.philip@e-mail.com</li>
</ul>
</body>
</html>
::SimpleTemplater::render "<template_path>" "<view>"
package require "SimpleTemplater"
puts [::SimpleTemplater::render "<template_path>" {
<[Template Object_Name]> <[Value]>
}]
# eg.
puts [::SimpleTemplater::render "states.tpl" [dict create \
states [list \
[dict create \
name "Alabama" \
cities [list \
[dict create \
name "auburn" \
] \
[dict create \
name "birmingham" \
] \
] \
] \
[dict create \
name "Alaska" \
cities [list \
[dict create \
name "anchorage" \
] \
[dict create \
name "fairbanks" \
] \
] \
] \
] \
]]
package require "SimpleTemplater"
set compiled_template [::SimpleTemplater::compile "<template_path>"]
puts [$compiled_template execute {
<[Template Object_Name]> <[Value]>
}]
$compiled_template destroy
package require "SimpleTemplater"
puts [::SimpleTemplater::renderString "<template_string>" {
<[Template Object_Name]> <[Value]>
}]
#####View
{
name {John}
}
#####Template
<p>Hello {{name}}</p>
#####Output
<p>Hello John</p>
#####View
{
address {
{
name {John Doe}
}
{
name {Philip Alex}
}
}
}
#####Template
{% for addr in address %}
<p>{{loop.count}} Firstname: {{addr.name.0}}</p>
{% endfor %}
#####Output
<p>1 Firstname: John</p>
<p>2 Firstname: Philip</p>
A list element can be accessed by providing the numeric index {{ context_var.index }}
and a key-value dictionary styled list element can be accessed providing the key as the index {{ context_var.key }}
#####View
{
players {
{Rafael Nadal}
{Roger Federer}
}
}
#####Template
{% for person in players %}
<p>{{person}}</p>
{% endfor %}
#####Output
<p>Rafael Nadal</p>
<p>Roger Federer</p>
#####View
{
players {
{Rafael Nadal}
{Roger Federer}
{Novak Djokovic}
{Andy Murray}
}
}
#####Template
{% for person1, person2 in players %}
<p>{{person1}}, {{person2}}</p>
{% endfor %}
#####Output
<p>Rafael Nadal, Roger Federer</p>
<p>Novak Djokovic, Andy Murray</p>
#####Template
{% for a in "hello world" %}
<p>{{a}}</p> <!-- first hello second world -->
{% endfor %}
#####Output
<p>hello</p>
<p>world</p>
#####View
{
players {
{Rafael Nadal}
{Roger Federer}
}
}
#####Template
{% for person in players %}
<p>{{ loop.count }}. {{ person }}</p>
{% endfor %}
#####Output
<p>1. Rafael Nadal</p>
<p>2. Roger Federer</p>
#####View
{
name {John John}
}
#####Template
{% if name.0 == name.1 %}
<p>You have an interesting name!</p>
{% endif %}
#####Output
<p>You have an interesting name!</p>
{% if name.0 == "John" %}
<!-- do something -->
{% endif %}
{% if name.0 == "John" %}
<!-- do something -->
{% else %}
<!-- do something else-->
{% endif %}
{% if not name %}
<!-- do something -->
{% endif %}
<!-- OR -->
{% if !name %}
<!-- do something -->
{% endif %}
#####View
{
members {
{
active 1
name "John Doe"
}
{
active 0
name "Philip Alex"
}
}
}
#####Template
Active members:
<table>
{% for mem in members %}
{% if mem.active %}
<tr><td>{{ mem.name }}</td></tr>
{% endif %}
{% endfor %}
</table>
Inactive members:
<table>
{% for mem in members %}
{% if not mem.active %}
<tr><td>{{ mem.name }}</td></tr>
{% endif %}
{% endfor %}
</table>
#####Output
Active members:
<table>
<tr><td>John Doe</td></tr>
</table>
Inactive members:
<table>
<tr><td>Philip Alex</td></tr>
</table>
Any variable used within the template would be auto-escaped. Consider an email id of a person saved as
<script type="text/javascript">alert('XSS');</script>
would make your site vulnerable to XSS. SimpleTemplater would automatically get all your variables escaped
<tr><td>Email</td><td>{{ addr.personal.email }}</td></tr>
into
<tr><td>Email</td><td><script type="text/javascript">alert('XSS');</script></td></tr>
instead of
<tr><td>Email</td><td><script type="text/javascript">alert('XSS');</script></td></tr>
You can explicitly mark a variable not to be escaped by applying a safe filter (filters mentioned below)
<tr><td>Email</td><td>{{ addr.personal.email|safe }}</td></tr>
Usage: {{ context_var|<filter> }}
{{ context_var|safe }}
prevents your variable from being auto-escaped
{{ context_var|tick }}
converts all '
in your variable to ´
after escaping
Create a new transformation procedure
proc Modulus { context args } {
return [expr { $context % [lindex $args 0] }]
}
Register the filter in your script
::SimpleTemplater::registerFilter -filter modulus -proc Modulus
# optional -safe true|false
Syntax: ::SimpleTemplater::registerFilter -filter <filter_name> -safe <true|false> -proc <procedure_name>
Apply the filter in your template
{{ index|modulus:"10" }}
View
proc Modulus { context args } {
if { ![regexp {^\d+$} [lindex $args 0]] } { return 0 }
return [expr { $context % [lindex $args 0] }]
}
proc Class { context args } {
return [lindex $args $context]
}
::SimpleTemplater::registerFilter -filter modulus -proc Modulus
::SimpleTemplater::registerFilter -filter class -proc Class
puts [::SimpleTemplater::render "/home/user/templates/sample.tpl" {
example {
.... ....
}
}]
Template
{% for ex in example %}
...
<tr class="{{loop.count|modulus:"2"|class:"grey,white"}}">..</tr>
...
{% endfor %}
Output
...
<tr class="white">..</tr>
<tr class="grey">..</tr>
<tr class="white">..</tr>
<tr class="grey">..</tr>
<tr class="white">..</tr>
...