C Template is a template expander library written in C and is similar to the perl HTML::Template module. Input to the expander is a template file (or string), a variable list (TMPL_varlist), and an optional format function list (TMPL_fmtlist). The expander processes the template using the variables in the variable list, and outputs the result.
Using templates makes it easier to separate information (the variable list) from the presentation (the template file and format functions). You can construct a variable list that can be output in a variety of formats or languages, using different templates. The template language is simple, but powerful enough for you to design flexible templates that handle many different situations. The C Template library is fast and efficient because it is written in C, and the API is easy to use.
Author: Stephen C. Losen, University of Virginia
A template file consists of text sequences, template tags and comments. Text sequences are copied to the output. Template tags are removed from the input, processed, and the result is output. Comments are removed from the input and do not appear in the output.
The template language supports these features:
A text sequence is any sequence of characters that is not a template tag and not a comment. A text sequence is copied unchanged to the output with a few exceptions. You can use a backslash (\) to suppress outputting a line terminator. A single \ at the end of a line is not output and neither is the line terminator. You can escape this behavior with two backslashes (\\) at the end of a line, which is output as a single \ followed by the line terminator. Any other \ is copied unchanged to the output.
Any text (including template tags and line terminators) enclosed by <* and *> is a comment, which the expander does not output. Comments cannot be nested. Within a comment <* is ignored and the comment ends at the first *> A *> without a preceding <* is considered ordinary text. A <* without a following *> is an error. Template tags may not contain comments because inserting a comment into a tag splits it into ordinary text sequences. If your template needs to output a literal <*, you can split < and * with a short comment like this: <<**>*.
You are certainly free to use comments supported by whatever language that your template outputs. For example, you can use HTML comments such as <!-- this is a comment -->. The template expander treats this as ordinary text and copies it to the output.
The template language has the following tags.
Usually the value is output without modification. The optional fmt="fmtname" attribute specifies a format function that the expander calls to output the value with appropriate formatting or encoding. The format function is also called when a tag's default value is output.
If filename begins with .../ then ... is replaced with the directory name of the enclosing template filename. If there is no directory name (no slash) then the .../ is removed. For example, if the enclosing file is dir/templates/main.tmpl and filename is .../include/incl1.tmpl, then the result is dir/templates/include/incl1.tmpl.
Template tag syntax has these features.
The syntax of the if statement is
A template-list is any sequence (including an empty sequence) of comments, text sequences, template tags, if statements, or loop statements. One TMPL_IF tag is required and must come first. There can be zero or more TMPL_ELSIF tags and zero or one <TMPL_ELSE>, which must come after any TMPL_ELSIF tags. The final </TMPL_IF> is always required. Any other use of these tags is an error.
Zero or one of the template-lists in an if statement is expanded and the rest of the if statement disappears. The TMPL_IF and TMPL_ELSIF tags are evaluated in order until a tag evaluates to true. Then the template-list immediately following the true tag is expanded. If no tags are true then the template-list after the <TMPL_ELSE> is expanded. Since the <TMPL_ELSE> is optional, possibly no template-lists are expanded and the entire if statement disappears.
The if statement syntax shown above is formatted for readability. You can format if statements any way you want, such as putting an entire if statement on one line. The same is true for loop statements.
The syntax of the loop statement is
Any other use of these tags is an error. Loopname is the name of a loop variable, which is a list of variable lists. Each variable list is essentially a row of data. If loopname does not exist, then the loop statement silently disappears. Otherwise the template-list is expanded repeatedly, one time for each variable list (row) in loopname. Within the template-list you can refer to variables in loopname's current variable list and refer to variables in enclosing variable lists, such as the variable list that contains loopname. A variable in an inner list overrides a variable with the same name in an enclosing list.
Within a loop statement you can use the TMPL_BREAK tag to break out of the loop and resume processing immediately after the loop statement. Or you can use the TMPL_CONTINUE tag to skip the rest of the current loop iteration and resume at the beginning of the next iteration.
In your C source you include the ctemplate.h header file, which declares these data types.
typedef void (*TMPL_fmtfunc)(const char *, FILE *);
The C Template library provides these functions.
These functions are reentrant because they do not use global variables or static local variables, so you can use this library with threads.
Some functions accept null terminated string parameters of type const char *. These functions make copies of strings as necessary so that after the function returns you can safely do anything you want with any string that you have passed as a parameter.
If you write your own format function, then it must have a prototype like this:
void yourfunc(const char *value, FILE *out);
and it should output value to open file pointer out, with appropriate formatting or encoding.
In your C source, include stdio.h and ctemplate.h and link your program with libctemplate.a.
#include <stdio.h> #include <ctemplate.h> int main(int argc, char **argv) { TMPL_varlist *mylist; /* declare the variable list */ /* load the variable list with TMPL_add_var() */ mylist = TMPL_add_var(0, "var1", "value1", "var2", "value2", 0); mylist = TMPL_add_var(mylist, "var3", "value3", 0); TMPL_add_var(mylist, "var4", "value4", 0); /* output the template */ TMPL_write("tmplfile", 0, 0, mylist, stdout, stderr); /* TMPL_write() frees any memory that it allocates. */ TMPL_free_varlist(mylist); /* free the variable list */ return 0; }
Constructing a loop variable for a loop statement is a bit trickier. Construct variable lists with TMPL_add_var() and add each one to the loop variable with TMPL_add_varlist(). Add the loop variable to the main variable list with TMPL_add_loop().
#include <stdio.h> #include <ctemplate.h> int main(int argc, char **argv) { TMPL_varlist *vl, *mainlist; TMPL_loop *loop; /* build the loop variable */ loop = 0; vl = TMPL_add_var(0, "row", "one", "user", "Bill", 0); loop = TMPL_add_varlist(loop, vl); vl = TMPL_add_var(0, "row", "two", "user", "Susan", 0); loop = TMPL_add_varlist(loop, vl); TMPL_add_varlist(loop, TMPL_add_var(0, "row", "three", "user", "Jane", 0)); /* add the loop variable to the main variable list */ mainlist = TMPL_add_loop(0, "myloop", loop); /* output the template and free variable list memory */ TMPL_write("tmplfile", 0, 0, mainlist, stdout, stderr); TMPL_free_varlist(mainlist); return 0; }
Here is an example template for expanding the loop.
Before loop. <TMPL_LOOP name = "myloop"> This is row <TMPL_VAR name = "row"> and the user is <TMPL_VAR name = "user"> </TMPL_LOOP> After loop.
Here is the output.
Before loop. This is row one and the user is Bill This is row two and the user is Susan This is row three and the user is Jane After loop.
Those blank lines appear because all the line terminators in the template file are outside of template tags and are therefore copied to the output. You can suppress outputting a line terminator by preceding it with a backslash (\) or enclosing it in a comment.
Nested loops are supported with no limit to the depth of nesting. You can build a loop variable, add it to a variable list and then add the variable list to an enclosing loop variable, etc.
Cycles are not permitted, however. You cannot add a loop variable to a variable list that is contained by the loop variable and you cannot add a variable list to a loop variable that is contained by the variable list. A loop variable can be added to a variable list one time only and a variable list can be added to a loop variable one time only. The template library enforces these rules by silently declining to perform any illegal operation.
To better separate information from presentation, you may want to store unformatted strings in a variable list and let the template expander format the strings when outputting them. That way the same variable list can be output in a variety of formats by passing it to different templates. For example, your variable list may have a variable named greeting with value <<HELLO>> that you want to insert into a HTML document. You could convert this string to <<HELLO>> before storing it in the variable list, but that encoding is specific to HTML, making the variable list unsuitable for a template that outputs something other than HTML.
To solve this problem use a TMPL_VAR tag such as <TMPL_VAR name="greeting" fmt="entity">, where fmt="entity" specifies a format function that does the necessary conversion. The expander calls this function to output the value of greeting.
You are welcome to write your own format functions and for your convenience the C Template library includes these functions that are helpful when outputting HTML.
To allow the attribute fmt="entity" to be used in TMPL_VAR tags, you must add the name entity and the function pointer TMPL_encode_entity to a format function list with TMPL_add_fmt() and pass the format function list to TMPL_write().
TMPL_varlist *varlist; TMPL_fmtlist *fmtlist; fmtlist = TMPL_add_fmt(0, "entity", TMPL_encode_entity); varlist = TMPL_add_var(0, "greeting", "<<HELLO>>", 0); TMPL_write("tmplfile", 0, fmtlist, varlist, stdout, stderr); TMPL_free_varlist(varlist); TMPL_free_fmtlist(fmtlist);
The C Template library comes with a program named template, which takes a template file name and variable list on the command line and outputs the result on stdout. This command is handy for checking a template file for syntax errors and for testing a template with a variety of input data.
The template command uses the format functions TMPL_encode_entity() and TMPL_encode_url(), which you can select in TMPL_VAR tags with fmt="entity" and fmt="url", respectively.
Usage: template filename varname1 value1 varname2 value2 ...
where filename is a template file and the rest of the arguments are variable names and values, each of which must be a separate argument.
template weather.tmpl title "Current Weather" temp 62 \ dewpoint 45 windspeed 8 windgust 15 winddir WNW \ condition "mostly sunny"
You can also define a loop variable with its name followed by one or more variable lists, each enclosed by { and }.
template tmplfile myloop { row one user Bill } \ { row two user Susan } { row three user Jane }
Each { and } must be a separate argument. A loop variable can appear anywhere that a simple name/value pair may appear, so they can be nested, which is necessary if the template has nested loops. For example,
template tmplfile title "Nested Loops" \ outerloop \ { var1 first innerloop { var2 third } { var2 fourth } } \ { var1 second innerloop { var2 fifth } { var2 sixth } }
The template file might look like this.
<h1><TMPL_VAR name = "title"></h1> <TMPL_LOOP name = "outerloop"> Begin outer loop <TMPL_LOOP name = "innerloop"> Begin inner loop The value of var1 is <TMPL_VAR name = "var1"> The value of var2 is <TMPL_VAR name = "var2"> End inner loop </TMPL_LOOP> End outer loop </TMPL_LOOP> End template
And here is the output.
<h1>Nested Loops</h1> Begin outer loop Begin inner loop The value of var1 is first The value of var2 is third End inner loop Begin inner loop The value of var1 is first The value of var2 is fourth End inner loop End outer loop Begin outer loop Begin inner loop The value of var1 is second The value of var2 is fifth End inner loop Begin inner loop The value of var1 is second The value of var2 is sixth End inner loop End outer loop End template
See the examples directory and the t/test.sh script for more examples.
The template language was designed to provide as much functionality as possible while keeping the syntax as simple as possible. In particular, TMPL_IF supports only rudimentary tests of variable values. While it would be nice if TMPL_IF supported more general expressions, this would greatly complicate the syntax.
The deficiencies of TMPL_IF can be mitigated by coding complicated logic in the C program and saving the result in a template variable. Then the template can use the rudimentary TMPL_IF syntax to test this variable. For example, suppose we have a template that outputs total disk space, used disk space and available space and we want this information to appear in red when over 90% of the space is used. The template language provides no direct way to accomplish this, but we can easily do the necessary calculation in C and set another template variable named almost_full when the information should appear in red.
<p> <TMPL_IF name="almost_full"><font color="red"></TMPL_IF> Total: <TMPL_VAR name="total"> Used: <TMPL_VAR name="used"> Available: <TMPL_VAR name="avail"> <TMPL_IF name="almost_full"></font></TMPL_IF> </p>
Similarly, TMPL_LOOP has no support for complicated logic and does little more than iterate through a list of variable lists. However, most of the functionality that TMPL_LOOP lacks can be easily provided by the C program and passed to the template in variables. Suppose we are displaying a very long HTML table and we would like to output a row of column headers every 25 rows so that the reader does not need to scroll all the way back to the top of the table to see them. The TMPL_LOOP statement has no built in support for this, but in our C program we can easily keep count of the rows and insert an extra template variable named "need_headers" on every 25th row.
<TMPL_LOOP name="table_rows"> <TMPL_IF name="need_headers"> <tr> <th>Col Header1</th> <th>Col Header2</th> <th>Col Header3</th> </tr> </TMPL_IF> <tr> <td><TMPL_VAR name="colvar1"></td> <td><TMPL_VAR name="colvar2"></td> <td><TMPL_VAR name="colvar3"></td> </tr> </TMPL_LOOP>
You can also use (abuse?) format functions to provide more complicated logic, since a format function can potentially do just about anything.