cJinja 是一个利用cpp编写的轻量html模版解析库,依赖 ejson 来实现模版的数据更换(在jinja中称为context,高下文)。模版的语法基本与django jinja同等,功能还算丰富。源码仅有700行,适宜学习,以为不错的点个star吧。
(该程序为 https://github.com/HuangHongkai/tinyserver 中的一个模块)
编译
利用cmake来编译,windows和linux下均可编译。推举利用clion作为IDE。
编译成功后在build目录下会有libcjinja.a和cjinja_test.exe这2个文件。libcjinja.a是静态库,cjinja_test.exe是一个大略的测试程序。
运行测试程序后会涌现output.html(该文件是tmp.html解析后的结果。)
已经完成的功能
变量,例如 {{ var }}变量索引访问,例如 {{ var.var2 }} {{ var[2] }} {{ var[2].key.value[2] }},个中[] 表示对数组(类似python的list)进行索引, . 表示对object进行索引(类似与python的dict)表达式打算(包括字符串拼接) ,例如{{ 11+2-3var }} {{ 1+12-3/4 }} {{ \"大众asdfsf\公众+var }}for-endfor对列表进行迭代, 例如 {% for var in list %} {% endfor %}for-endfor对工具进行迭代,例如 {% for key,value in object %} {% endfor %} 或者 {% for key in object %}{% endfor %} 或者 {% for ,value in object %} {% endfor %}if-else-endif 语句, 个中if的条件支持四则运算,大略的比较(!= == )等,例如 {% if 1+1==2 %}aaa{% else %}bbb{%endif %}模版包含,嵌套其他的模版文件{% include 'other.html' %}模版语法缺点提示须要把稳,表达式之间不能含有空格,例如{{ 1 + 1 }}是非法的,而{{ 1+1 }}是合法的。
利用方法
1. 变量和变量索引
大略的例子如下,
HtmlTemplate html(\公众username:{{ username }}\n\"大众 \公众parm.list[1][2]: {{parm.list[1][2] }} \n\"大众 \公众parm.key: {{ parm.key }}\公众, 1); // 参数1表示传入的是模版字符串,0表示传入的是文件名,默认为0JSONObject obj = { {\"大众username\"大众, 1234}, {\"大众parm\"大众, { {\"大众key\"大众, \公众cde\公众}, {\"大众list\"大众, {1, {1,2.3, \"大众abcd\"大众}, \"大众hahaha\"大众}}, }}};html.setValue(obj);cout << html.render() << endl << endl;/ 运行后打印如下username:1234parm.list[1]: abcd parm.key: cde/
HtmlTemplate是一个库的紧张类,布局函数为
explicit HtmlTemplate(const string& str, int flag = 0); // flag=0是str表示文件路径,不为0是表示传入的模版字符串
个中str参数为字符串,可以表示html模板原始串,也可也表示为文件的路径,flag默认为0。
setValue 方法表示传入数据给模版工具。
render() 方法表示将模版解析成字符串。
JSONObject来源于 ejson 库,用来仿照python的dict,布局函数也比较随意马虎看懂。
2. 列表迭代
HtmlTemplate html(\"大众{% for x in list %}{{ x }}\n{%endfor%}\"大众 \"大众此时x已经是临时变量了,不可以在打印了 {{x}}\n\"大众 , 1);JSONObject obj = OBJECT( KEYVALUE(\"大众list\公众, LIST(1,2,3,4,5)));cout << html.setValue(obj).render() << endl << endl;/运行后输出如下12345此时x已经是临时变量了,不可以在打印了 /
把稳到在迭代过程中x是作为临时变量,在外部的话是无法打印出来的。
3. 字典迭代
HtmlTemplate html(\"大众{% for key in dict %}迭代1: 字典的key值为 {{ key }}\n{% endfor %}\公众 \公众{% for key,value in dict %}迭代2: 字典的key值为 {{ key }}, value值为 {{ value}}\n{% endfor %}\公众 \"大众{% for ,value in dict %}迭代3: 字典的value值为 {{ value }}\n{% endfor %}\"大众, 1);JSONObject obj = OBJECT( KEYVALUE(\公众dict\"大众, OBJECT( KEYVALUE(\"大众key1\"大众, \"大众value1\公众), KEYVALUE(\"大众key2\公众, 1234), KEYVALUE(\公众key3\"大众, nullptr), )));cout << html.setValue(obj).render() << endl << endl;/运行后输出迭代1: 字典的key值为 key1迭代1: 字典的key值为 key2迭代1: 字典的key值为 key3迭代2: 字典的key值为 key1, value值为 value1迭代2: 字典的key值为 key2, value值为 1234迭代2: 字典的key值为 key3, value值为 null迭代3: 字典的value值为 value1迭代3: 字典的value值为 1234迭代3: 字典的value值为 null/
4. 字符串拼接与表达式打算
HtmlTemplate html(\"大众{{ a+b+c+\\公众444\\"大众 }}\n\"大众 \"大众{{x}} {{y}} + 2 3 - 4 / {{x}} = {{ xy+23-4/x }}\n\"大众, 1);JSONObject obj = OBJECT( KEYVALUE(\公众a\公众, \"大众111\"大众), KEYVALUE(\公众b\"大众, \公众222\公众), KEYVALUE(\"大众c\公众, \公众333\"大众), KEYVALUE(\公众x\"大众, 12), KEYVALUE(\公众y\"大众, 34) );cout << html.setValue(obj).render() << endl << endl;/运行后输出11122233344412 34 + 2 3 - 4 / 12 = 413.667/
5. if-else-endif语句
HtmlTemplate html(\"大众{% if 1==1 %} 1==1 成立 {% else %} 1==1不成立 {%endif %}\n\"大众 \"大众{% if !x %} x为空 {% else %} x不为空 {%endif %}\n\公众 \公众{% if x==2 %} x==2 成立 {% endif %}\n\"大众 \"大众{% if x+1!=2 %} x+1!=2 成立 {% endif %}\n\公众 \"大众{% if x<3 %} x<3 成立 {% endif %}\n\公众 \"大众{% if x>1 %} x>1 成立 {% endif %}\n\"大众 \"大众{% if str==\\公众abcd\\"大众 %} str为abcd {% endif %}\n\"大众 \"大众{% if 1 %} 常量表达式1 {% endif %}\n\"大众 \"大众{% if 0 %} 常量表达式0,此处不会输出 {%endif%}\"大众, 1);JSONObject obj = { {\"大众x\"大众, 2}, {\公众str\"大众, \"大众abcd\"大众}};cout << html.setValue(obj).render() << endl;/运行后输出 1==1 成立 x不为空 x==2 成立 x+1!=2 成立 x<3 成立 x>1 成立 str为abcd 常量表达式1 /
6.for与if嵌套利用
HtmlTemplate html(\"大众{%for x in list%}\"大众 \"大众{%if x %}\公众 \"大众{% for y in list2%}\"大众 \公众{{x}} {{y}} = {{ xy }}\n\"大众 \公众{% endfor %}\公众 \"大众{% else %}\"大众 \"大众x的值为空\n\"大众 \公众{%endif%}\"大众 \公众{% endfor%}\公众, 1);JSONObject obj = OBJECT( KEYVALUE(\"大众list\"大众, LIST(1,2,3,4,5)), KEYVALUE(\公众list2\公众, LIST(1,2,3)),);cout << html.setValue(obj).render() << endl << endl;/运行后输出1 1 = 11 2 = 21 3 = 32 1 = 22 2 = 42 3 = 63 1 = 33 2 = 63 3 = 94 1 = 44 2 = 84 3 = 125 1 = 55 2 = 105 3 = 15/
7.模版文件作为输出
HtmlTemplate html(\"大众tmpl.html\公众);JSONObject context = OBJECT( ...);FILE f = fopen(\"大众output.html\公众, \"大众w\"大众); // 写入到文件中string&& str = html.setValue(context).render();fwrite(str.c_str(), 1, str.size(), f);fclose(f);/运行后,代开当前目录的tmpl.html文件作为输入,输出文件为output.html//如果tmpl.html不存在则抛出非常/
8. 非常处理
HtmlTemplate html(\"大众{% if 1 %} xxx \公众, 1);// 不传入contexttry { cout << html.render() << endl;} catch(exception& e) { cerr << e.what() << endl;}cout << endl;
运行后终端上打印如下,
会提示非常的类名,非常文件所在位置,代码行数,以及一些缺点的信息。
谈论
1. 实现一个大略的表达式打算器用什么方法比较好?(例如 {{ 2.33+4/5x }} 这类表达式)
我分享一下我自己的方法,有什么更好的方法一起谈论一下。
第一步,先把数据和符号提取出来放入到数组中,输入类型全部设为double。例如上面那个表达式,符号提取出来是{, /, }, 数据提取出来是{2.3, 3, 4, 5, x}这一步位于__parse_var这个函数,比较大略不详细谈论。第二步,先打算乘除法,结果放入栈中,在对栈中元素打算加减法(按照我们平常打算表达式的思路先乘除后加减),这一步实现如下(个中利用到C措辞的宏和C++11的匿名函数)double cJinja::HtmlTemplate::calculator(vector<any>& number, vector<char>& op) { // 例如下表达式会成为 // 1 - 2 - 3 + 2 3 4 - 45 // vector<char> op = { '-', '-', '+', '', '', '-', '' }; // vector<any> number = { 1, 2, 3, 2, 3, 4, 4, 5 }; if (number.size() != op.size() + 1) throwException(TemplateParseException, \"大众运算符号数和操作数不匹配\"大众); / 定义打算器的内部函数 / auto calc = [](any& var1, double var2, char op) -> double{ // var2 + var1 // var2 var1 // var2 - var1 // var2 / var1 // 把稳顺序#define CALC(op2) \ if(#op2[0] == op) { \ if (var1.type() == typeid(int)) \ return var2 op2 static_cast<double>(any_cast<int>(var1)); \ else if (var1.type() == typeid(float)) \ return var2 op2 static_cast<double>(any_cast<float>(var1)) ; \ else if (var1.type() == typeid(double)) \ return var2 op2 static_cast<double>(any_cast<double>(var1)) ; \ } CALC(+); CALC(-); CALC(); CALC(/); throwException(TemplateParseException, \公众不许可对空指针进走运算\"大众);#undef CALC }; vector<double> num_stack; // 打算中间结果存储栈 num_stack.push_back(calc(number[0], 0, '+')); // 获取值 number[i+1] + 0 (加法运算零元为0,乘法运算零元为1) / 打算 / 法/ for (size_t i = 0; i < op.size(); i++) { if (op[i] == '+' || op[i] == '-') { num_stack.push_back(calc(number[i + 1], 0, '+')); // number[i+1] + 0 } else if (op[i] == '' || op[i] == '/') { double var1 = num_stack.back(); num_stack.pop_back(); num_stack.push_back(calc(number[i + 1], var1, op[i])); // var1/number[i+1] 或者是 var1/number[i+1] } else throwException(TemplateParseException, str_format(\"大众造孽操作符 %d\"大众, op[i])); } / 打算 + - 法/ double result = num_stack[0]; size_t i = 1; for (auto& ch : op) { if (ch == '+') { result += num_stack[i++]; } else if(ch == '-') { result -= num_stack[i++]; } } return result;}
2. 抛出非常包含更多的信息
我定义了一个throwException宏,如下
#define throwException(Exception, ...) { \ std::cerr << \"大众[\"大众 << #Exception << \"大众] : FILE: \"大众 << string(__FILE__).substr(string(__FILE__).find_last_of('/') + 1) << \公众 LINE: \"大众 << __LINE__ << \"大众 FUNCTION: \公众 <<__FUNCTION__ << std::endl; \ throw Exception(__VA_ARGS__); \ }
个中__FILE__ 为文件名,__LINE__ 为当前代码行数,这些都是C中的内置宏,__VA_ARGS__是可变参数,对应于宏函数参数中的....