SimpleTemplate模板引擎开发
模板引擎相信大家是经常使用的,但是实现原理估计没多少人知道(你要是说不就是replace嘛,那我也无话说了...)。
先来看看这个SimpleTemplate想实现的是什么功能吧:
- 是个C#端的模板引擎
- 模板中能放普通变量(i, j, index, username这种直接了当的变量名)
- 模板中能放复合变量(user.FirstName, user.LastName这种有对象前缀的变量)
最终客户端代码通过下面的方式进行调用:
static void Main(string[] args)
{
string template = @"
your name: @{name}
your age: @{age}
";
Dictionary<string, object> ctx = new Dictionary<string, object>(); ctx["name"] = "McKay";
ctx["age"] = "你猜"; Console.WriteLine(STParser.GenerateStringView(template, ctx)); Console.ReadKey();
}
大家看出来了,重点就在@{xxxx}上
大家也先别喷我,说用正则、replace就搞定了,看后面先,小心喷了后悔。
我选择用antlr来做这个模板引擎,因为虽然现在看上去这个模板引擎很简单,但是不代表以后不扩展啊,以后还要加入if/else/for这种通用编程语法的,所以为了扩展性,就用语法解析器了。
知道yacc/flex的也可以去看看,只不过没有C#插件,而antlr正好有这插件,就用了。
下面我们先来写文法规则:
parse
:expression*
; expression
: stringtext
| simple_variable
| complex_variable
;
parse
规则代表开始规则,这个名称可以自己起名,只要是小写就行。
内容只有一行,expression*,代表0个或者无限个expression规则
expression
看清,第一个是冒号“:”,后续的是或者号“|”,最后是封号";"
代表expression可以是三种规则中的一种:stringtext、simple_variable、complex_variable,代表普通的字符串文本、简单变量、复合变量规则
先来看看普通文本字符串的定义
stringtext
: placeholderChar (placeholderChar)*
| newlines
; placeholderChar
: CHAR
| ':'
| SPACE
| NUMBER
| DOT
| '\''
| '"'
| '<'
| '>'
| '_'
| '+'
| '-'
| '*'
| '/'
; newlines
:NEWLINE NEWLINE*
; NEWLINE:'\r'? '\n';
NUMBER: '0'..'9';
CHAR: 'a'..'z'|'A'..'Z';
SPACE:' ';
DOT:'.';
stringtext
二选一的规则
第一行代表至少一个占位字符的字符串(后面用了*号,就代表字符数不限)
newlines,看后面的定义也是用了*号,代表一个回车,或者多个回车的规则匹配
占位符,大家看placeholderChar规则,就知道允许的占位符是哪些字符了
注意:大写的规则其实不是规则,而是token
再来看看简单变量规则的定义
simple_variable
:V_START simple_variable_inner V_END
; simple_variable_inner
:identity
; identity
:(UNDERLINE|CHAR) (UNDERLINE|CHAR|NUMBER)*
; V_START:'@{';
V_END:'}';
NUMBER: '0'..'9';
CHAR: 'a'..'z'|'A'..'Z';
UNDERLINE: '_';
simple_variable
定义了一个V_START的TOKEN为开头,也定义了必须以V_END为结尾,字符分别是 @{和},呵呵,中间就是那个变量名了
这个变量名其实就是identity规则的定义,是说第一个字符必须以下划线或英文字母开头,后续字符可有可无,有的话必须是下划线、英文字母、数字
再看看复合变量的规则
complex_variable
:V_START complex_variable_inner V_END
;
complex_variable_inner
:identity DOT identity
; identity
:(UNDERLINE|CHAR) (UNDERLINE|CHAR|NUMBER)*
; DOT:'.';
说说这里的complex_variable_inner规则
由于是要匹配obj.property格式,因此用了个点号DOT,obj和property的规则匹配其实就是identity的规则匹配
我们看看上面规则的效果,antlr解析树:
还是比较帅的
下面的问题是,怎么运用到C#项目中了
怎么运用到C#项目中
首先,新建一个项目,然后在NuGet中搜索"antlr" ,找到antlr4,然后安装
然后新建一个任意文件,新建后重命名为g4文件,比如SimpleTemplate.g4,接着还要设置下这个g4文件的生成方式,如下图
这样,当我们生成时,antlr就会根据g4文件的规则定义生成对应的C#代码了。
然后再说说g4文件的内容是怎么拷贝过来(原先的解析树是在eclipse中才能看的,所以原先的g4定义都在那边做的)
首先,上方的grammar xxxxx;这里的xxxxx必须要和文件名称一致。
其次,compileUnit后面的那个规则,必须存在,代表默认规则
再其次,如果编译时总是报错(但是eclipse中是正常的),这时要修改下vs环境下的g4文件的编码,如下:
还得把eclipse中的g4文件内容拷贝到新的g4文件中,别忘了。
接下来就要进入C#编码层面了,呵呵,是不是有点不耐烦了,`(*∩_∩*)′
挂钩函数就那么一个,很简单,基本就是拷贝:
public static class STParser
{
public static string GenerateStringView(string template, Dictionary<string, object> variables)
{
Antlr4.Runtime.AntlrInputStream input = new Antlr4.Runtime.AntlrInputStream(template);
TemplateLexer lexer = new TemplateLexer(input); Antlr4.Runtime.UnbufferedTokenStream tokens = new Antlr4.Runtime.UnbufferedTokenStream(lexer);
TemplateParser parser = new TemplateParser(tokens); var tree = parser.parse(); SimpleTemplateVisitor visitor = new SimpleTemplateVisitor(variables); string result=visitor.Visit(tree); return result;
}
}
template是传进来的模板文本
variables是传进来的变量集合
这段代码中都是antlr引擎自动生成的类,除了SimpleTemplateVisitor是自定义的(不然咋替换字符串啊)
来看看这个类吧,里面都是VisitXXXX规则的函数重载,需要的自定义逻辑都在里面改写
class SimpleTemplateVisitor:g4.TemplateBaseVisitor<string>
{
private Dictionary<string, object> ctx; public SimpleTemplateVisitor(Dictionary<string, object> ctx)
{
this.ctx = ctx;
} public override string VisitParse(g4.TemplateParser.ParseContext context)
{
StringBuilder sb = new StringBuilder(); foreach(var exp in context.expression())
sb.Append(VisitExpression(exp)); return sb.ToString();
} public override string VisitNewlines(g4.TemplateParser.NewlinesContext context)
{
return context.GetText();
} public override string VisitStringtext(g4.TemplateParser.StringtextContext context)
{
return context.GetText();
} public override string VisitSimple_variable(g4.TemplateParser.Simple_variableContext context)
{
return VisitSimple_variable_inner(context.simple_variable_inner());
} public override string VisitComplex_variable(g4.TemplateParser.Complex_variableContext context)
{
return VisitComplex_variable_inner(context.complex_variable_inner());
} public override string VisitSimple_variable_inner(g4.TemplateParser.Simple_variable_innerContext context)
{
string var_name = context.identity().GetText(); if (!ctx.ContainsKey(var_name))
throw new NullReferenceException(var_name); return Convert.ToString(ctx[var_name]);
} public override string VisitComplex_variable_inner(g4.TemplateParser.Complex_variable_innerContext context)
{
string var_name = context.identity()[].GetText(); if (!ctx.ContainsKey(var_name))
throw new NullReferenceException(var_name); string propertyName = context.identity()[].GetText(); object obj = ctx[var_name]; Type t = obj.GetType();
PropertyInfo propertyInfo = t.GetProperty(propertyName); var value = propertyInfo.GetValue(obj, null); string string_value = Convert.ToString(value); return string_value;
}
}
构造函数中传入的ctx是我们要替换的变量集合
光看这些函数是会晕的,你得结合eclipse中的解析树层次图来同时看,要清楚的知道上下关系,然后再套上面这个visit类才能看懂,呵呵,慢慢折腾看吧。
此处等待数周。。。
上面这个只是替换变量的没意思,我们再做个有循环的,比如:
your name: @{user.name}
your age: @{user.age} 1
2 3 @{repeat 5}
testing
@{end repeat}
---------------------
@{repeat count}
testing
@{end repeat}
看,支持了循环repeat语法
repeat后面可以支持固定的数字,也可以支持简单变量,也可以支持复合变量,大家应该能在脑子里画出规则形状来吧。
有兴趣深入的同学可以自己试下实现if/else语法。
代码已经上传到github上了,url: https://github.com/daibinhua888/SimpleTemplate/
SimpleTemplate模板引擎开发的更多相关文章
- 前后端数据交互处理基于原生JS模板引擎开发
json数据错误处理,把json文件数据复制到----> https://www.bejson.com/ 在线解析json 这样能直观的了解到是否是json数据写错,在控制台打断点,那里错误打那 ...
- 转 如何使用velocity模板引擎开发网站
基于 Java 的网站开发,很多人都采用 JSP 作为前端网页制作的技术,尤其在是国内.这种技术通常有一些问题,我试想一下我们是怎样开发网站的,通常有几种方法: 1:功能确定后,由美工设计网页的UI( ...
- Nvelocity模板引擎开发网页
在ASP.NET网站开发中,我们要做许多的网页,如果多个网页的内容框架有些重复使用,我们用NVelocity模板引擎,就可以把相同的部分html代码单独放在一个文件中就行了,当要使用的时候,只需使用# ...
- 写一个迷你版Smarty模板引擎,对认识模板引擎原理非常好(附代码)
前些时间在看创智博客韩顺平的Smarty模板引擎教程,再结合自己跟李炎恢第二季开发中CMS系统写的tpl模板引擎.今天就写一个迷你版的Smarty引擎,虽然说我并没有深入分析过Smarty的源码,但是 ...
- Express开发实例(2) —— Jade模板引擎
前一篇通过helloworld,简单介绍了Express中的开发,本篇继续深入的学习express的模板. 关于Jade的用法,网上有很多,本篇参考:Jade语法 安装相关模块 在实验代码前,应该先安 ...
- 基于Velocity开发自己的模板引擎
Velocity是一个基于java的模板引擎(template engine).它同意不论什么人只简单的使用模板语言(template language)来引用由java代码定义的对象. 当Veloc ...
- Spring Boot 系列(五)web开发-Thymeleaf、FreeMarker模板引擎
前面几篇介绍了返回json数据提供良好的RESTful api,下面我们介绍如何把处理完的数据渲染到页面上. Spring Boot 使用模板引擎 Spring Boot 推荐使用Thymeleaf. ...
- 基于hi-nginx的web开发(python篇)——使用jinja2模板引擎
模板引擎的使用在web开发中是不可避免和必要的.hi.py框架使用jinja2作为模板引擎. 为了使用hi.py提供的jinja2引擎,首先需要引入它: from hi import hi,templ ...
- 使用新一代js模板引擎NornJ提升React.js开发体验
当前的前端世界中有很多著名的开源javascript模板引擎如Handlebars.Nunjucks.EJS等等,相信很多人对它们都并不陌生. js模板引擎的现状 通常来讲,这些js模板引擎项目都有一 ...
随机推荐
- HTTP学习笔记(1)ULR语法
1,概述 当你打开一个浏览器则会进入一个主页,也许你会想只是打开了浏览器罢了,但是浏览器默默的把这个主页默认的网址发送出去,你只是不知道而已,你只是没有输入而已.我们来做个实验. 1,双击打开 2,可 ...
- Ubuntu下制作ISO文件
利用Ubuntu自带的命令mkisofs就可以制作iso文件,具体方法如下: 1. 如果你是直接从cd压制iso文件的,执行 sudo umount /dev/cdromdd if=/dev/cd ...
- Robot Framework-Mac版本安装
Robot Framework-Mac版本安装 Robot Framework-Windows版本安装 Robot Framework-工具简介及入门使用 Robot Framework-Databa ...
- 让谷歌浏览器 chrome 支持小于12px的字体
webkit的私有属性:{-webkit-text-size-adjust:none;} 但是,在最新版的谷歌里.已经不在支持这个属性啦. 用css3的transform:scale()缩放大小,但是 ...
- Neither BindingResult nor plain target object for bean
当你开发一个项目,如果你选择的是spring MVC 框架,而你在前台使用spring的标签时,那么你有可能出现在这个情况. javax.servlet.jsp.JspTagException: Ne ...
- 如何使不同主机上的docker容器互相通信
docker启动时,会在宿主主机上创建一个名为docker0的虚拟网络接口,默认选择172.17.42.1/16,一个16位的子网掩码给容器提供了65534个IP地址.docker0只是一个在绑定到这 ...
- Magcodes.WeiChat——通过CsvFileResult以及DataAnnotations实现导出CSV文件
我们先来看看效果图: 从上图中可以看出,导出的文件中列名与表格名称保持一致,并且忽略了某些字段. 相关代码实现 我们来看相关代码: 页面代码: @using (Html.BeginForm(" ...
- Asp.Net Web API 2第十八课——Working with Entity Relations in OData
前言 阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnblogs.com/aehyok/p/3446289.html. 本文的示例代码的下载地址 ...
- Linux:常用快捷键
按键 作用 Ctrl+d 键盘输入结束或退出终端 Ctrl+s 暂定当前程序,暂停后按下任意键恢复运行 Ctrl+z 将当前程序放到后台运行,恢复到前台为命令fg Ctrl+a 将光标移至输入行头,相 ...
- C# 通过SerialPort简单调用串口
问题 最近比较经常使用串口进行发送以及传输数据,但是笔者在刚开始接触SerialPort类时,对于Write之后去Read数据的时候,由于设备上面还没有返回数据,读取到的只能是空值.然而,再进行下一次 ...