Library: further work out the framework for TextTemplate instantiation

This commit is contained in:
Fischlurch 2024-03-20 00:18:28 +01:00
parent d2dcf6c163
commit 7893cc99c3
2 changed files with 261 additions and 40 deletions

View file

@ -23,7 +23,69 @@
/** @file text-template.hpp
** 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>`.
**
** # Template syntax and features
**
** 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:
** \code
** Rendered at ${date}.
** ${if critical}
** WARNING: critical!
** ${else}(routine report)${end if critical}
**
** Participants
** ${for person}- ${name} ${if role}(${role})${end if role}
** ${else}** no participants **
** ${end for person}
** \endcode
** This template spec is parsed and preprocessed into an internal representation,
** which can then be rendered with any suitable data source.
** - the placeholder `${date}` is replaced by a value retrieved with the key "date"
** - the conditional section will appear only if a key "critical" is defined
** - when the data defines content under the key "person", and this content
** can be suitably interpreted as a sequence of sub-scopes, then the »for block«
** is instantiated for each entry, using the values retrieved through the keys
** "name" and "role". Typically these keys are defined for each sub-scope
** - note that the key "role" is enclosed into a conditional section
** - note that both for conditional sections, and for iteration, an _else branch_
** can be defined.
** How data is actually accessed, and what constitutes a nested scope is obviously
** a matter of the actual data binding, which is picked up through a template
** specialisation for lib::TextTemplate::DataSource
**
** # Implementation notes
**
** The template specification is parsed and compiled immediately when constructing
** the TextTemplate instance. At this point, syntactical 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 actual instantiation is initiated through TextTemplate::render(), which picks
** a suitable data binding (causing a compilation failure in case not 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.
**
** @todo WIP-WIP-WIP 3/2024
** @see TextTemplate_test
** @see gnuplot-gen.hpp
@ -43,26 +105,28 @@
//#include <cmath>
//#include <limits>
#include <vector>
#include <string>
//#include <stdint.h>
//#include <boost/rational.hpp>
#include <vector>
#include <stack>
#include <map>
namespace lib {
// using Rat = boost::rational<int64_t>;
// using boost::rational_cast;
// using std::abs;
using std::string;
using StrView = std::string_view;
namespace {// preconfigured TextTemplate data bindings
namespace {
/** shorthand for an »iter-explorer« build from some source X */
template<class X>
using ExploreIter = decltype (lib::explore (std::declval<X>()));
}
/**
/*****************************************//**
* Text template substitution engine
*/
class TextTemplate
@ -92,31 +156,48 @@ namespace lib {
Idx refIDX{0};
template<class IT>
string instantiate (IT&);
};
/** Binding to a specific data source.
* @note requires partial specialisation */
template<class DAT, typename SEL=void>
struct InstanceCore
{
static_assert (not sizeof(DAT),
"unable to bind this data source "
"for TextTemplate instantiation");
StrView instantiate (IT&);
};
/** the text template is compiled into a sequence of Actions */
using ActionSeq = std::vector<Action>;
using PipeTODO = std::vector<string>;
using InstanceIter = decltype (explore (std::declval<PipeTODO const&>()));
/** Binding to a specific data source.
* @note requires partial specialisation */
template<class DAT, typename SEL=void>
class DataSource;
template<class SRC>
class InstanceCore
{
using ActionIter = ExploreIter<ActionSeq const&>;
using DataCtxIter = typename SRC::Iter;
using NestedCtx = std::pair<DataCtxIter, SRC>;
using CtxStack = std::stack<NestedCtx, std::vector<NestedCtx>>;
SRC dataSrc_;
ActionIter actionIter_;
CtxStack ctxStack_;
StrView rendered_;
public:
InstanceCore (ActionSeq const& actions, SRC);
bool checkPoint() const;
StrView& yield() const;
void iterNext();
};
template<class DAT>
using InstanceIter = ExploreIter<InstanceCore<DataSource<DAT>>>;
public:
TextTemplate(string spec)
{ }
template<class DAT>
InstanceIter
InstanceIter<DAT>
render (DAT const& data) const;
template<class DAT>
@ -126,9 +207,75 @@ namespace lib {
/* ======= preconfigured data bindings ======= */
template<class DAT, typename SEL=void>
struct TextTemplate::DataSource
{
static_assert (not sizeof(DAT),
"unable to bind this data source "
"for TextTemplate instantiation");
};
template<>
struct TextTemplate::DataSource<std::map<string,string>>
{
using Iter = std::string_view;
};
template<class SRC>
TextTemplate::InstanceCore<SRC>::InstanceCore (TextTemplate::ActionSeq const& actions, SRC s)
: dataSrc_{s}
, actionIter_{explore (actions)}
, ctxStack_{}
{ }
template<class SRC>
inline bool
TextTemplate::InstanceCore<SRC>::checkPoint() const
{
UNIMPLEMENTED ("TextTemplate instantiation: check point on action token");
}
template<class SRC>
inline StrView&
TextTemplate::InstanceCore<SRC>::yield() const
{
UNIMPLEMENTED ("TextTemplate instantiation: yield instantiated string element for action token");
}
template<class SRC>
inline void
TextTemplate::InstanceCore<SRC>::iterNext()
{
UNIMPLEMENTED ("TextTemplate instantiation: advance to interpretation of next action token");
}
/**
* Interpret an action token from the compiled text template
* based on the given data binding and iteration state to yield a rendering
* @param instanceIter the wrapped InstanceCore with the actual data binding
* @return a string-view pointing to the effective rendered chunk corresponding to this action
*/
template<class IT>
inline StrView
TextTemplate::Action::instantiate (IT& instanceIter)
{
UNIMPLEMENTED ("actual implementation of template action interpretation");
}
/** */
template<class DAT>
inline TextTemplate::InstanceIter
inline TextTemplate::InstanceIter<DAT>
TextTemplate::render (DAT const& data) const
{
UNIMPLEMENTED ("actually instantiate the text template");

View file

@ -112529,12 +112529,13 @@ std::cout &lt;&lt; tmpl.render({&quot;what&quot;, &quot;World&quot;}) &lt;&lt; s
<icon BUILTIN="button_ok"/>
<node CREATED="1710802871952" ID="ID_1230958115" MODIFIED="1710802881229" TEXT="ben&#xf6;tigte Operationen">
<icon BUILTIN="info"/>
<node CREATED="1710802887262" ID="ID_943892158" MODIFIED="1710803289340" TEXT="DataSrc.retrieveContent(key) &#x27fc; iter"/>
<node CREATED="1710802982433" ID="ID_555113619" MODIFIED="1710803294652" TEXT="DataSrc.handleMiss(key) &#x27fc; iter">
<node CREATED="1710887924376" ID="ID_1738189195" MODIFIED="1710887947623" TEXT="DataSrc.contains(key) &#x27fc; bool"/>
<node CREATED="1710802887262" ID="ID_943892158" MODIFIED="1710887952504" TEXT="DataSrc.retrieveContent(key) &#x27fc; string"/>
<node CREATED="1710802982433" ID="ID_555113619" MODIFIED="1710887958495" TEXT="DataSrc.handleMiss(key) &#x27fc; string">
<node CREATED="1710803102126" ID="ID_929049801" MODIFIED="1710803116035" TEXT="liefert einen Ersatz-Content"/>
<node CREATED="1710803122407" ID="ID_1916953854" MODIFIED="1710803131553" TEXT="darf Exception werfen"/>
</node>
<node CREATED="1710803055072" ID="ID_1341021229" MODIFIED="1710803301854" TEXT="DataSrc.getSequence(iter) &#x27fc; iter">
<node CREATED="1710803055072" ID="ID_1341021229" MODIFIED="1710888017135" TEXT="DataSrc.getSequence(key) &#x27fc; iter">
<node CREATED="1710803141876" ID="ID_196999296" MODIFIED="1710803152399" TEXT="interpretiert Content in eine Datensequenz"/>
<node CREATED="1710803153298" ID="ID_1252110387" MODIFIED="1710803171163" TEXT="liefert neuen Iterator &#xfc;ber Sequenz-content"/>
</node>
@ -112549,16 +112550,45 @@ std::cout &lt;&lt; tmpl.render({&quot;what&quot;, &quot;World&quot;}) &lt;&lt; s
<node CREATED="1710803524513" ID="ID_527165239" MODIFIED="1710803549685" TEXT="ein generischer Datentyp (eigentlich das Binding)"/>
<node CREATED="1710803532568" ID="ID_362764880" MODIFIED="1710803540795" TEXT="stellt die o.g. Operationen bereit"/>
<node CREATED="1710803559841" ID="ID_978322266" MODIFIED="1710803571734" TEXT="kann ausgewertet werden um Dateninhalte zu gewinnen"/>
<node CREATED="1710888899301" ID="ID_1431488282" MODIFIED="1710889348598" TEXT="mu&#xdf; zuweisbar sein (also letztlich ein Pointer auf die Daten)"/>
<node CREATED="1710889358377" ID="ID_903149258" MODIFIED="1710889479699" TEXT="wird rekursiv / re-entrant verwendet">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
das bedeutet: nachdem ein nested-context <i>ge&#246;ffnet wurde, </i>m&#252;ssen wir einen State erlangen, auf dem transparent genauso gearbeitet werden kann, wie auf dem initialen / top-level-State
</p>
</body>
</html>
</richcontent>
<icon BUILTIN="messagebox_warning"/>
</node>
</node>
<node CREATED="1710803573890" ID="ID_331230947" MODIFIED="1710803577253" TEXT="Iter">
<node CREATED="1710803580073" ID="ID_92823392" MODIFIED="1710803623176" TEXT="Auswertungszustand auf der DataSrc"/>
<node CREATED="1710803710216" ID="ID_1903129970" MODIFIED="1710803728139" TEXT="kann dereferenziert werden &#x27fc; string-konvertierbar"/>
<node CREATED="1710803743860" ID="ID_408889688" MODIFIED="1710803759710" TEXT="kann auf &#xbb;Gehalt&#xab; gepr&#xfc;ft werden &#x27fc; bool"/>
<node CREATED="1710803768785" ID="ID_1240137544" MODIFIED="1710803786266" TEXT="kann u.U iteriert werden">
<node CREATED="1710803789014" ID="ID_871294739" MODIFIED="1710803797988" TEXT="findet aber nur in der Sequenz statt"/>
<node CREATED="1710803768785" ID="ID_1240137544" MODIFIED="1710888759444" TEXT="kann iteriert werden (Iterator-Protokoll)">
<node CREATED="1710803799133" ID="ID_16257402" MODIFIED="1710803811694" TEXT="nur ein gehaltvoller Iterator darf Iteriert werden"/>
<node CREATED="1710803812469" ID="ID_776439841" MODIFIED="1710803820957" TEXT="nach der Iteration mu&#xdf; erneut auf Gehalt gepr&#xfc;ft werden"/>
</node>
<node CREATED="1710888766407" ID="ID_1542277193" MODIFIED="1710888791904" TEXT="kann &#xbb;ge&#xf6;ffnet&#xab; werden &#x27fc; nested DataSrc">
<node CREATED="1710888800147" ID="ID_415681901" MODIFIED="1710888817940" TEXT="konzeptionell wie eine Dreferentiation"/>
<node CREATED="1710888818840" ID="ID_480044105" MODIFIED="1710888865476" TEXT="aber die Implementierung obliegt der DataSrc">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
weil sie im Einzelfall auch komplexer sein kann, und im Besonderen eine parent-Verkn&#252;pfung beinhaltet
</p>
</body>
</html>
</richcontent>
</node>
</node>
</node>
</node>
<node CREATED="1710803901743" ID="ID_1926090539" MODIFIED="1710803921865" TEXT="jeder Datentyp, welcher diese Eigenschaften bietet, kann als Datenbinding fungieren">
@ -112831,35 +112861,79 @@ std::cout &lt;&lt; tmpl.render({&quot;what&quot;, &quot;World&quot;}) &lt;&lt; s
<node CREATED="1710856916531" ID="ID_1047340326" MODIFIED="1710856926342" TEXT="die ActionSeq konsumieren"/>
<node CREATED="1710857877562" ID="ID_707601175" MODIFIED="1710857911451" TEXT="Instantiierungs-Prozessor : function-mapping"/>
<node CREATED="1710858136177" ID="ID_1016066994" MODIFIED="1710858673982" TEXT="logische Signatur des Einzelschrits: (Action, Binding, Ctx) &#x27fc; string"/>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1710858917164" ID="ID_1301011429" MODIFIED="1710859941873" TEXT="praktischer Ansatz: IterableDecorator &#xfc;ber DataBind">
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1710858917164" ID="ID_1301011429" MODIFIED="1710889523599" TEXT="praktischer Ansatz: IterableDecorator einer InscanceCore">
<icon BUILTIN="pencil"/>
<node CREATED="1710859059558" ID="ID_878052078" MODIFIED="1710859508605" TEXT="das ist das konkrete Binding">
<node CREATED="1710859078771" ID="ID_283747990" MODIFIED="1710859082314" TEXT="ist move-only"/>
<node CREATED="1710889530609" ID="ID_1033056147" MODIFIED="1710889545299" TEXT="diese beinhaltet den Interpreter-State"/>
<node CREATED="1710889546263" ID="ID_1579638728" MODIFIED="1710889557098" TEXT="einschlie&#xdf;lich der M&#xf6;glichkeit verschachtelter States"/>
<node CREATED="1710889557878" ID="ID_1797783109" MODIFIED="1710889645948" TEXT="letzteres ist aber als seltene/optionale Erweiterung anzusetzen">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
will sagen, der Standardfall ist, lediglich auf eine Map per Key zuzugreifen &#8212; und die gesamte Datenstruktur ist hierauf zu optimieren
</p>
</body>
</html>
</richcontent>
</node>
<node CREATED="1710859059558" ID="ID_878052078" MODIFIED="1710889700823" TEXT="eingebettete DataSrc &#x2261; das ist das konkrete Binding">
<node CREATED="1710859078771" ID="ID_283747990" MODIFIED="1710889722804" TEXT="ist zuweisbar (f&#xfc;r nesting)"/>
<node CREATED="1710859083131" ID="ID_1103676674" MODIFIED="1710859102540" TEXT="const- referenziert die Daten"/>
<node CREATED="1710859106953" ID="ID_1570382274" MODIFIED="1710859128336" TEXT="implementiert das Binding-Concept"/>
</node>
<node CREATED="1710859515737" ID="ID_1401350564" MODIFIED="1710859521900" TEXT="enth&#xe4;lt eine DataSrc">
<node CREATED="1710859523200" ID="ID_1772496628" MODIFIED="1710859542793" TEXT="diese referenziert die Daten"/>
<node CREATED="1710859544029" ID="ID_1522505977" MODIFIED="1710859550400" TEXT="ist aber assignable"/>
<node CREATED="1710859551122" ID="ID_602535289" MODIFIED="1710859562631" TEXT="und implementiert die Binding-Funktionalit&#xe4;t"/>
</node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1710859064221" ID="ID_190332829" MODIFIED="1710859956662" TEXT="h&#xe4;lt au&#xdf;erdem einen DataSrc::Iter">
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1710859064221" ID="ID_190332829" MODIFIED="1710889769933" TEXT="stellt au&#xdf;erdem einen Stack f&#xfc;r Verschachtelung bereit">
<icon BUILTIN="flag-pink"/>
<node CREATED="1710889849728" ID="ID_399157295" MODIFIED="1710889858546" TEXT="falls leer &#x27f9; einfacher Standardfall"/>
<node CREATED="1710889859678" ID="ID_1032750724" MODIFIED="1710889877399" TEXT="sonst: mu&#xdf; sich jeweils die Vorg&#xe4;nger-DataSrc merken"/>
<node CREATED="1710889878990" ID="ID_936276949" MODIFIED="1710889900005" TEXT="...um diese vor dem pop() wiederherstellen zu k&#xf6;nnen"/>
</node>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1710901560145" ID="ID_783804444" MODIFIED="1710901575026" TEXT="puh .... m&#xfc;hsam">
<icon BUILTIN="smiley-angry"/>
<node CREATED="1710901576799" ID="ID_309524144" MODIFIED="1710901587322" TEXT="&#xfc;berall bei&#xdf;t sich die Katze in den Schwanz"/>
<node CREATED="1710901588837" ID="ID_1966885383" MODIFIED="1710901606703" TEXT="ich will die Implementierungs-Logik m&#xf6;glichst weit nach unten schieben"/>
</node>
<node BACKGROUND_COLOR="#d2beaf" COLOR="#5c4d6e" CREATED="1710898561618" ID="ID_961175411" MODIFIED="1710898581408" TEXT="Optimierung: string_view durch das ganze System durchf&#xe4;deln">
<icon BUILTIN="hourglass"/>
<node CREATED="1710898588958" ID="ID_329918455" MODIFIED="1710898606712" TEXT="es l&#xe4;uft stets auf einen koordinierten Datenzugriff hinaus"/>
<node CREATED="1710898607588" ID="ID_1515577566" MODIFIED="1710898620430" TEXT="und die Datenstruktur wird referenziert w&#xe4;hrend der Instantiierung"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1710898624785" ID="ID_153699526" MODIFIED="1710898818451" TEXT="&#x27f9; theoretisch sollte es m&#xf6;glich sein durch den gesamten Proze&#xdf; hindurch zu referenzieren">
<node CREATED="1710898727250" ID="ID_1040294627" MODIFIED="1710898736225" TEXT="die Keys sind zwar kurz..."/>
<node CREATED="1710898736874" ID="ID_379388881" MODIFIED="1710898751668" TEXT="aber die konstanten Template-Bestandteile sind (sehr) lang"/>
<node CREATED="1710898758439" ID="ID_40861357" MODIFIED="1710898781000" TEXT="mit String-Values w&#xfc;rde zudem auf vielen Ebenen kopiert und Heap-Memory verwaltet"/>
<node CREATED="1710898782460" ID="ID_1183887615" MODIFIED="1710901418729" TEXT="und letztlich verlangt auch das Iterator-Konzept eine Referenz vom &#xbb;core-yield&#xab;">
<arrowlink COLOR="#756ac6" DESTINATION="ID_98335183" ENDARROW="Default" ENDINCLINATION="63;-36;" ID="Arrow_ID_506603112" STARTARROW="None" STARTINCLINATION="-260;10;"/>
</node>
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1710898830750" ID="ID_629945091" MODIFIED="1710898839421" TEXT="ist das auch praktisch machbar?">
<icon BUILTIN="help"/>
</node>
</node>
<node CREATED="1710901352781" ID="ID_98335183" MODIFIED="1710901434035" TEXT="mu&#xdf; es schon deshalb machen, weil das Rendern einmalig bei der Iteration passiert">
<linktarget COLOR="#756ac6" DESTINATION="ID_98335183" ENDARROW="Default" ENDINCLINATION="63;-36;" ID="Arrow_ID_506603112" SOURCE="ID_1183887615" STARTARROW="None" STARTINCLINATION="-260;10;"/>
<icon BUILTIN="messagebox_warning"/>
</node>
</node>
</node>
<node CREATED="1710856784967" ID="ID_201927694" MODIFIED="1710856798759" TEXT="Parsing soll eager sein (wegen Syntax-Fehlern)"/>
</node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1710901479260" ID="ID_1390433932" MODIFIED="1710901499394" TEXT="Action::instantiate() auf Basis des Data-Bindings implementieren">
<icon BUILTIN="flag-pink"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1710901502001" ID="ID_1137356719" MODIFIED="1710901531737" TEXT="hier wird der Kern der Template-Interpretation ausformuliert">
<icon BUILTIN="yes"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1710793352301" ID="ID_1691061928" MODIFIED="1710793355254" TEXT="Bindings">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1710793356309" ID="ID_618241969" MODIFIED="1710793358557" TEXT="Map">
<icon BUILTIN="flag-yellow"/>
<node CREATED="1710804031539" ID="ID_1692068622" MODIFIED="1710804061062" TEXT="der Inhaltstyp wird auf via util::toString() zugegriffen"/>
<node CREATED="1710804031539" ID="ID_1692068622" MODIFIED="1710879172991" TEXT="der Inhaltstyp wird via util::toString() zugegriffen"/>
<node CREATED="1710804062496" ID="ID_377899149" MODIFIED="1710804093937" TEXT="DataSrc ist ein generischer Wrapper um die Map"/>
<node CREATED="1710804167972" ID="ID_200600045" MODIFIED="1710804186992" TEXT="der Iterator ist eine String-View (ein String-Range)"/>
<node CREATED="1710804197208" ID="ID_709716942" MODIFIED="1710804233519" TEXT="Sequenz-Auswertung interpretiert den String als CSV-Zeile"/>
<node CREATED="1710804454510" ID="ID_835685377" MODIFIED="1710804469839" TEXT="die einzelnen Datenelemente werden als Key-Pr&#xe4;fix interpretiert"/>
<node CREATED="1710804454510" ID="ID_835685377" MODIFIED="1710879851836" TEXT="die einzelnen Sequenz-Elemente werden als Key-Pr&#xe4;fix interpretiert"/>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1710804234267" ID="ID_226073889" MODIFIED="1710804261633" TEXT="Verschachtelung wird nicht unterst&#xfc;tzt">
<icon BUILTIN="yes"/>
<icon BUILTIN="messagebox_warning"/>