Lumiera
0.pre.03
»edit your freedom«
|
Go to the source code of this file.
A minimalistic text templating engine with flexible data binding.
Text template instantiation implies the interpretation of a template specification, which contains literal text with some placeholder tags. This is combined with an actual data source; the engine needs to retrieve data values as directed by key names extracted from the placeholders and render and splice them into the placeholder locations. This process is crucial for code generation, for external tool integration and is also often used for dynamic web page generation. Several external libraries are available, offering a host of extended functionality. This library implementation for internal use by the Lumiera application however attempts to remain focused on the essential functionality, with only minimal assumptions regarding the data source used for instantiation. Rather than requiring data to be given in some map, or custom JSON data type, or some special property-tree or dynamic object type, a data binding protocol is stipulated; this way, any data type can be attached, given that five generic functions can be implemented to establish the binding. By default, a pre-defined binding is provided for a STL map and for Lumiera's »External Tree Description« format based on Record<GenNode>
.
TextTemplate is able to substitute simple placeholders by name, it can handle conditional sections and supports a data iteration construct for a nested scope. The supported functionality is best explained with an example:
This template spec is parsed and preprocessed into an internal representation, which can then be rendered with any suitable data source.
${date}
is replaced by a value retrieved with the key "date"${role}
is enclosed into a conditional section, making it optionalThe template specification is parsed and compiled immediately when constructing the TextTemplate instance. At this point, syntactical and logical errors, e.g. mismatched conditional opening and closing tags will be detected and raised as exceptions. The compiled template is represented as a vector of action tokens, holding the constant parts as strings in heap memory and marking the positions of placeholders and block bounds. The branching and looping possibly happening later, on instantiation, is prepared by issuing appropriate branching and jump markers, referring to other points in the sequence by index number...
TEXT
stores a text segment to be included literallyKEY
marks the placeholders, storing the key to retrieve a substitution valueCOND
indicates a branching point, based on a data value retrieved by keyITER
indicates the start of an iteration over data indicated by keyLOOP
marks the end of the iterated segment, linked back to the startJUMP
represents an unconditional jump to the index number given Whenever an else-section is specified in the template, a JUMP
is emitted beforehand, while the first TEXT
in the else-section is wired as refIDX
from the starting token.The actual instantiation is initiated through TextTemplate::submit(), which picks a suitable data binding (causing a compilation failure in case no binding can be established). This function yields an iterator, which will traverse the sequence of action tokens precompiled for this template and combine them with the retrieved data, yielding a std::string_view for each instantiated chunk of the template. The full result can thus be generated either by looping, or by invoking util::join() on the provided iterator.
** The instantiation processing logic is defined in terms of a data binding, represented as TextTemplate::DataSource. This binding, assuming a generic data access protocol, has to be supplied by a concrete (partial) specialisation of the template DataSource<DAT>
. This allows to render the text template with structured data, in whatever actual format the data is available. Notably, bindings are pre-defined for string data in a Map, and for Lumiera's »External Tree Description« format, based on a generic data node. Generally speaking, the following abstracted primitive operations are required to access data:
DataSource<DAT>
object itself is a copyable value object, representing an abstracted reference to the data. We can assume that it stores a const *
internally, pointing to some data entity residing elsewhere in memory.bool dataSrc.contains(key)
checks if a binding is available for the given key. If this function returns false
no further access is attempted for this key.string const& retrieveContent(key)
acquires a reference to string data representing the content bound to this key. This string content is assumed to remain stable in memory during the instantiation process, which exposes a std::string_view
Iter getSequence(key)
attempts to »open« a data sequence, assuming that the key somehow links to data that can somehow be interpreted as a sequence of nested sub-data-entities. The result is expected as »Lumiera Forward Iterator«.DataSource<DAT> openContext(Iter)
is supplied with the Iter from getSequence()
and assumed to return a new data binding as DataSource
object, tied to the nested data entity or context corresponding to the current »yield« of the Iterator. This implies that a Iter it
can be advanced by ++iter
and then passed in again to get the data-src (reference handle) to access the next »sub entity«, repeating this procedure until the iterator is exhausted (bool false
). Moreover, it is assumed, that recursive invocations of retrieveConent(key)
on this sub-scope reference will yield the data values designated by key for this sub-entity, as well as possibly also accessing data _visible from enclosing scopes.std::map<string,string>
implements this protocol — relying however on some trickery and conventions, since the map as such is one single „flat“ data repository. The intricate part relates to iteration (which can be considered more a »proof of concept« for testing). More specifically, accessing data for a loop control key should yield a CSV list of key prefixes. These are combined with the loop control key to form a prefix for individual data values: "<loop>.<entity>.<key>"
. When encountering a "key" while in iteration, first an access is attempted with this _decoration prefix; if this fails, a second attempt is made with the bare key alone. See TextTemplate_test::verify_iteration, which uses a special setup, where a string of key=value
pairs is parsed on-the-fly to populate a map<string,string>
GenNode
data (ETD), which is provided in the separate header text-template-gen-node-binding.hpp, is meant to handle structural data, as encountered in the internal communication of components within the Lumiera application — notably the »diff binding« used to populate the GUI with entities represented in the Session Model in Steam-Layer. The mapping is straight-forward, as the required concepts can be supported directlyRec<GenNode>
and can be represented recursively as a DataSource<Rec<GenNode>>Definition in file text-template.hpp.
#include "lib/error.hpp"
#include "lib/nocopy.hpp"
#include "lib/iter-index.hpp"
#include "lib/iter-explorer.hpp"
#include "lib/format-string.hpp"
#include "lib/format-util.hpp"
#include "lib/regex.hpp"
#include "lib/util.hpp"
#include <memory>
#include <string>
#include <vector>
#include <stack>
#include <map>
Classes | |
struct | TextTemplate::Action |
class | TextTemplate::ActionCompiler |
struct | DataSource< DAT, SEL > |
class | DataSource< DAT, SEL > |
Binding to a specific data source. More... | |
struct | DataSource< MapS > |
Data-binding for a Map-of-strings. More... | |
struct | DataSource< string > |
Adapter for the Map-Data-Source to consume a string spec (for testing) More... | |
class | TextTemplate::InstanceCore< SRC > |
Iterator »State Core« to process the template instantiation. More... | |
class | TextTemplate::InstanceCore< SRC > |
Iterator »State Core« to process the template instantiation. More... | |
struct | TextTemplate::ParseCtx |
struct | TagSyntax |
class | TextTemplate |
Text template substitution engine. More... | |
Typedefs | |
using | MapS = std::map< string, string > |
using | PairS = std::pair< string, string > |
using | StrView = std::string_view |
Functions | |
template<class STR , typename = meta::enable_if<meta::is_StringLike<STR>>> | |
DataSource (STR const &) -> DataSource< string > | |
Deduction Guide: help the compiler with picking the proper specialisation for a test-data source defined through a string spec or char literal. | |
return | explore (util::RegexSearchIter{input, ACCEPT_MARKUP}) .transform(classify) |
else | if (mat[2].matched) tag.syntax |
auto | iterBindingSeq (string const &dataDef) |
auto | iterNestedKeys (string key, StrView const &iterDef) |
auto | parse (string const &input) |
Variables | |
const regex | ACCEPT_BINDING_ELM {MATCH_DELIMITER + MATCH_BINDING_TOK} |
const regex | ACCEPT_DATA_ELM {MATCH_DELIMITER + MATCH_DATA_TOKEN} |
const regex | ACCEPT_MARKUP |
const string | MATCH_BINDING_KEY = R"~(([\w\.]+))~" |
const string | MATCH_BINDING_TOK = MATCH_BINDING_KEY+"\\s*=\\s*(?:"+MATCH_BINDING_VAL+"|"+MATCH_QUOTED_VAL+")" |
const string | MATCH_BINDING_VAL = R"~(([^,;"\s]+)\s*)~" |
const string | MATCH_DATA_TOKEN = R"~(([^,;"\s]*)\s*)~" |
< Parser and DataSource binding for lib::TextTemplate | |
const string | MATCH_DELIMITER = R"~((?:^|,|;)\s*)~" |
const string | MATCH_ELSE_TOK = "else" |
const string | MATCH_END_TOK = "end\\s*" |
const string | MATCH_ESCAPE = R"~((\\\$))~" |
const string | MATCH_FIELD = "\\$\\{\\s*(?:"+MATCH_SYNTAX+")\\s*\\}" |
const string | MATCH_KEY_PATH = MATCH_SINGLE_KEY+"(?:\\."+MATCH_SINGLE_KEY+")*" |
const string | MATCH_LOGIC_TOK = "if|for" |
const string | MATCH_QUOTED_VAL = R"~("([^"]+)"\s*)~" |
const string | MATCH_SINGLE_KEY = "[A-Za-z_]+\\w*" |
const string | MATCH_SYNTAX = "("+MATCH_ELSE_TOK+")|(?:("+MATCH_END_TOK+")?("+MATCH_LOGIC_TOK+")\\s*)?("+MATCH_KEY_PATH+")?" |
else if("end"==mat[5]) throw error else tag | syntax = TagSyntax::KEYID |
return | tag |
tag | tail = rest |
Namespaces | |
lib | |
Implementation namespace for support and library code. | |
class DataSource |
struct TextTemplate::ParseCtx |
const regex ACCEPT_MARKUP |
Definition at line 247 of file text-template.hpp.