172 #ifndef LIB_TEXT_TEMPLATE_H 173 #define LIB_TEXT_TEMPLATE_H 196 class TextTemplate_test;
200 using StrView = std::string_view;
211 const string MATCH_DELIMITER = R
"~((?:^|,|;)\s*)~" ; 212 const regex ACCEPT_DATA_ELM {MATCH_DELIMITER + MATCH_DATA_TOKEN};
215 iterNestedKeys (
string key, StrView
const& iterDef)
218 .transform ([key](smatch mat){
return key+
"."+
string{mat[1]}+
"."; });
222 const string MATCH_BINDING_KEY = R
"~(([\w\.]+))~"; 223 const string MATCH_BINDING_VAL = R
"~(([^,;"\s]+)\s*)~"; 224 const string MATCH_QUOTED_VAL = R
"~("([^"]+)"\s*)~"; 225 const string MATCH_BINDING_TOK = MATCH_BINDING_KEY+
"\\s*=\\s*(?:"+MATCH_BINDING_VAL+
"|"+MATCH_QUOTED_VAL+
")";
226 const regex ACCEPT_BINDING_ELM {MATCH_DELIMITER + MATCH_BINDING_TOK};
229 iterBindingSeq (
string const& dataDef)
232 .transform ([&](smatch mat){
return std::make_pair (
string{mat[1]}
233 ,
string{mat[3].matched? mat[3]:mat[2]}); });
238 const string MATCH_SINGLE_KEY =
"[A-Za-z_]+\\w*";
239 const string MATCH_KEY_PATH = MATCH_SINGLE_KEY+
"(?:\\."+MATCH_SINGLE_KEY+
")*";
240 const string MATCH_LOGIC_TOK =
"if|for";
241 const string MATCH_END_TOK =
"end\\s*";
242 const string MATCH_ELSE_TOK =
"else";
243 const string MATCH_SYNTAX =
"("+MATCH_ELSE_TOK+
")|(?:("+MATCH_END_TOK+
")?("+MATCH_LOGIC_TOK+
")\\s*)?("+MATCH_KEY_PATH+
")?";
244 const string MATCH_FIELD =
"\\$\\{\\s*(?:"+MATCH_SYNTAX+
")\\s*\\}";
245 const string MATCH_ESCAPE = R
"~((\\\$))~"; 247 const regex ACCEPT_MARKUP { MATCH_ESCAPE+
"|"+MATCH_FIELD
248 , regex::ECMAScript|regex::optimize
262 Keyword syntax{ESCAPE};
269 parse (
string const& input)
271 auto classify = [rest=StrView(input)]
274 REQUIRE (not mat.empty());
276 auto restAhead = mat.length() + mat.suffix().length();
277 auto pre = rest.length() - restAhead;
278 tag.lead = rest.substr(0, pre);
279 rest = rest.substr(tag.lead.length());
283 rest = rest.substr(1);
286 rest = rest.substr(mat.length());
290 tag.syntax = mat[3].matched? TagSyntax::END_IF : TagSyntax::IF;
293 tag.syntax = mat[3].matched? TagSyntax::END_FOR : TagSyntax::FOR;
299 tag.syntax = TagSyntax::ELSE;
303 " ...%s${end |↯|}"} % tag.lead};
305 tag.syntax = TagSyntax::KEYID;
312 .transform (classify);
320 template<
class DAT,
typename SEL=
void>
346 TEXT, KEY, COND, JUMP, ITER, LOOP
361 using ScopeStack = std::stack<ParseCtx, std::vector<ParseCtx>>;
383 using ActionIter = IterIndex<const ActionSeq>;
384 using DataCtxIter =
typename SRC::Iter;
385 using NestedCtx = std::pair<DataCtxIter, SRC>;
386 using CtxStack = std::stack<NestedCtx, std::vector<NestedCtx>>;
387 using Value =
typename SRC::Value;
390 ActionIter actionIter_;
397 bool checkPoint()
const;
401 Value instantiateNext();
402 Value reInstatiate (
Idx =
Idx(-1));
403 Value getContent(
string key);
404 bool conditional (
string key);
405 bool openIteration (
string key);
415 : actions_{compile (spec)}
420 submit (DAT
const& data)
const;
424 render (DAT
const& data)
const;
428 apply (
string spec, DAT
const& data);
433 static ActionSeq compile (
string const&);
434 friend class test::TextTemplate_test;
460 buildActions (PAR&& parseIter)
464 compile (parseIter, actions);
471 compile (PAR& parseIter,
ActionSeq& actions)
473 auto currIDX = [&]{
return actions.size(); };
474 auto valid = [&](
Idx i){
return 0 < i and i < actions.size(); };
475 auto clause = [](Clause c)->
string {
return c==IF?
"if" :
"for"; };
476 auto scopeClause = [&]{
return scope_.empty()?
"??" : clause(scope_.top().clause); };
479 auto beginIdx = [&]{
return scope_.empty()? 0 : scope_.top().begin; };
480 auto scopeKey = [&]{
return valid(beginIdx())? actions[beginIdx()].val :
"";};
481 auto keyMatch = [&]{
return isnil(parseIter->key) or parseIter->key == scopeKey(); };
482 auto clauseMatch = [&](Clause c){
return not scope_.empty() and scope_.top().clause == c; };
483 auto scopeMatch = [&](Clause c){
return clauseMatch(c) and keyMatch(); };
485 auto lead = [&]{
return parseIter->lead; };
486 auto clashLead = [&]{
return actions[scope_.top().after - 1].val; };
487 auto abbrev = [&](
auto s){
return s.length()<16? s : s.substr(s.length()-15); };
490 auto __requireKey = [&](
string descr)
492 if (isnil (parseIter->key))
494 % abbrev(lead()) % descr
496 auto __checkBalanced = [&](Clause c)
498 if (not scopeMatch(c))
500 " -- found ...%s${end |↯|%s %s}"}
501 % scopeClause() % scopeKey()
503 % clause(c) % parseIter->key
505 auto __checkInScope = [&] {
510 auto __checkNoDup = [&] {
511 if (scope_.top().after != 0)
512 throw error::Invalid{_Fmt{
"Conflicting ...%s${else} ⟷ ...%s|↯|${else}"}
513 % abbrev(clashLead()) % abbrev(lead())};
515 auto __checkClosed = [&] {
516 if (not scope_.empty())
517 throw error::Invalid{_Fmt{
"Unclosed Logic tags: |↯|${end %s %s} missing"}
518 % scopeClause() % scopeKey()};
522 auto add = [&](Code c,
string v){ actions.push_back (
Action{c,v});};
523 auto addCode = [&](Code c) { add ( c, parseIter->key); };
524 auto addLead = [&] { add (
TEXT,
string{parseIter->lead}); };
525 auto openScope = [&](Clause c){ scope_.push (
ParseCtx{c, currIDX()}); };
526 auto closeScope = [&] { scope_.pop(); };
528 auto linkElseToStart = [&]{ actions[beginIdx()].refIDX = currIDX(); };
529 auto markJumpInScope = [&]{ scope_.top().after = currIDX(); };
530 auto linkLoopBack = [&]{ actions.back().refIDX = scope_.top().begin; };
531 auto linkJumpToNext = [&]{ actions[scope_.top().after].refIDX = currIDX(); };
533 auto hasElse = [&]{
return scope_.top().after != 0; };
535 using text_template::TagSyntax;
538 switch (parseIter->syntax) {
539 case TagSyntax::ESCAPE:
542 case TagSyntax::KEYID:
543 __requireKey(
"<placeholder>");
548 __requireKey(
"if <conditional>");
553 case TagSyntax::END_IF:
563 __requireKey(
"for <data-id>");
568 case TagSyntax::END_FOR:
570 __checkBalanced(FOR);
581 case TagSyntax::ELSE:
585 if (IF == scope_.top().clause)
601 NOTREACHED (
"uncovered TagSyntax keyword while compiling a TextTemplate.");
604 StrView tail = parseIter->tail;
608 add (
TEXT,
string{tail});
619 throw error::Invalid (
"TextTemplate spec without active placeholders.");
632 template<
class DAT,
typename SEL>
635 static_assert (not
sizeof(DAT),
636 "unable to bind this data source " 637 "for TextTemplate instantiation");
642 using MapS = std::map<string,string>;
665 MapS
const * data_{
nullptr};
668 bool isSubScope() {
return not isnil (keyPrefix_); }
676 using Value = std::string_view;
677 using Iter = decltype(iterNestedKeys(
"",
""));
680 contains (
string key)
682 return (isSubScope() and util::contains (*data_, keyPrefix_+key))
683 or util::contains (*data_, key);
687 retrieveContent (
string key)
689 MapS::const_iterator elm;
692 elm = data_->find (keyPrefix_+key);
693 if (elm == data_->end())
694 elm = data_->find (key);
697 elm = data_->find (key);
698 ENSURE (elm != data_->end());
703 getSequence (
string key)
705 if (not contains(key))
708 return iterNestedKeys (key, retrieveContent(key));
712 openContext (Iter&
iter)
716 nested.keyPrefix_ += *iter;
722 using PairS = std::pair<string,string>;
735 explore (iterBindingSeq (dataSpec))
736 .foreach([
this](PairS
const& bind){ spec_->insert (bind); });
740 openContext (Iter&
iter)
744 nested.keyPrefix_ = nestedBase.keyPrefix_;
753 template<
class STR,
typename = meta::enable_if<meta::is_StringLike<STR>> >
792 NOTREACHED (
"uncovered Activity verb in activation function.");
801 , actionIter_{actions}
805 rendered_ = instantiateNext();
817 return bool(actionIter_);
824 return unConst(
this)->rendered_;
832 rendered_ = instantiateNext();
838 inline typename SRC::Value
841 return actionIter_? actionIter_->instantiate(*
this)
852 inline typename SRC::Value
855 if (nextCode ==
Idx(-1))
858 actionIter_.setIDX (nextCode);
859 return instantiateNext();
864 inline typename SRC::Value
868 return dataSrc_.contains(key)? dataSrc_.retrieveContent(key) : nil;
876 return not util::isNo (
string{getContent (key)});
894 if (conditional (key))
895 if (DataCtxIter dataIter = dataSrc_.getSequence(key))
897 ctxStack_.push (NestedCtx{move (dataIter)
915 DataCtxIter& dataIter = ctxStack_.top().first;
924 std::swap (dataSrc_, ctxStack_.top().second);
944 REQUIRE (not ctxStack_.empty());
945 NestedCtx& innermostScope = ctxStack_.top();
946 DataCtxIter& currentDataItem = innermostScope.first;
947 SRC& parentDataSrc = innermostScope.second;
949 this->dataSrc_ = parentDataSrc.openContext (currentDataItem);
973 return util::join (submit (data),
"");
989 .filter ([](
Action const& a){
return a.code == KEY or a.code == COND or a.code == ITER; })
990 .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
Iterator-style access handle to a referred container with subscript index.
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
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