htmlcc: Statically compiled HTML templates for C
This article presents htmlcc, an HTML templating engine for C utilising static compilation.
Motivation
Web applications are not commonly written in C; one notable counterexample is cgit, a web frontend for git repositories. HTML output in cgit is implemented from scratch, using bespoke helper functions and hundreds of individual printf-esque calls. A representative example of HTML-related code in cgit looks like:
void cgit_print_docstart(void)
{
/* ... */
html("<html lang='en'>\n");
html("<head>\n");
html("<title>");
html_txt(ctx.page.title);
html("</title>\n");
htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
if (ctx.cfg.robots && *ctx.cfg.robots)
htmlf("<meta name='robots' content='%s'/>\n", ctx.cfg.robots);
/* ... */
}
While highly performant, this code is non-trivial to work with, particularly to make significant changes to the HTML layout. Typical support tooling (e.g. syntax highlighting) is also not available.
For this reason, I sought to create an HTML templating engine for C. In order to preserve performance and simplify implementation as a drop-in replacement, it was desired not to write a template interpreter in C. Rather, templates would be transpiled into C code (like that above) and statically compiled. Since there is no widespread standardised approach to writing web applications in C, it was also desired that emitted C code be easily customisable (e.g. printf-style as above, versus fill a buffer and return the result, etc.).
I am aware of limited prior art in this respect. TinyTemplate has similar syntax, but templates are dynamically rather than statically compiled, and interspersed C code is not possible so significant architectural changes would be required to any software that expects to output HTML while manipulating internal state. Omar Polo describes a similar templating engine, but it does not have an extensible architecture (though is designed to be easily modified).
About htmlcc
htmlcc is a command line Python application. It takes as input a template file (syntax inspired by Jinja, using double curly braces) and outputs C code in the above described style. An extensible framework is provided to customise the C code output; examples are provided implementing output to stdout, or cgit-style function calls. Although the transpiler is implemented in Python, the generated C code is pure C, cross-platform, has no inherent dependencies, and requires no memory allocations.
Supported language features include Jinja-like filter expressions (a limited number have been implemented), control flow (if, while, for), and interspersing raw C code.
Example
The following is a simple example of an htmlcc template:
{! char *my_name = "Alice"; !}
{% page hello_world %}
<html>
<body>Hello world, my name is {{ my_name }}!</body>
</html>
{% endpage %}
Using the default C emitter (print to stdout), the template transpiles into the following C code:
#include <stdio.h>
void output_variable_as_text(char *variable) { /* escape special characters ... */ }
char *my_name = "Alice";
void hello_world(void) {
printf("%s", "<html>\n");
printf("%s", " <body>Hello world, my name is ");
output_variable_as_text(my_name);
printf("%s", "!</body>\n");
printf("%s", "</html>\n");
}
The following example demonstrates how control flow is supported:
{% page main %}
<html>
<body>
<p>Counting to 10:</p>
<p>
{% for int i = 1; i <= 10; i++ %}
{{ i|%d }}{% if i != 10 %},{% endif %}
{% endfor %}
</p>
</body>
</html>
{% endpage %}
Further details
Source code and further details on htmlcc are available at https://yingtongli.me/git/htmlcc. A further article discussing the application of htmlcc to a cgit fork is forthcoming.