{{substag}} tag substitution engine
27 Sep 2016
This blog is built by custom static site generator written in C++. It combines some HTML templates, some text snippets and files with blog posts contents into a couple of plain HTML files. An important part of this process is actual template substitution.
Before writing my own template substitution engine I've searched for existing solutions. {{mustache}}
looks like an almost ideal engine for my case.
But existing C++ libs for it turned out to be too complex for such simple task. They require using some variant-like tree structures to pass data into the engine. And some aspects of {{mustache}}
are not great for me:
- It HTML-escapes everything by default. While I need to stitch actual HTML code.
- The different behavior of regular tag expansion and partial expansion. A lot of my snippets need recursive expansion.
And it was actually faster to write my own tag substitution engine than to try to work around existing libs.
So here it is: {{substag}}
It's single-header C++14 library which uses boost::spirit::qi
for parsing.
Key differences from {{mustache}}
:
{{substag}}
uses simple maps of strings as input data instead of complex variant-based hierarchies.- All tags can be recursive, just like partials in
{{mustache}}
. - No HTML or any other kind of escaping.
- Tag delimiter changes can't leak out of section block.
Usage example:
#include <string> #include <map> #include <iostream> #include "substag.h" int main() { using namespace std; string input_template = "Hello {{name}}{{punctuation}}\n" "{{#colors}}\n" "{{! comment }} Color name: {{name}} R: {{red}} G: {{green}} B: {{blue}}{{#alpha}} A: {{alpha}}{{/alpha}}\n" "{{/colors}}\n" "{{=[[ ]]=}}\n" "[[^words]]\n" "No 'words' tag or section {{punctuation}}[[! {{punctuation}} not expanded here ]]\n" "[[/words]]\n" "[[={{ }}=]]\n" "{{endphrase}}\n"; map<string, string> tags = { { "name", "World" }, { "punctuation", "!" }, // recursive expansion: { "endphrase", "The End{{punctuation}}" }, { "c1name", "Yellow" }, { "c1red", "1" }, { "c1green", "1" }, { "c1blue", "0" }, { "c2name", "Cyan" }, { "c2red", "0" }, { "c2green", "1" }, { "c2blue", "1" }, // no 'name' tag for c3, will use 'name' from outer context: { "c3red", "1" }, { "c3green", "0" }, { "c3blue", "1" }, { "c3alpha", "0.5" } }; multimap<string, string> sections = { { "colors", "c1" }, { "colors", "c2" }, { "colors", "c3" } }; string result = substag::render( input_template, tags, sections ); result.erase( std::unique( result.begin(), result.end(), []( char a, char b ) { return a == '\n' && a == b; } ), result.end() ); cout << result; /* Hello World! Color name: Yellow R: 1 G: 1 B: 0 Color name: Cyan R: 0 G: 1 B: 1 Color name: World R: 1 G: 0 B: 1 A: 0.5 No 'words' tag or section {{punctuation}} The End! */ return 0; }
It is distributed under the Boost Software License. So feel free to use it in your projects.