The engine works by converting a template (which is a string, or a file) into a Tcl script, and then running it. Each line of text encountered will be returned as is. Exception is text between <% ... %> which is treated as Tcl code (the eval happens in a regular interp).
expand is a simple and snugly for Tcl language, be mounted in tcllib. You can use it as quick as flash.
But, that library has a problem, when to really use. We cannot write easily following signs
[ ] { }DDG No! We can easily write
[ ] { }with expand if we use alternative brackets for expansion:
(Tclkit) 24 % package require textutil::expander
1.3.1
(Tclkit) 25 % textutil::expander exp
::exp
(Tclkit) 26 % set string {[] {} <% set x %> }
[] {} <% set x %>
(Tclkit) 27 % set x 65
65
(Tclkit) 28 % exp expand $string {<% %>}
[] {} 65 kanryu Oh, I didn't know to be able to change brackets with textutil::expander. Then, there are restricted cases to use this library, maybe.
TemplaTcl is a powerful and cool template engine.
But now, we cannot use it on Tcl8.4 :(
a simple and fast template engine. That still not have function to read a file, create a parser object.
But we can use it instant, only write a command require or source.
Simple variable substitution is done with "<%= $variable %>" (e.g., Thanks <%=$count%> accesses!).
Simple expression substitution is done with "<%:Tcl expression%>" (e.g., <%:$i+2000%>).
First, let's create a template file (templtest.tmpl):
<table><%
for {set i 0} {$i < 4} {incr i} { %>
<tr>
<td><%= $i %></td>
</tr><%
} %>
</table>
<ul>
<% foreach item $cityList {
%><li><%= $item %>
<% } %>
</ul>The above template (in HTML language) generates a 4-row table and an unordered list, taking values from a $cityList variable that we're going to define. First let's load the code:
source tmpl_parser.tcl
reading template file.
set fd [open templtest.tmpl r] set tmpl [read $fd] close $fd
now create a parser instance:
set parser [::tmpl_parser::tmpl_parser $tmpl]
set the variables used in our template:
set cityList {Ragusa Ravenna Rieti Rimini Rome Rovigo}and render the template:
puts [eval $parser]
Here's the output that gets produced:
<table>
<tr>
<td>1</td>
</tr>
<tr>
<td>2</td>
</tr>
<tr>
<td>3</td>
</tr>
<tr>
<td>4</td>
</tr>
</table>
<ul>
<li>Ragusa
<li>Ravenna
<li>Rieti
<li>Rimini
<li>Rome
<li>Rovigo
</ul># tmpl_parser.tcl # # Tcl embeddedd script parser(a template engine) # # This module comverts Tcl embedded scripts into a Tcl normal script(parser), # after you just have to do eval command for the generated parser. # # Copyright (c) 2007 by Kanryu KATO<[email protected]> # licensed on Tcl License. package require Tcl 8.3 package provide tmpl_parser 0.1 namespace eval ::tmpl_parser { namespace export tmpl_parser } proc ::tmpl_parser::tmpl_parser {tmpl} { # Tcl enbedded tags # [[outer <%...inner...%> outer]] <-$tmpl # [ = ] <-$token # cd ef hi j <-indexes set parser { {set _o {}} } while {[set i [string first %> $tmpl]] != -1} { set h [expr {$i-1}] set j [expr {$i+2}] set token [string range $tmpl 0 $h] set d [string first <% $token] set c [expr {$d-1}] set e [expr {$d+2}] set f [expr {$d+3}] # outer lappend parser [escaped_parse [string range $token 0 $c]] switch [string index $token $e] { "=" { # normal expression (e.g., Thanks <%=$count%> accesses!) lappend parser [normal_parse [string range $token $f end]] } ":" { # numeric expression (e.g., <%:$i+2000%>) lappend parser [numeric_parse [string range $token $f end]] } default { # embedded Tcl command is passed through listing lappend parser [string range $token $e end] } } # after "%>" set tmpl [string range $tmpl $j end] } #last outer lappend parser [escaped_parse $tmpl] lappend parser {join $_o ""} return [join $parser "\n"] } proc ::tmpl_parser::escaped_parse {str} { set str [string map {\" \\\" \{ \\\{ \} \\\} \\ \\\\} $str] return "lappend _o \"$str\"" } proc ::tmpl_parser::normal_parse {str} { return "lappend _o $str" } proc ::tmpl_parser::numeric_parse {str} { return "lappend _o [expr $str]" }
milarepa - 2014-07-20 17:26:11
I been using tmpl_parser to parse a configuration file. So far it works fine. My problem now is that when I insert a variable like this:
<%= $variable_name %>
Inside that $variable_name there is a plain text that containes another tag to process another variable:
<%= $variable_inside_a_variable %>
I understand when the parser processes $variable_name will print $variable_inside_a_variable literally without doing the variable substitution.
Is there a way to do exactly that?
milarepa - 2014-07-20 17:35:05
I just go it !!!
Put the following lines to parse the variable once more:
set variable_inside_a_variable [eval [::tmpl_parser::tmpl_parser $variable_inside_a_variable]]
dbohdan 2014-08-04: I've reimplemented tmpl_parser with regexp and Tcl's built-in quoting used for code generation (I found the original's approach to quoting a bit of a problem when trying to extend it). In this version <%= ... %> can be used for both variable substitution and expressions, which are evaluated a run time; parse-time expression evaluation that the original used <%: ... %> for is not present. A new tag pair <%! ... %> is introduced: <%! command %> translates to code to insert the return value of command into the output. The motivation is that it is shorter than saying <%= command %> and eliminates unnecessary expression evaluation.
Caveat: I am not sure what is the oldest Tcl version that will run this implementation; it runs in 8.5.
# Convert a template into Tcl code.
proc parse {template} {
set result {}
set regExpr {^(.*?)<%(.*?)%>(.*)$}
set listing "set _output {}\n"
while {[regexp $regExpr $template \
match preceding token template]} {
append listing [list append _output $preceding]\n
switch -exact -- [string index $token 0] {
= {
append listing \
[format {append _output [expr %s]} \
[list [string range $token 1 end]]]
}
! {
append listing \
[format {append _output [%s]} \
[string range $token 1 end]]
}
default {
append listing $token
}
}
append listing \n
}
append listing [list append _output $template]\n
return $listing
}The <table> and <ul> examples from "Usage" work as is. What follows is a more in-depth example that comes from Tclssg. It employs the new <%! ... %> feature and generates HTML for use with Bootstrap.
<%
interp-source default-procs.tcl
%><!DOCTYPE html>
<html>
<head>
<meta charset="<%! website-var-get-default charset UTF-8 %>">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<!-- <link rel="icon" href="<%= $rootDirPath %>/favicon.ico"> -->
<title><%! format-html-title %></title>
<!-- Bootstrap core CSS -->
<link href="<%= $rootDirPath %>/external/bootstrap-3.2.0-dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap theme -->
<link href="<%= $rootDirPath %>/external/bootstrap-3.2.0-dist/css/bootstrap-theme.min.css" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="<%= $rootDirPath %>/tclssg.css" rel="stylesheet">
<%! page-var-get-default headExtra "" %>
</head>
<body>
<div class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#"><%= $websiteTitle %></a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><!-- class="active" --><a href="<%= $indexLink %>">Home</a></li>
<li><a href="<%= $blogIndexLink %>">Blog</a></li>
<li><a href="<%= $rootDirPath %>/contact.html">Contact</a></li>
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
<div class="container">
<div class="row">
<% if {[page-var-get-default blogPost 0] && \
(![page-var-get-default hideSidebar 0] || \
[page-var-get-default showTagCloud 0])} { %>
<div class="col-md-8">
<%= $content %>
<%! format-prev-next-links {Previous page} {Next page} %>
</div>
<div class="col-md-4 well">
<%! format-sidebar %>
<%! format-tag-cloud %>
</div>
<% } else { %>
<div class="col-md-12">
<%= $content %>
<%! format-prev-next-links {Previous page} {Next page} %>
</div>
<% }
%>
<div>
</div>
</div>
<%! format-comments %>
<div class="footer">
<%! format-footer %>
</div>
</div> <!-- /container -->
<!-- Bootstrap core JavaScript
================================================== -->
<!-- Placed at the end of the document so the pages load faster -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="<%= $rootDirPath %>/external/bootstrap-3.2.0-dist/js/bootstrap.min.js"></script>
</html>