luaでPHPライクにwebに組み込めるテンプレートを作ってみた。
luaを使って webアプリ作れれば面白くね?
と、いうことで、 PHPのノリで lua を使えるテンプレートを作ってみました。
<html> <body> <?lua for(i=1,99){ local key=""..i..""; if( not out["records"][key]){ break; } local value = out["records"][key]; ?> <div class="img" id="img-1"> <div><a href="javascript:void(0)" onclick="mediaplay(<?= key ?>)"> <img width="255" height="255" src='data:image/jpeg;base64,<?= value["image"] ?>'><br /> <div class="msg" ><span class="select_mark"><?= key ?></span><?= value["title"] ?></div> </a></div> </div> <?lua } ?> </body> <html>
こんなふうに書くと、 lua プログラムに変換した文字列にしてくれる関数です。
変換したファイル名と、この構文を格納する関数名を入れれば、
functionname(out) { } という関数が手に入ります。
それを呼べば htmlが出力されるといった感じです。
outは、このhtmlテンプレートが利用する文字列を保持しているテーブルです。
PHPでいう htmlspecialchars などを施してあげるなどすれば、自動タグエスケープなどもできますね。
std::string convertTemplate(const std::string & filename,const std::string functionname) { enum TPL_STATE { TPL_STATE_HTML ,TPL_STATE_CODE ,TPL_STATE_DOUBLE_QUOTE ,TPL_STATE_SINGLE_QUOTE }; TPL_STATE state = TPL_STATE_HTML; std::ostringstream out; out << "function " << functionname << "(out) "; out << "print( [["; const char * lineCommentStart = NULL; bool easy_echo = false; std::ifstream TPL( filename ); std::string line; while( std::getline(TPL,line) ) { lineCommentStart = NULL; const char * p = line.c_str(); const char * start = p; for( ; *p ; ++p ) { if (state == TPL_STATE_HTML) { if (*p == '<' && *(p+1) == '?') { if (*(p+2) == '=') { //<?= state = TPL_STATE_CODE; out << std::string(start , 0 , (int)(p - start) ) << "]] ); print("; start = p + 2 + 1; p+=2; easy_echo = true; lineCommentStart = NULL; } else if (*(p+2) == 'l' && *(p+3) == 'u' && *(p+4) == 'a') { //<?lua state = TPL_STATE_CODE; out << std::string(start , 0 , (int)(p - start) ) << "]] ); "; start = p + 4 + 1; p+=4; easy_echo = false; lineCommentStart = NULL; } } } else if (state == TPL_STATE_CODE) { if (*p == '\\' && *(p+1) == '"') { p++; //skip } else if (*p == '\\' && *(p+1) == '\'') { p++; //skip } else if ( (*p == '-' && *(p+1) == '-') || (*p == '/' && *(p+1) == '/') ) { if (lineCommentStart == NULL) { lineCommentStart = p; } } else if (*p == '"') { state = TPL_STATE_DOUBLE_QUOTE; } else if (*p == '\'') { state = TPL_STATE_SINGLE_QUOTE; } else if (*p == '?' && *(p+1) == '>') { state = TPL_STATE_HTML; const char * codeend = p; //今の行にシングルコメントが入っている場合は、そのコメントの前まで。 if ( lineCommentStart ) { codeend = lineCommentStart; } if ( easy_echo ) { out << std::string(start , 0 , (int)(codeend - start) ) << "); print( [["; } else { out << std::string(start , 0 , (int)(codeend - start) ) << "; print( [["; } start = p + 2 ; p ++; } } else if (state == TPL_STATE_DOUBLE_QUOTE) { if (*p == '\\' && *(p+1) == '"') { p++; //skip } else if (*p == '"') { state = TPL_STATE_CODE; } } else if (state == TPL_STATE_DOUBLE_QUOTE) { if (*p == '\\' && *(p+1) == '\'') { p++; //skip } else if (*p == '\'') { state = TPL_STATE_CODE; } } } out << std::string(start , 0 , (int)(p - start) ) << std::endl; } if(state == TPL_STATE_HTML) { out << "]] );"; } out << " end"; /* //空出力を削除してパフォーマンスを上げる std::string sourceCode = out.str(); sourceCode = XLStringUtil::replace(sourceCode,"print( [[]] );",""); sourceCode = XLStringUtil::replace(sourceCode,"print( [[\r\n]] );",""); */ return sourceCode; }
これに htmlspecialchars みたいなものを組み込んで動くルーチンにしたのが、l_webload関数です。
https://github.com/rti7743/kaden_voice/blob/master/naichichi2/naichichi2/ScriptRunner.cpp#L637
ライブラリに依存しているので、コード自体はそのままだと動きません。
これは、C++が文字列リプレースみたいなものを標準で持っていないためですね。ダメダメですね。困りますね。
(std::string の replaceは使えない謎仕様だし。)
とりあえず、雰囲気(なぜか変換できる)だけでも味わってください。あんまり綺麗なソースではないですが。
//web専用 テンプレートを読み込む int ScriptRunner::l_webload(lua_State* L) { ScriptRunner* _this = __this(L); _this->PoolMainWindow->SyncInvokeLog(std::string() + "lua function:" + lua_funcdump(L,"webload") ,LOG_LEVEL_DEBUG); //テンプレート名 if (! lua_isstring(L,-2) ) { return luaL_errorHelper(L,lua_funcdump(L,"webload") + "の第1引数が文字列ではありません"); } //table if (! lua_istable(L,-1) ) { return luaL_errorHelper(L,lua_funcdump(L,"webload") + "の第2引数がテーブルではありません"); } //tableをすべてエスケープする。 //動的にテーブルを構築するのはスタックが汚れてしまって無理があるので、 //一度配列に構築した後、それをテーブルとして複写する. //一次元のリストだが、配列の操作を取得時と、再構築時に同じことを行うわけだから、次元は無視してもよい。 struct _temp { std::string key; std::string value; enum type { _temp_type_value ,_temp_type_nest ,_temp_type_up } type; }; struct _nest_lamba { lua_State* L; int index; _nest_lamba(lua_State* L,int index) : L(L) , index(index) { func(); }; //再起するのでクラス内クラスで。 void func() { _USE_WINDOWS_ENCODING; lua_pushnil(L); while (lua_next(L, index - 1) != 0) { _temp* p = new _temp; #ifdef _WINDOWS p->key = _U2A(lua_tostring(L, -2)); #else p->key = lua_tostring(L, -2); #endif if (lua_istable(L, -1)) { p->type = _temp::_temp_type_nest; safeArray.push_back(p); func(); _temp* p2 = new _temp; p2->type = _temp::_temp_type_up; safeArray.push_back(p2); } else { p->type = _temp::_temp_type_value; if (p->key.size() >= 2 && p->key[0] == '_' && p->key[1] == '_') {//キーが __hogehoge のように、 __ で始まる場合のみ自動エスケープをしない. p->value = lua_dump(L,-1 , false,0); } else { p->value = XLStringUtil::htmlspecialchars(lua_dump(L,-1 ,false, 0)); } safeArray.push_back(p); } lua_pop(L, 1); } } std::list< _temp* > safeArray; } //テーブルの読み込み処理 nest(L,-1); //テンプレートの読み込み std::string functionanme = "webtemplate"; std::string filename = lua_tostringHelper(L,-2); filename = _this->PoolMainWindow->Httpd.WebPathToRealPath(filename); std::string tplcode = convertTemplate(filename,functionanme); //ここからスタックを破壊するので、関数名を避難させる。 std::string func = lua_funcdump(L,"webload"); //テンプレートコードの読み込み if ( luaL_loadstring(L, tplcode.c_str()) ) { return luaL_errorHelper(L,func + "のテンプレート" + filename + " の解析中にエラー。 Lua:" + lua_tostringHelper(L, -1)); } //まずそのスクリプトを実行させて、 functionanme を登録します。 if ( lua_pcall( L, 0, 0, 0 ) ) { return luaL_errorHelper(L,func + "のテンプレート" + filename + " の実行中にエラー。 Lua:" + lua_tostringHelper(L, -1)); } //functionanme を呼び出す準備に入ります。 lua_getglobal(L, functionanme.c_str() ); if(!lua_isfunction(L,-1) ) { return luaL_errorHelper(L,func + "のテンプレート" + filename + " を実行しましたが、内部用関数" + functionanme + "が登録されていません。 Lua:" + lua_tostringHelper(L, -1)); } //テンプレート引数としてのテーブルを具現化 //thank http://logsoku.com/thread/pc2.2ch.net/tech/1063711237/824 lua_newtable(L); for(auto it = nest.safeArray.begin() ; it != nest.safeArray.end() ; ++it) { if ((*it)->type == _temp::_temp_type_value) { lua_pushstringHelper(L, (*it)->key); lua_pushstringHelper(L, (*it)->value); lua_settable(L, -3); } else if ((*it)->type == _temp::_temp_type_nest) { lua_pushstringHelper(L, (*it)->key); lua_newtable(L); } else //if ((*it)->type == _temp::_temp_type_up) { lua_settable(L, -3); } delete *it; } //関数呼び出し if ( lua_pcall( L, 1, 0, 0 ) ) { return luaL_errorHelper(L,func + "のテンプレート" + filename + " の実行中にエラー。 Lua:" + lua_tostringHelper(L, -1)); } return 0; //戻り値の数を指定 }