// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/> // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. #include "templatizer.h" bool isBlocStart(CTemplatizerParser t) { return (t.isValid() && t[0] == '{' && t[1] == '{'); } bool isBlocEnd(CTemplatizerParser t) { return (t.isValid() && t[0] == '}' && t[1] == '}'); } enum TTemplatizerToken { BlocStart, BlocEnd, OpenParenth, CloseParenth, Equal, Comma, Arobace, Dollar, Quote, CommentStart, CommentEnd, Identifier, ListIdentifier, Unknown }; struct SToken { TTemplatizerToken Token; const char* Text; }; SToken SimpleTokens[] = { { BlocStart, "{" }, { BlocEnd, "}" }, { OpenParenth, "(" }, { CloseParenth, ")" }, { Equal, "=" }, { Arobace, "@" }, { Dollar, "$" }, { Comma, "," }, { Quote, "\"" }, { CommentStart, "/*" }, { CommentEnd, "*/" }, }; CTemplatizerParser skipSpace(CTemplatizerParser t) { while (t.isValid() && *t != '\0' && isspace(*t)) ++t; return t; } const char* skipSpace(const char* t) { while (t != NULL && *t != '\0' && isspace(*t)) ++t; return t; } CTemplatizerParser match(const char* keyword, CTemplatizerParser match) { while (*keyword != '\0' && *match != '\0' && *keyword == *match) { ++keyword; ++match; } return *keyword == '\0' ? match : CTemplatizerParser(); } TTemplatizerToken getToken(CTemplatizerParser& t, bool skipspc, std::string* value = NULL) { if (skipspc) t = skipSpace(t); if (!t.isValid()) return Unknown; uint i; CTemplatizerParser result; for (i=0; i<sizeof(SimpleTokens)/sizeof(SimpleTokens[0]); ++i) { result = match(SimpleTokens[i].Text, t); if (result.isValid()) { t = result; return SimpleTokens[i].Token; } } if (isalpha(*t)) { TTemplatizerToken retToken = Identifier; if (value != NULL) *value = ""; do { while (isalpha(*t)) { if (value != NULL) *value += *t; ++t; } t = skipSpace(t); if (*t != EnvSeparator) break; retToken = ListIdentifier; if (value != NULL) *value += *t; ++t; } while (true); return retToken; } return Unknown; } bool popToken(CTemplatizerParser& t, TTemplatizerToken token, bool skipspc, std::string* value = NULL) { CTemplatizerParser save = t; if (getToken(save, skipspc, value) == token) { t = save; return true; } return false; } bool isNextToken(CTemplatizerParser t, TTemplatizerToken token, bool skipspc, std::string* value = NULL) { return getToken(t, skipspc, value) == token; } /* * Destructor */ CTemplatizerEnv::~CTemplatizerEnv() { clear(); } // Clear Env void CTemplatizerEnv::clear() { TEnvMap::iterator ite; for (ite=Envs.begin(); ite!=Envs.end(); ++ite) delete (*ite).second; TValueMap::iterator itv; for (itv=Values.begin(); itv!=Values.end(); ++itv) delete (*itv).second; Envs.clear(); Values.clear(); } /* * Evaluate string (string replacement) */ std::string CTemplatizerEnv::eval(const std::string& text) { std::string res; const char* ptr = text.c_str(); while (*ptr != '\0') { if (ptr[0] == '$' && ptr[1] == '(') { ptr += 2; std::string var; ptr = skipSpace(ptr); while (isalpha(*ptr) || *ptr == '/' || *ptr == '.') var += *(ptr++); while (*ptr != '\0' && *ptr != ')') ++ptr; if (*ptr == ')') ++ptr; res += get(var); } else if (*ptr != '\r') { res += *(ptr++); } } return res; } /* * Constructor */ ITemplatizerBloc::ITemplatizerBloc() { } /* * Destructor */ ITemplatizerBloc::~ITemplatizerBloc() { uint i; for (i=0; i<Blocs.size(); ++i) delete Blocs[i]; TParamMap::iterator it; for (it=Params.begin(); it!=Params.end(); ++it) delete (*it).second; } /* * Constructor */ CTemplatizer::CTemplatizer() { RootBloc = NULL; RootEnv = NULL; } /* * Destructor */ CTemplatizer::~CTemplatizer() { if (RootBloc != NULL) delete RootBloc; if (RootEnv != NULL) delete RootEnv; } /* * Build templatizer from text */ bool CTemplatizer::build(const char* text) { CTemplatizerParser parser(text); RootBloc = ITemplatizerBloc::parseBloc(parser); RootEnv = new CTemplatizerEnv(NULL); return (RootBloc != NULL); } /** * Evaluate template and render to string */ std::string CTemplatizer::eval() { if (RootBloc != NULL && RootEnv != NULL) return RootBloc->eval(RootEnv); else return ""; } /* * Parse bloc */ ITemplatizerBloc* ITemplatizerBloc::parseBloc(CTemplatizerParser& ptr) { std::string blocType; ITemplatizerBloc* bloc = NULL; if (popToken(ptr, Identifier, true, &blocType) || popToken(ptr, ListIdentifier, true, &blocType)) { if (blocType == "root") bloc = new CTemplatizerRootBloc(); else if (blocType == "sub") bloc = new CTemplatizerSubBloc(); else if (blocType == "loop") bloc = new CTemplatizerLoopBloc(); else if (blocType == "ifdefenv") bloc = new CTemplatizerIfDefEnvBloc(); else if (blocType == "ifdef") bloc = new CTemplatizerIfDefBloc(); else if (blocType == "ifnotdefenv") bloc = new CTemplatizerIfNotDefEnvBloc(); else if (blocType == "ifnotdef") bloc = new CTemplatizerIfNotDefBloc(); else if (blocType == "switch") bloc = new CTemplatizerSwitchBloc(); else if (blocType == "file") bloc = new CTemplatizerFileBloc(); else if (blocType == "set") bloc = new CTemplatizerSetBloc(); else if (blocType == "append") bloc = new CTemplatizerAppendBloc(); else if (blocType == "define") bloc = new CTemplatizerDefineBloc(); else if (blocType == "if") bloc = new CTemplatizerIfBloc(); else if (blocType == "ifnot") bloc = new CTemplatizerIfNotBloc(); else if (blocType == "join") bloc = new CTemplatizerJoinBloc(); else if (blocType == "class") bloc = new CTemplatizerClassBloc(); else if (blocType == "object") bloc = new CTemplatizerObjectBloc(); else if (blocType == "ref") bloc = new CTemplatizerReferenceBloc(); else if (blocType == "refenv") bloc = new CTemplatizerRefEnvBloc(); else if (blocType == "breakpoint") bloc = new CTemplatizerBreakpointBloc(); else bloc = new CTemplatizerUserFunctionBloc(blocType); if (bloc == NULL) { nlwarning("Templatizer: failed to decode bloc '%s' at line %d", blocType.c_str(), ptr.getLine()); return NULL; } ptr = bloc->parseHeader(ptr); if (!ptr.isValid()) { nlwarning("Templatizer: failed to decode header of bloc '%s' at line %d", blocType.c_str(), ptr.getLine()); delete bloc; return NULL; } if (bloc->hasInternal()) { if (!popToken(ptr, BlocStart, true)) { nlwarning("Templatizer: failed to decode start of bloc '%s' at line %d", blocType.c_str(), ptr.getLine()); delete bloc; return NULL; } ptr = bloc->parseInternal(ptr); if (!ptr.isValid()) { nlwarning("Templatizer: failed to parse bloc '%s' at line %d", blocType.c_str(), ptr.getLine()); delete bloc; return NULL; } if (!popToken(ptr, BlocEnd, true)) { nlwarning("Templatizer: failed to decode end of bloc '%s' at line %d", blocType.c_str(), ptr.getLine()); delete bloc; return NULL; } } } else if (isNextToken(ptr, CommentStart, true)) { bloc = new CTemplatizerCommentBloc(); ptr = bloc->parseInternal(ptr); if (!ptr.isValid()) nlwarning("Templatizer: failed to parse bloc 'Comment' at line %d", ptr.getLine()); } else if (isNextToken(ptr, Quote, true)) { bloc = new CTemplatizerTextBloc(); ptr = bloc->parseInternal(ptr); if (!ptr.isValid()) nlwarning("Templatizer: failed to parse bloc 'Text' at line %d", ptr.getLine()); } if (!ptr.isValid()) { delete bloc; return NULL; } return bloc; } /* * Parse bloc header */ CTemplatizerParser ITemplatizerBloc::parseHeader(CTemplatizerParser ptr) { if (popToken(ptr, OpenParenth, true)) { uint currentDefArg = 0; const char** args = getDefParamList(); if (popToken(ptr, CloseParenth, true)) return ptr; do { std::string paramName; if (!popToken(ptr, Identifier, true, ¶mName)) { if (args == NULL || args[currentDefArg] == NULL) { ptr.invalidate(); return ptr; } paramName = args[currentDefArg++]; } else { if (!popToken(ptr, Equal, true)) { ptr.invalidate(); return ptr; } } ITemplatizerBloc* bloc = parseBloc(ptr); if (bloc == NULL) { ptr.invalidate(); return ptr; } Params[paramName] = bloc; if (!popToken(ptr, Comma, true)) break; } while (true); if (!popToken(ptr, CloseParenth, true)) { ptr.invalidate(); return ptr; } } return ptr; } /* * Parse bloc internal data */ CTemplatizerParser ITemplatizerBloc::parseInternal(CTemplatizerParser ptr) { ITemplatizerBloc* bloc = NULL; do { bloc = parseBloc(ptr); if (bloc != NULL) Blocs.push_back(bloc); } while (bloc != NULL && *ptr != '\0' && !isBlocEnd(ptr)); return ptr; } /* * Parse bloc internal data */ CTemplatizerParser CTemplatizerTextBloc::parseInternal(CTemplatizerParser ptr) { Text = ""; ptr = skipSpace(ptr); if (*ptr != '"') return CTemplatizerParser(); ++ptr; while (*ptr != '\0' && *ptr != '"') { if (*ptr == '\\' && ptr[1] != '\0') { ++ptr; switch (*ptr) { case 'n': Text += '\n'; break; case 't': Text += '\t'; break; case '\r': ++ptr; if (*ptr == '\n') ++ptr; break; case '\n': ++ptr; if (*ptr == '\r') ++ptr; break; default: Text += *ptr; break; } ++ptr; } else { if (*ptr != '\r') Text += *ptr; ++ptr; } } if (*ptr != '"') return CTemplatizerParser(); return ++ptr; } /* * Parse bloc internal data */ CTemplatizerParser CTemplatizerCommentBloc::parseInternal(CTemplatizerParser ptr) { ptr = skipSpace(ptr); if (!popToken(ptr, CommentStart, true)) return NULL; while (*ptr != '\0' && !isNextToken(ptr, CommentEnd, false)) ++ptr; if (!popToken(ptr, CommentEnd, false)) { ptr.invalidate(); return ptr; } return ptr; }