SimpleTemplate模板引擎开发
模板引擎相信大家是經常使用的,但是實現原理估計沒多少人知道(你要是說不就是replace嘛,那我也無話說了...)。
先來看看這個SimpleTemplate想實現的是什么功能吧:
最終客戶端代碼通過下面的方式進行調用:?
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 23@{repeat 5} testing @{end repeat} --------------------- @{repeat count} testing @{end repeat}?
看,支持了循環repeat語法
repeat后面可以支持固定的數字,也可以支持簡單變量,也可以支持復合變量,大家應該能在腦子里畫出規則形狀來吧。
?
有興趣深入的同學可以自己試下實現if/else語法。
?
代碼已經上傳到github上了,url:?https://github.com/daibinhua888/SimpleTemplate/
?
轉載于:https://www.cnblogs.com/aarond/p/4856402.html
總結
以上是生活随笔為你收集整理的SimpleTemplate模板引擎开发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 6.Python补充_Python之道
- 下一篇: python函数参数*args和**ar