SimpleTemplate模板引擎开发

模板引擎相信大家是经常使用的,但是实现原理估计没多少人知道(你要是说不就是replace嘛,那我也无话说了...)。


先来看看这个SimpleTemplate想实现的是什么功能吧:

  1. 是个C#端的模板引擎
  2. 模板中能放普通变量(i, j, index, username这种直接了当的变量名)
  3. 模板中能放复合变量(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()[0].GetText();

            if (!ctx.ContainsKey(var_name))
                throw new NullReferenceException(var_name);

            string propertyName = context.identity()[1].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/

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Simple是一个异步的HTTP引擎,能够用数量有限的服务线程扩展更多负荷。在传输层运用NIO,确保响应和资源的效率。它具有充分综合的API,开发比Java Servlet的API更方便。 1.高性能的HTTP内核 Simple比流行的商业和开放源代码的Java HTTP服务器更胜一筹。就目前的基准与Jetty和AsyncWeb相比较,它拥有更高的throughput,而且在增加负荷时,它的规模要好得多。(比较结果显示) 2.异步处理 从一开始做项目的目标就是提供一个真正的异步HTTP引擎。当您需要启动一个可能耗费很长时间的应用程序进程,而用户又无需等候进程完成以便从服务器获取响应时,异步处理程序非常有用。服务器提供了允许 request completion,以推动使用internal,transparent和monitoring系统。这比目前的Servlet引擎简单了许多。 3.嵌入式框架 Simple提供一个框架,并且可以嵌入到任何现有的应用程序之中。与Spring 框架结合更完美。 4.占用内存小 尽管具有很高的扩展性,但是Simple占用的内存却很小。用最近的基准来与比其他的NIO服务器(Jetty and AsyncWeb)比较,它占用的内存空间几乎要少一半。 5.完全独立 它没有外部dependancies。除Java 5 SDK或以上,不需要任何其他的库。这样也使得内存占用少,并且确保在整合现有的应用程序时不存在任何的兼容问题。 6.开放源代码 发布基于LGPL,可完全整合或改装为商业和开放源代码的项目。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值