Hypertextcpp
An HTML template engine using C++ code generation
Install / Use
/learn @kamchatka-volcano/HypertextcppREADME
hypertextcpp is an HTML templating system for C++ applications. It provides an .htcpp template file format and a command-line utility that transpiles it to C++ HTML rendering code. Include the generated C++ header file in your project, set up a build system to update it when necessary, and you're all set.
A quick example:
<html>
<body>
<h1>$(cfg.name)'s todo list:</h1>
<p> No tasks found </p>?(cfg.tasks.empty())
<ul>
<li>$(task.name)</li>@(auto task : cfg.tasks)
</ul>
</body>
</html>
Now let's generate the C++ header:
kamchatka-volcano@home:~$ hypertextcpp todolist.htcpp
This command creates the todolist.h file in the current working directory. All that's left is to use it in our program.
examples/01/todolist_printer.cpp
#include "todolist.h"
#include <string>
#include <vector>
int main()
{
struct PageParams{
struct Task{
std::string name;
};
std::string name = "Bob";
std::vector<Task> tasks = {{"laundry"}, {"cooking"}};
} pageParams;
auto page = todolist{};
page.print(pageParams);
return 0;
}
Compile it with your preferred method, launch it, and you will get this output:
kamchatka-volcano@home:~$ ./todolist_printer
<html>
<body>
<h1>Bob's todo list:</h1>
<ul>
<li>laundry</li><li>cooking</li>
</ul>
</body>
</html>
Table of contents
Template file syntax
Expressions
$(c++ expression)
Expressions are used to add the application's data to the template. It can be any valid C++ expression, with the only condition that its result must be streamable to the default output stream std::ostream. Expressions can be placed anywhere in the HTML template, except in tag names.
In our todolist example, $(cfg.name) is an expression that adds the template config variable name to the result page.
Statements
${c++ statement(s)}
Statements are used to add any valid C++ code to the template rendering function. For example, you can add variables, class declarations, or lambdas. Let's say you don't like the default name cfg used for passing data to the template. You can create a reference to it with any name you like and use it later:
${ auto& param = cfg;}
<h1>$(param.name)'s todo list:</h1>
Note, that cfg and out are reserved names used for parameters in the generated
rendering function. Also, don't put anything in the htcpp namespace.
Global statements
#{c++ statement(s)}
These statements are used to add any valid C++ code outside the template rendering function. Unlike regular statements, with global ones you can add include directives or function definitions. Global statements can only be placed at the top level of the htcpp template, outside any HTML element. Please don't put anything in the htcpp namespace.
Control flow extensions
If hypertextcpp used a common approach for control flow in HTML template engines, our todolist example would look something like this:
<html>
<body>
<h1>%%cfg.name%%'s todo list:</h1>
%%if cfg.tasks.empty()%%
<p> No tasks found </p>
%%end%%
<ul>
%%for auto task : cfg.tasks%%
<li>$(task.name)</li>
%%end%%
</ul>
</body>
</html>
In our opinion, it significantly hurts the readability of the document tree and makes it hard to choose indentation and keep it consistent — notice how different approaches are used for if and for blocks in the example.
hypertextcpp solves this problem by applying control flow to the HTML elements themselves without adding logic block scopes to the document. It uses just two extensions for tags to make this work:
-
Conditional extension
?(c++ condition)
HTML elements with this extension are added to the document only when condition is fulfilled. Example of usage from our todolist template:<p> No tasks found </p>?(cfg.tasks.empty()) -
Loop extension
@(c++ init-statement; condition; iteration_expression)
or
@(c++ range_declaration : range_expression)
HTML elements with this extension are added to the document multiple times, on each step of the loop or for each element of the iterated range. Example of usage from our todolist template:<li>$(task.name)</li>@(auto task : cfg.tasks)Let's add another example for another type of
forloop:<li>Task#$(i)</li>@(auto i = 0 ; i < 3; ++i)which evaluates to
<li>Task#0</li> <li>Task#1</li> <li>Task#2</li>
Both extensions can be added to the opening or closing tag, but each tag and HTML element can only have one extension.
It's recommended to add the extension to the opening tag for multiline HTML elements:
<div>?(cfg.greet)
<p>Hello world!</p>
</div>
and to the closing tag for the single-line ones:
<div><p>Hello world!</p></div>?(cfg.greet)
Note that while extensions hide control flow block scopes from the template document, they're still present in the generated C++ code and implemented with regular if and for control structures. Therefore, a template like this:
<div>@(auto i = 0; i<3; ++i)
${ auto num = i*2;}
<p> Item #$(num)<p>
</div>
<p> Last num is $(num) </p>
won't compile because the num variable isn't visible outside the for block scope generated by the loop extension on the div tag.
Sections
[[ text, html elements, statements, expressions or other sections ]]
Sections can contain a part of the template document, and it's possible to attach control flow extensions to them. Their main usage is adding attributes to HTML elements conditionally.
Let's update the todolist example by adding a line-through text style to completed tasks:
examples/02/todolist.htcpp
<html>
<body>
<h1>$(cfg.name)'s todo list:</h1>
<p> No tasks found </p>?(cfg.tasks.empty())
<ul>
<li [[style="text-decoration: line-through;"]]?(task.isCompleted)>$(task.name)</li>@(auto task : cfg.tasks)
</ul>
</body>
</html>
Don't forget to update the C++ template config structure!
examples/02/todolist_printer.cpp
//...
struct PageParams{
struct Task{
std::string name;
bool isCompleted = false;
};
std::string name = "Bob";
std::vector<Task> tasks = {{"laundry", true}, {"cooking", false}};
} pageParams;
//...
Tip of the day: Keep your template tidy and don't introduce sections when it's possible to attach extensions to HTML elements.
Procedures and partial rendering
#procedureName(){html elements, statements, expressions or sections}
Parts of the htcpp template can be placed inside procedures—parameterless functions capturing the cfg variable. They are available for call from the C++ application, so if any part of the page needs to be rendered separately from the whole template, procedures are a big help.
Procedures can only be placed at the top level of the htcpp template, outside any HTML element.
Let's put the list of tasks from the todolist example in the procedure:
#taskList(){
<li [[style="text-decoration: line-through;"]]?(task.isCompleted)>$(task.name)</li>@(auto task : cfg.tasks)
}
<html>
<body>
<h1>$(cfg.name)'s todo list:</h1>
<p> No tasks found </p>?(cfg.tasks.empty())
<ul>
$(taskList())
</ul>
</body>
</html>
Now the tasks list can be output to stdout by itself like this:
examples/03/todolist_printer.cpp
//...
auto page = todolist{};
page.print("taskList", pageParams);
//...
Code generation
Command line parameters
kamchatka-volcano@home:~$ hypertextcpp --help
Usage: hypertextcpp [commands] [flags]
Flags:
--help show usage info and exit
Commands:
generateHeaderOnly [options] generate header only file
generateSharedLibrarySource [options] generate shared library source
file
generateHeaderAndSource [options] generate header and source files
Single header renderer generation:
kamchatka-volcano@home:~$ hypertextcpp generateHeaderOnly --help
Usage: hypertextcpp generateHeaderOnly <input> [params] [flags]
Arguments:
<input> (path) .htcpp file to transpile
-outputDir=<path> output dir
(if empty, current working directory is used)
(optional, default: "")
-className=<
