Library: define skeleton of TextTemplate compilation

...implemented as »custom processing layer« within a
demand-driven parsing pipeline, with the ability to
inject additional Action-tokens to represent the intermittent
constant text between tags; special handling to expose one
constant postfix after the last active tag.
This commit is contained in:
Fischlurch 2024-03-23 19:38:53 +01:00
parent 5b53b53c4c
commit a9cbe7eb90
4 changed files with 306 additions and 58 deletions

View file

@ -105,6 +105,7 @@
#include "lib/regex.hpp"
#include "lib/util.hpp"
#include <optional>
#include <string>
#include <vector>
#include <stack>
@ -113,6 +114,8 @@
namespace lib {
using std::optional;
using std::nullopt;
using std::string;
using StrView = std::string_view;
@ -149,8 +152,9 @@ namespace lib {
, END_FOR
, ELSE
};
Keyword syntaxCase{ESCAPE};
Keyword syntax{ESCAPE};
StrView lead;
StrView tail;
string key;
};
@ -166,30 +170,33 @@ namespace lib {
auto pre = rest.length() - restAhead;
tag.lead = rest.substr(0, pre);
rest = rest.substr(tag.lead.length());
if (mat[1].matched)
return tag;
if (mat[5].matched)
tag.key = mat[5];
if (mat[4].matched)
{ // detected a logic keyword...
if ("if" == mat[4])
tag.syntaxCase = mat[5].matched? TagSyntax::END_IF : TagSyntax::IF;
if (not mat[1].matched)
{ // not escaped but indeed active field
rest = rest.substr(mat.length());
if (mat[4].matched)
{ // detected a logic keyword...
if ("if" == mat[4])
tag.syntax = mat[3].matched? TagSyntax::END_IF : TagSyntax::IF;
else
if ("for" == mat[4])
tag.syntax = mat[3].matched? TagSyntax::END_FOR : TagSyntax::FOR;
else
throw error::Logic("unexpected keyword");
}
else
if ("for" == mat[4])
tag.syntaxCase = mat[5].matched? TagSyntax::END_FOR : TagSyntax::FOR;
if (mat[2].matched)
tag.syntax = TagSyntax::ELSE;
else
throw error::Logic("unexpected keyword");
tag.syntax = TagSyntax::KEYID;
}
else
if (mat[3].matched)
tag.syntaxCase = TagSyntax::ELSE;
else
tag.syntaxCase = TagSyntax::KEYID;
tag.tail = rest;
return tag;
};
return explore (util::RegexSearchIter{input, ACCEPT_MARKUP})
.transform(classify);
.transform (classify);
}
}
@ -224,7 +231,7 @@ namespace lib {
struct Action
{
Code code{TEXT};
string val{""};
string val{};
Idx refIDX{0};
template<class SRC>
@ -234,7 +241,11 @@ namespace lib {
/** the text template is compiled into a sequence of Actions */
using ActionSeq = std::vector<Action>;
/** processor in a parse pipeline — yields sequence of Actions */
template<class PAR>
class ActionCompiler;
/** Binding to a specific data source.
* @note requires partial specialisation */
template<class DAT, typename SEL=void>
@ -283,6 +294,89 @@ namespace lib {
/* ======= Parser / Compiler pipeline ======= */
/**
* @remarks this is a »custom processing layer«
* to be used in an [Iter-Explorer](\ref iter-explorer.hpp)-pipeline.
* The source layer (which is assumed to comply to the »State Core« concept),
* yields TagSyntax records, one for each match of the ACCEPT_MARKUP reg-exp.
* The actual compilation step, which is implemented as pull-processing here,
* will emit one or several Action tokens on each match, thereby embedding the
* extracted keys and possibly static fill strings. Since the _performance_ allows
* for conditionals and iteration, some cross-linking is necessary, based on index
* numbers for the actions emitted and coordinated by a stack of bracketing constructs.
*/
template<class PAR>
class TextTemplate::ActionCompiler
{
Idx idx_{0};
Action currToken_{};
optional<StrView> post_{nullopt};
public:
using PAR::PAR;
/* === state core protocol === */
bool
checkPoint() const
{
return PAR::checkPoint()
or bool(post_);
}
Action const&
yield() const
{
return currToken_;
}
void
iterNext()
{
++idx_;
if (post_)
post_ = nullopt;
else
currToken_ = compile();
}
private:
Action
compile()
{ //...throws if exhausted
TagSyntax& tag = PAR::yield();
auto isState = [this](Code c){ return c == currToken_.code; };
auto nextState = [this] {
StrView lead = tag.tail;
PAR::iterNext();
// first expose intermittent text before next tag
if (PAR::checkPoint())
lead = PAR::yield().lead;
else // expose tail after final match
post_ = lead;
return Action{TEXT, lead};
};
switch (tag.syntax) {
case TagSyntax::ESCAPE:
return nextState();
case TagSyntax::KEYID:
if (isState (KEY))
return nextState();
return Action{KEY, tag.key};
case TagSyntax::IF:
case TagSyntax::END_IF:
case TagSyntax::FOR:
case TagSyntax::END_FOR:
default:
NOTREACHED ("uncovered TagSyntax keyword while compiling a TextTemplate.");
}
}
};
/* ======= preconfigured data bindings ======= */
@ -320,6 +414,36 @@ namespace lib {
/* ======= implementation of the instantiation state ======= */
/**
* 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 SRC>
inline StrView
TextTemplate::Action::instantiate (InstanceCore<SRC>& core) const
{
switch (code) {
case TEXT:
return val;
case KEY:
return core.getContent (val);
case COND:
return "";
case JUMP:
return "";
case ITER:
return "";
case LOOP:
return "";
default:
NOTREACHED ("uncovered Activity verb in activation function.");
}
}
template<class SRC>
TextTemplate::InstanceCore<SRC>::InstanceCore (TextTemplate::ActionSeq const& actions, SRC s)
: dataSrc_{s}
@ -375,35 +499,6 @@ namespace lib {
/**
* 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 SRC>
inline StrView
TextTemplate::Action::instantiate (InstanceCore<SRC>& core) const
{
switch (code) {
case TEXT:
return val;
case KEY:
return core.getContent (val);
case COND:
return "";
case JUMP:
return "";
case ITER:
return "";
case LOOP:
return "";
default:
NOTREACHED ("uncovered Activity verb in activation function.");
}
}
/** */

View file

@ -837,7 +837,7 @@ namespace test{
/**
* demo of a custom processing layer
* interacting directly with the iteration mechanism.
* @note we can assume `SRC` is itself a Lumiera Iterator
* @note we can assume `SRC` is itself a Lumiera »State Core«
*/
template<class SRC>
struct MagicTestRubbish

View file

@ -194,7 +194,7 @@ namespace test {
"${two}, \\$, ${if high}"_expect);
auto render = [](TagSyntax& tag) -> string
{ return _Fmt{"▶%s‖%d|%s‖▷"} % string{tag.lead} % uint(tag.syntaxCase) % tag.key; };
{ return _Fmt{"▶%s‖%d|%s‖▷"} % string{tag.lead} % uint(tag.syntax) % tag.key; };
auto wau = parse(input)
.transform(render);

View file

@ -112558,8 +112558,7 @@ std::cout &lt;&lt; tmpl.render({&quot;what&quot;, &quot;World&quot;}) &lt;&lt; s
&#252;ber einen freien Erweiterungspunkt? &#10233; die optimal flexible L&#246;sung, aber trickreich zu realisieren und schwer zuverl&#228;ssig zu steuern
</p>
</body>
</html>
</richcontent>
</html></richcontent>
</node>
</node>
<node CREATED="1711050681983" ID="ID_1452056641" MODIFIED="1711050729214" TEXT="gebe stattdessen einen literalen leeren String"/>
@ -112736,7 +112735,45 @@ std::cout &lt;&lt; tmpl.render({&quot;what&quot;, &quot;World&quot;}) &lt;&lt; s
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1710799920412" ID="ID_1195498496" MODIFIED="1710800023758" TEXT="parsing">
<icon BUILTIN="flag-yellow"/>
<node CREATED="1710804975672" ID="ID_1778438879" MODIFIED="1710804998137" TEXT="Reg-Exp-Iteration auf die Tag-Syntax"/>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1710804975672" ID="ID_1778438879" MODIFIED="1711211821132" TEXT="Reg-Exp-Iteration auf die Tag-Syntax">
<icon BUILTIN="pencil"/>
<node COLOR="#338800" CREATED="1711211822654" FOLDED="true" ID="ID_1575734676" LINK="#ID_1871157756" MODIFIED="1711212214143" TEXT="Layer-1 : &#xdc;bersetzung in Syntax-F&#xe4;lle">
<icon BUILTIN="button_ok"/>
<node CREATED="1711212050899" ID="ID_573965593" MODIFIED="1711212112634" TEXT="gibt jeweils einen TagSyntax-Record aus">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
...dieser enth&#228;lt die Informationen aus dem RegExp-Match bereits semantisch aufgeschl&#252;sselt
</p>
</body>
</html>
</richcontent>
</node>
<node CREATED="1711212113875" ID="ID_842279818" MODIFIED="1711212212420" TEXT="zudem wird eine Anschlu&#xdf;position im Functor &#xbb;versteckt mitgeschleppt&#xab;">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
...das hei&#223;t, die einzelne Auswertung ist keine <i>pure function </i>&#8212; aber der Seiteneffekt-Stat verbleibt in der Pipeline selber und merkt sich den Endpunkt des vorausgehenden Matches
</p>
</body>
</html>
</richcontent>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1711211835240" ID="ID_1594550860" LINK="#ID_1703324697" MODIFIED="1711211884546" TEXT="Layer-2 : Behandeln und Ausfalten dieser F&#xe4;lle">
<icon BUILTIN="flag-yellow"/>
<node CREATED="1711212223692" ID="ID_1386017379" MODIFIED="1711212229862" TEXT="ein custom-processing-Layer"/>
<node CREATED="1711212230571" ID="ID_1706127132" MODIFIED="1711212236414" TEXT="beinhaltet eine State-Machine"/>
<node CREATED="1711212237363" ID="ID_98431449" MODIFIED="1711212249045" TEXT="und einen Stack f&#xfc;r Klammer-Konstrukte"/>
<node CREATED="1711212252456" ID="ID_1957416037" MODIFIED="1711212264479" TEXT="pull-processing auf iterNext()"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1710804999413" ID="ID_670708059" MODIFIED="1710805006332" TEXT="handzuhabende Situationen">
<icon BUILTIN="flag-yellow"/>
<node CREATED="1710805054381" ID="ID_1600465677" MODIFIED="1710805058600" TEXT="erstes Tag">
@ -113083,32 +113120,148 @@ std::cout &lt;&lt; tmpl.render({&quot;what&quot;, &quot;World&quot;}) &lt;&lt; s
<node CREATED="1711158213519" ID="ID_367034320" MODIFIED="1711158228179" TEXT="der sub-Match hat nur einen operator-string()">
<icon BUILTIN="info"/>
</node>
<node CREATED="1711209038541" ID="ID_363063181" MODIFIED="1711209133550">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p>
man k&#246;nnte wohl was basteln mit den Funktionen <font color="#8b1212" face="Monospaced">position(i)</font>&#160;und <font color="#8b1212" face="Monospaced">length(i)</font>
</p>
</body>
</html>
</richcontent>
</node>
</node>
<node CREATED="1711158230860" ID="ID_421709614" MODIFIED="1711158244517" TEXT="also dann halt gleich als String speichern"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1711158258547" ID="ID_431240403" MODIFIED="1711158398896" TEXT="mu&#xdf; das letzte Postfix ohne Match finden">
<linktarget COLOR="#b72676" DESTINATION="ID_431240403" ENDARROW="Default" ENDINCLINATION="-233;16;" ID="Arrow_ID_232745747" SOURCE="ID_1998845229" STARTARROW="None" STARTINCLINATION="170;-14;"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1711158258547" ID="ID_431240403" MODIFIED="1711209191700" TEXT="mu&#xdf; das letzte Postfix ohne Match finden">
<linktarget COLOR="#b72676" DESTINATION="ID_431240403" ENDARROW="Default" ENDINCLINATION="-189;13;" ID="Arrow_ID_232745747" SOURCE="ID_1998845229" STARTARROW="None" STARTINCLINATION="170;-14;"/>
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1711158504002" ID="ID_1524679089" MODIFIED="1711158524645" TEXT="Idee: pseudo-Match ganz auf das Ende der Quelle">
<node CREATED="1711206740722" ID="ID_669120425" MODIFIED="1711206968242" TEXT="gute L&#xf6;sung h&#xe4;ngt stark von der intendierten Verwendung ab">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
...und diese soll <i>irgendwie</i>&#160;auf eine Pipeline aufbauen. Das bedeutet, die L&#246;sung sollte m&#246;glichst <i>in der Verarbeitung selber </i>zug&#228;nglich sein, und nicht &#252;ber eine externe Zusatz-Information oder einen Seiteneffekt. Es w&#228;re denkbar, auf das Ende des letzten Match aufzubauen &#8212; allerdings noch viel sch&#246;ner w&#228;re es, wenn der letzte Match den Quell-String <i>komplett aussch&#246;pft, </i>so da&#223; gar kein Rest &#252;brig bleibt
</p>
</body>
</html>
</richcontent>
</node>
<node COLOR="#5b280f" CREATED="1711158504002" ID="ID_1524679089" MODIFIED="1711211519363" TEXT="Idee: pseudo-Match ganz auf das Ende der Quelle">
<icon BUILTIN="idea"/>
<icon BUILTIN="button_cancel"/>
<node CREATED="1711211522235" ID="ID_1955150565" MODIFIED="1711211532437" TEXT="pfiffig &#x2014; aber nicht gut"/>
<node CREATED="1711211533385" ID="ID_306654164" MODIFIED="1711211543099" TEXT="weicht die Semantik eines Match auf"/>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1711211553358" ID="ID_649407235" MODIFIED="1711211681159" TEXT="der n&#xe4;chste &#xdc;bersetzungs-Schritt mu&#xdf; ohnehin zus&#xe4;tziche Token injizieren">
<arrowlink COLOR="#8a7986" DESTINATION="ID_381376485" ENDARROW="Default" ENDINCLINATION="59;-62;" ID="Arrow_ID_1443688910" STARTARROW="None" STARTINCLINATION="-219;8;"/>
<icon BUILTIN="idea"/>
<node CREATED="1711211697867" ID="ID_1270227186" MODIFIED="1711211718485" TEXT="damit pa&#xdf;t eine solche Spezial-Behandlung viel besser dorthin">
<icon BUILTIN="yes"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1711211720040" ID="ID_1750995315" MODIFIED="1711211788529" TEXT="man m&#xfc;&#xdf;te nur einen continuation-Iterator durchgeben">
<icon BUILTIN="flag-yellow"/>
</node>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1711158657004" ID="ID_1183584439" MODIFIED="1711158688520" TEXT="Vorsicht: string-view ist gef&#xe4;hrlich &#x2014; wirklich sinnvoll?">
<icon BUILTIN="help"/>
<node CREATED="1711158703398" ID="ID_338603175" MODIFIED="1711158746068" TEXT="in den Command-Tokens werden ohnehin Strings gespeichert"/>
<node CREATED="1711158703398" ID="ID_338603175" MODIFIED="1711211670624" TEXT="in den Command-Tokens werden ohnehin Strings gespeichert"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1711158747109" ID="ID_514130753" MODIFIED="1711158769304" TEXT="nochmal explizit durchverfolgen wo &#xbb;materialisiert&#xab; werden soll">
<icon BUILTIN="yes"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1711211350569" ID="ID_1703324697" MODIFIED="1711211362297" TEXT="&#xdc;bersetzung in eine Action-Sequenz">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1711211369094" ID="ID_381376485" MODIFIED="1711211681159" TEXT="Achtung: mu&#xdf; die Iteration &#xbb;rekursiv auffalten&#xab;">
<linktarget COLOR="#8a7986" DESTINATION="ID_381376485" ENDARROW="Default" ENDINCLINATION="59;-62;" ID="Arrow_ID_1443688910" SOURCE="ID_649407235" STARTARROW="None" STARTINCLINATION="-219;8;"/>
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1711211412952" ID="ID_1907051261" MODIFIED="1711211437713" TEXT="in diversen F&#xe4;llen erzeugt ein parse-Match mehrere Action-Token"/>
<node CREATED="1711212281357" ID="ID_888907782" MODIFIED="1711212311261" TEXT="zudem ist das nicht nur eine Sate-Machine, sondern sogar ein Finite-Automaton with Stack"/>
<node CREATED="1711212325687" ID="ID_1747255881" MODIFIED="1711212343572" TEXT="kann man aber in ein pull-Processing verwandeln">
<icon BUILTIN="idea"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1711212348956" ID="ID_363295345" MODIFIED="1711212359403" TEXT="custom-processing-Layer schaffen">
<icon BUILTIN="flag-yellow"/>
<node CREATED="1711212365801" ID="ID_537344075" MODIFIED="1711212378412" TEXT="dieser enth&#xe4;lt die eigentliche compile-Logik"/>
<node CREATED="1711212381344" ID="ID_1526201544" MODIFIED="1711212412343" TEXT="&#x27f9; wird eine nested template in der eigentlichen Engine"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1711212414388" ID="ID_21802142" MODIFIED="1711212493609" TEXT="mu&#xdf; &#xbb;State-Core&#xab;-Konzept implementieren &#x2014; Behandlung h&#xe4;ngt an iterNext()">
<icon BUILTIN="flag-yellow"/>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1711218612775" ID="ID_1789122488" MODIFIED="1711218978734" TEXT="State-Machine implementieren">
<icon BUILTIN="pencil"/>
<node COLOR="#435e98" CREATED="1711218621758" ID="ID_441666770" MODIFIED="1711218632180" TEXT="Funktion compile()">
<icon BUILTIN="info"/>
</node>
<node COLOR="#338800" CREATED="1711218635275" ID="ID_567626918" MODIFIED="1711218954571" TEXT="zweistufige Entscheidung">
<icon BUILTIN="button_ok"/>
<node CREATED="1711218644620" ID="ID_193795202" MODIFIED="1711218667627" TEXT="basierend auf dem aktuell ausgegebenen Action-Token"/>
<node CREATED="1711218668263" ID="ID_1000340819" MODIFIED="1711218684833" TEXT="plus zus&#xe4;tzlich dem im Basis-Iter sichtbaren Syntax-Match"/>
</node>
<node COLOR="#338800" CREATED="1711218687005" ID="ID_268208820" MODIFIED="1711218952954" TEXT="bei Weiterschalten...">
<icon BUILTIN="button_ok"/>
<node CREATED="1711218708158" ID="ID_614509520" MODIFIED="1711218720643" TEXT="wird stets zun&#xe4;chst eine Action-TEXT emittiert"/>
<node CREATED="1711218721440" ID="ID_676740256" MODIFIED="1711218738637" TEXT="diese exponiert den lead-Text (zwischen den Tags)"/>
<node CREATED="1711218743589" ID="ID_1099243535" MODIFIED="1711218795646">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p>
erst in einem zweiten Schritt wird explizit eine spezifische Action f&#252;r <i>diese Syntax </i>emittiert
</p>
</body>
</html>
</richcontent>
</node>
</node>
<node COLOR="#338800" CREATED="1711218803245" ID="ID_843446113" MODIFIED="1711218950661" TEXT="Spezialfall &#xbb;Ende&#xab;">
<icon BUILTIN="button_ok"/>
<node CREATED="1711218813985" ID="ID_1359111172" MODIFIED="1711218820291" TEXT="es gibt einen optional-tail"/>
<node CREATED="1711218821082" ID="ID_1017969918" MODIFIED="1711218843881" TEXT="dieser wird nur aktiviert, wenn das Weiterschalten den Basis(Syntax)-Iterartor verbraucht hat"/>
<node CREATED="1711218847503" ID="ID_730765448" MODIFIED="1711218910259">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p>
in diesem speziellen Fall wird das verbleibende Postfix
</p>
<p>
vom letzten beobachteten Syntax-Match als TEXT-lead ausgegeben
</p>
</body>
</html>
</richcontent>
</node>
<node CREATED="1711218915070" ID="ID_201326654" MODIFIED="1711218937703" TEXT="iterNext() erkennt das &#x27f9; schaltet den optional-tail wieder weg"/>
<node CREATED="1711218938586" ID="ID_253133952" MODIFIED="1711218948260" TEXT="danach: Trap-door">
<icon BUILTIN="idea"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1711218958736" ID="ID_263202088" MODIFIED="1711218975015" TEXT="Klammer-Konstrukte">
<icon BUILTIN="flag-yellow"/>
</node>
</node>
</node>
</node>
<node CREATED="1711158288320" ID="ID_309368656" MODIFIED="1711158292632" TEXT="Test....">
<node CREATED="1711158297133" ID="ID_788373690" MODIFIED="1711158307246" TEXT="Iteration als Solche ist m&#xf6;glich"/>
<node BACKGROUND_COLOR="#fafe99" COLOR="#fa002a" CREATED="1711158308121" ID="ID_1938624994" MODIFIED="1711158363263" TEXT="K&#xfc;rzen des `lead` mu&#xdf; das Pattern selber &#xfc;berspringen">
<icon BUILTIN="broken-line"/>
</node>
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1711158373801" ID="ID_1998845229" MODIFIED="1711158405321" TEXT="verliere das letzte Postfix">
<arrowlink COLOR="#b72676" DESTINATION="ID_431240403" ENDARROW="Default" ENDINCLINATION="-233;16;" ID="Arrow_ID_232745747" STARTARROW="None" STARTINCLINATION="170;-14;"/>
<node BACKGROUND_COLOR="#f8f1cb" COLOR="#a50125" CREATED="1711158373801" ID="ID_1998845229" MODIFIED="1711209191700" TEXT="verliere das letzte Postfix">
<arrowlink COLOR="#b72676" DESTINATION="ID_431240403" ENDARROW="Default" ENDINCLINATION="-189;13;" ID="Arrow_ID_232745747" STARTARROW="None" STARTINCLINATION="170;-14;"/>
<icon BUILTIN="messagebox_warning"/>
</node>
</node>