163 #ifndef LIB_TEXT_TEMPLATE_H 164 #define LIB_TEXT_TEMPLATE_H 187 class TextTemplate_test;
191 using StrView = std::string_view;
202 const string MATCH_DELIMITER = R
"~((?:^|,|;)\s*)~" ; 203 const regex ACCEPT_DATA_ELM {MATCH_DELIMITER + MATCH_DATA_TOKEN};
206 iterNestedKeys (
string key, StrView
const& iterDef)
209 .transform ([key](smatch mat){
return key+
"."+
string{mat[1]}+
"."; });
213 const string MATCH_BINDING_KEY = R
"~(([\w\.]+))~"; 214 const string MATCH_BINDING_VAL = R
"~(([^,;"\s]+)\s*)~"; 215 const string MATCH_QUOTED_VAL = R
"~("([^"]+)"\s*)~"; 216 const string MATCH_BINDING_TOK = MATCH_BINDING_KEY+
"\\s*=\\s*(?:"+MATCH_BINDING_VAL+
"|"+MATCH_QUOTED_VAL+
")";
217 const regex ACCEPT_BINDING_ELM {MATCH_DELIMITER + MATCH_BINDING_TOK};
220 iterBindingSeq (
string const& dataDef)
223 .transform ([&](smatch mat){
return std::make_pair (
string{mat[1]}
224 ,
string{mat[3].matched? mat[3]:mat[2]}); });
229 const string MATCH_SINGLE_KEY =
"[A-Za-z_]+\\w*";
230 const string MATCH_KEY_PATH = MATCH_SINGLE_KEY+
"(?:\\."+MATCH_SINGLE_KEY+
")*";
231 const string MATCH_LOGIC_TOK =
"if|for";
232 const string MATCH_END_TOK =
"end\\s*";
233 const string MATCH_ELSE_TOK =
"else";
234 const string MATCH_SYNTAX =
"("+MATCH_ELSE_TOK+
")|(?:("+MATCH_END_TOK+
")?("+MATCH_LOGIC_TOK+
")\\s*)?("+MATCH_KEY_PATH+
")?";
235 const string MATCH_FIELD =
"\\$\\{\\s*(?:"+MATCH_SYNTAX+
")\\s*\\}";
236 const string MATCH_ESCAPE = R
"~((\\\$))~"; 238 const regex ACCEPT_MARKUP { MATCH_ESCAPE+
"|"+MATCH_FIELD
239 , regex::ECMAScript|regex::optimize
253 Keyword syntax{ESCAPE};
260 parse (
string const& input)
262 auto classify = [rest=StrView(input)]
265 REQUIRE (not mat.empty());
267 auto restAhead = mat.length() + mat.suffix().length();
268 auto pre = rest.length() - restAhead;
269 tag.lead = rest.substr(0, pre);
270 rest = rest.substr(tag.lead.length());
274 rest = rest.substr(1);
277 rest = rest.substr(mat.length());
281 tag.syntax = mat[3].matched? TagSyntax::END_IF : TagSyntax::IF;
284 tag.syntax = mat[3].matched? TagSyntax::END_FOR : TagSyntax::FOR;
290 tag.syntax = TagSyntax::ELSE;
294 " ...%s${end |↯|}"} % tag.lead};
296 tag.syntax = TagSyntax::KEYID;
303 .transform (classify);
311 template<
class DAT,
typename SEL=
void>
337 TEXT, KEY, COND, JUMP, ITER, LOOP
352 using ScopeStack = std::stack<ParseCtx, std::vector<ParseCtx>>;
374 using ActionIter = IndexIter<const ActionSeq>;
375 using DataCtxIter =
typename SRC::Iter;
376 using NestedCtx = std::pair<DataCtxIter, SRC>;
377 using CtxStack = std::stack<NestedCtx, std::vector<NestedCtx>>;
378 using Value =
typename SRC::Value;
381 ActionIter actionIter_;
388 bool checkPoint()
const;
392 Value instantiateNext();
393 Value reInstatiate (
Idx =
Idx(-1));
394 Value getContent(
string key);
395 bool conditional (
string key);
396 bool openIteration (
string key);
406 : actions_{compile (spec)}
411 submit (DAT
const& data)
const;
415 render (DAT
const& data)
const;
419 apply (
string spec, DAT
const& data);
424 static ActionSeq compile (
string const&);
425 friend class test::TextTemplate_test;
451 buildActions (PAR&& parseIter)
455 compile (parseIter, actions);
462 compile (PAR& parseIter,
ActionSeq& actions)
464 auto currIDX = [&]{
return actions.size(); };
465 auto valid = [&](
Idx i){
return 0 < i and i < actions.size(); };
466 auto clause = [](Clause c)->
string {
return c==IF?
"if" :
"for"; };
467 auto scopeClause = [&]{
return scope_.empty()?
"??" : clause(scope_.top().clause); };
470 auto beginIdx = [&]{
return scope_.empty()? 0 : scope_.top().begin; };
471 auto scopeKey = [&]{
return valid(beginIdx())? actions[beginIdx()].val :
"";};
472 auto keyMatch = [&]{
return isnil(parseIter->key) or parseIter->key == scopeKey(); };
473 auto clauseMatch = [&](Clause c){
return not scope_.empty() and scope_.top().clause == c; };
474 auto scopeMatch = [&](Clause c){
return clauseMatch(c) and keyMatch(); };
476 auto lead = [&]{
return parseIter->lead; };
477 auto clashLead = [&]{
return actions[scope_.top().after - 1].val; };
478 auto abbrev = [&](
auto s){
return s.length()<16? s : s.substr(s.length()-15); };
481 auto __requireKey = [&](
string descr)
483 if (isnil (parseIter->key))
485 % abbrev(lead()) % descr
487 auto __checkBalanced = [&](Clause c)
489 if (not scopeMatch(c))
491 " -- found ...%s${end |↯|%s %s}"}
492 % scopeClause() % scopeKey()
494 % clause(c) % parseIter->key
496 auto __checkInScope = [&] {
501 auto __checkNoDup = [&] {
502 if (scope_.top().after != 0)
503 throw error::Invalid{_Fmt{
"Conflicting ...%s${else} ⟷ ...%s|↯|${else}"}
504 % abbrev(clashLead()) % abbrev(lead())};
506 auto __checkClosed = [&] {
507 if (not scope_.empty())
508 throw error::Invalid{_Fmt{
"Unclosed Logic tags: |↯|${end %s %s} missing"}
509 % scopeClause() % scopeKey()};
513 auto add = [&](Code c,
string v){ actions.push_back (
Action{c,v});};
514 auto addCode = [&](Code c) { add ( c, parseIter->key); };
515 auto addLead = [&] { add (
TEXT,
string{parseIter->lead}); };
516 auto openScope = [&](Clause c){ scope_.push (
ParseCtx{c, currIDX()}); };
517 auto closeScope = [&] { scope_.pop(); };
519 auto linkElseToStart = [&]{ actions[beginIdx()].refIDX = currIDX(); };
520 auto markJumpInScope = [&]{ scope_.top().after = currIDX(); };
521 auto linkLoopBack = [&]{ actions.back().refIDX = scope_.top().begin; };
522 auto linkJumpToNext = [&]{ actions[scope_.top().after].refIDX = currIDX(); };
524 auto hasElse = [&]{
return scope_.top().after != 0; };
526 using text_template::TagSyntax;
529 switch (parseIter->syntax) {
530 case TagSyntax::ESCAPE:
533 case TagSyntax::KEYID:
534 __requireKey(
"<placeholder>");
539 __requireKey(
"if <conditional>");
544 case TagSyntax::END_IF:
554 __requireKey(
"for <data-id>");
559 case TagSyntax::END_FOR:
561 __checkBalanced(FOR);
572 case TagSyntax::ELSE:
576 if (IF == scope_.top().clause)
592 NOTREACHED (
"uncovered TagSyntax keyword while compiling a TextTemplate.");
595 StrView tail = parseIter->tail;
599 add (
TEXT,
string{tail});
610 throw error::Invalid (
"TextTemplate spec without active placeholders.");
623 template<
class DAT,
typename SEL>
626 static_assert (not
sizeof(DAT),
627 "unable to bind this data source " 628 "for TextTemplate instantiation");
633 using MapS = std::map<string,string>;
656 MapS
const * data_{
nullptr};
659 bool isSubScope() {
return not isnil (keyPrefix_); }
667 using Value = std::string_view;
668 using Iter = decltype(iterNestedKeys(
"",
""));
671 contains (
string key)
673 return (isSubScope() and util::contains (*data_, keyPrefix_+key))
674 or util::contains (*data_, key);
678 retrieveContent (
string key)
680 MapS::const_iterator elm;
683 elm = data_->find (keyPrefix_+key);
684 if (elm == data_->end())
685 elm = data_->find (key);
688 elm = data_->find (key);
689 ENSURE (elm != data_->end());
694 getSequence (
string key)
696 if (not contains(key))
699 return iterNestedKeys (key, retrieveContent(key));
703 openContext (Iter&
iter)
707 nested.keyPrefix_ += *iter;
713 using PairS = std::pair<string,string>;
726 explore (iterBindingSeq (dataSpec))
727 .foreach([
this](PairS
const& bind){ spec_->insert (bind); });
731 openContext (Iter&
iter)
735 nested.keyPrefix_ = nestedBase.keyPrefix_;
744 template<
class STR,
typename = meta::enable_if<meta::is_StringLike<STR>> >
783 NOTREACHED (
"uncovered Activity verb in activation function.");
792 , actionIter_{actions}
796 rendered_ = instantiateNext();
808 return bool(actionIter_);
815 return unConst(
this)->rendered_;
823 rendered_ = instantiateNext();
829 inline typename SRC::Value
832 return actionIter_? actionIter_->instantiate(*
this)
843 inline typename SRC::Value
846 if (nextCode ==
Idx(-1))
849 actionIter_.setIDX (nextCode);
850 return instantiateNext();
855 inline typename SRC::Value
859 return dataSrc_.contains(key)? dataSrc_.retrieveContent(key) : nil;
867 return not util::isNo (
string{getContent (key)});
885 if (conditional (key))
886 if (DataCtxIter dataIter = dataSrc_.getSequence(key))
888 ctxStack_.push (NestedCtx{move (dataIter)
906 DataCtxIter& dataIter = ctxStack_.top().first;
916 swap (dataSrc_, ctxStack_.top().second);
936 REQUIRE (not ctxStack_.empty());
937 NestedCtx& innermostScope = ctxStack_.top();
938 DataCtxIter& currentDataItem = innermostScope.first;
939 SRC& parentDataSrc = innermostScope.second;
941 this->dataSrc_ = parentDataSrc.openContext (currentDataItem);
965 return util::join (submit (data),
"");
981 .filter ([](
Action const& a){
return a.code == KEY or a.code == COND or a.code == ITER; })
982 .transform([](
Action const& a){
return a.val; });
Binding to a specific data source.
bool checkPoint() const
TextTemplate instantiation: check point on rendered Action.
string render(DataCap const &)
Result(VAL &&) -> Result< VAL >
deduction guide: allow perfect forwarding of a any result into the ctor call.
auto explore(IT &&srcSeq)
start building a IterExplorer by suitably wrapping the given iterable source.
Adapter for the Map-Data-Source to consume a string spec (for testing)
bool conditional(string key)
retrieve a data value for the key and interpret it as boolean expression
Types marked with this mix-in may be moved but not copied.
bool openIteration(string key)
Attempt to open data sequence by evaluating the entrance key.
Text template substitution engine.
auto submit(DAT const &data) const
Instantiate this (pre-compiled) TextTemplate using the given data binding.
A front-end for using printf-style formatting.
Implementation namespace for support and library code.
Iterator »State Core« to process the template instantiation.
Derived specific exceptions within Lumiera's exception hierarchy.
bool loopFurther()
Possibly continue the iteration within an already established nested scope.
Mix-Ins to allow or prohibit various degrees of copying and cloning.
string render(DAT const &data) const
submit data and materialise rendered results into a single string
Iterator-style access handle to a referred container with subscript index.
Value instantiateNext()
Instantiate next Action token and expose its rendering.
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...
auto instantiate(InstanceCore< SRC > &) const
Interpret an action token from the compiled text template based on the given data binding and iterati...
Value reInstatiate(Idx=Idx(-1))
relocate to another Action token and continue instantiation there
static ActionSeq compile(string const &)
auto keys() const
diagnostics: query a list of all active keys expected by the template.
Lumiera error handling (C++ interface).
wrapped regex iterator to allow usage in foreach loops
static string apply(string spec, DAT const &data)
one-shot shorthand: compile a template and apply it to the given data
const string MATCH_DATA_TOKEN
< Parser and DataSource binding for lib::TextTemplate
std::vector< Action > ActionSeq
the text template is compiled into a sequence of Actions
void focusNested()
Step down into the innermost data item context, prepared at the top of #ctxStack_.
Convenience wrappers and helpers for dealing with regular expressions.
Value getContent(string key)
retrieve a data value from the data source for the indiated key
Building tree expanding and backtracking evaluations within hierarchical scopes.
size_t Idx
cross-references by index number