Rusted Dreams blog

{{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:

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}}:

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.