文章目錄
- 2.1,間接屬性(indirect property names)
- 2.2,多值屬性(multi-valued attribute)
- 2.3 屬性的呈現(attribute render)
- 3.1 從檔案中載入模板
- 3.2 命名模板(Group Files)
- 4.1 匿名子模板(anonymous subtemplate)
- 4.2 條件包含子模板
- 5.1 未涉及的部分
- 5.2 連結
1,特點
1,強制Model和View的分離,也就是View負責呈現,不能修改Model。同時View中不用來包括任何商務邏輯,詳見作者的論文(中文,英文)。
2,Lazy-Evaluation,所有的Attribute直到st.ToString()調用時才evaluate.這樣的好處是setAttribute是Order Independent。
3,Recursive enable。
2,屬性(attribute)
StringTemplate文法有兩種組成元素,一種是屬性(attribute),另一種是一般字元(plain text)。在$…$中包圍的是屬性,其餘的都是一般字元。比如:
select $column$ from $table$
其中紅色部分的$column$和$table$都是屬性。在模板引擎呈現,即調用st.ToString()時,屬性被實際值替換,而一般字元原封不動地輸出。
我們可以通過下面的方式來向模板“填充”(push)屬性:
StringTemplate st = new StringTemplate("select $column$ from $table")st.SetAttribute("column", "uid");st.SetAttribute("table", "users");Console.WriteLine(st.ToString()); //output: select uid from users
屬性的填充方式很簡單,就是調用attribute.ToString()方法的結果,如果attribute是null不會拋出異常,而是輸出Null 字元串。
在.NET實現中attribute可以引用.net對象欄位或屬性(不區分大小寫),如果欄位和屬性同名則屬性優先。attribute也可以是Dictionary或HashTable的Key,這樣會找到該Key對應的值,然後在調用這個值的ToString()方法。樣本:
class Query{ public string column = "a"; public string table = "t"; public string Table { get { return "T"; } }}StringTemplate st = new StringTemplate("select $q.column$ from $q.TABLE$");Query q = new Query();st.SetAttribute("q",q);Console.WriteLine(st.ToString()); //output: select a from T
2.1,間接屬性(indirect property names)
你可以通過下面的attribute.(anotherAttr)文法來引用間接屬性:
StringTemplate st1 = new StringTemplate("$q.(property)$"); //property是間接屬性,也是一個attribute Query q = new Query();st1.SetAttribute("q", q);st1.SetAttribute("property", "table");Console.WriteLine(st1.ToString()); //output: t
有些StringTemplate的保留字(reserved keyword),是不能作為屬性的:
如果attribute中的property和這些保留字重複了,StringTemplate就會報錯,解決方案是使用間接屬性:
class Query{ public string first = "NumberOne";}StringTemplate st = new StringTemplate("select $q.(IndirectAttr)$");Query q = new Query();st.SetAttribute("q", q);st.SetAttribute("IndirectAttr", "first");Console.WriteLine(st.ToString()); //select NumberOne//或者直接用Literal String: select $q.("first")$2.2,多值屬性(multi-valued attribute)
所謂多值屬性其實就是像數組一樣的可迭代對象。多值屬性通常有兩種輸出方式:1,類似於String.Join()的拼接。2,迭代。
A)拼接
StringTemplate st = new StringTemplate("select $columns$ from users");List<string> columns = new List<string>();columns.Add("a");columns.Add("b");st.SetAttribute("columns", columns);Console.WriteLine(st.ToString()); //output: select ab from usersStringTemplate st1 = new StringTemplate("select $columns; separator=\",\"$ from users"); st1.SetAttribute("columns", "a");st1.SetAttribute("columns", "b"); //此時columns變成multi-valued,作者講其實SetAttribute應該叫做AddAtribute才對,對於同名attribute他不是替換而是添加Console.WriteLine(st1.ToString()); //output: select a,b from users StringTemplate st2 = new StringTemplate("select $columns; separator=dilimiter$ from users");st2.SetAttribute("columns", "a");st2.SetAttribute("columns", "b");st2.SetAttribute("dilimiter", ","); //separator也可以使exprConsole.WriteLine(st2.ToString()); //output: select a,b from users B)迭代
StringTemplate st = new StringTemplate("select $columns:{<i>$it$</i>}$ from users"); //迭代{...}中的anonymous template,並以自動填滿it為當前元素st.SetAttribute("columns", "a");st.SetAttribute("columns", "b"); Console.WriteLine(st.ToString()); //output: select <i>a</i> <i>b</i> from usersStringTemplate st1 = new StringTemplate("select $columns:{col|<i>$col$</i>}$ from users"); //自動填滿col為當前元素,同時it還是要填充的 st1.SetAttribute("columns", "a");st1.SetAttribute("columns", "b");Console.WriteLine(st1.ToString()); //output: select <i>a</i> <i>b</i> from usersC)函數
StringTemplate裡面內建了一些函數用來操作multi-valued attributes,把他當做Lisp中的List一樣操作,通過一些組合能實現牛逼功能。
StringTemplate st = new StringTemplate("[$first(columns)$] [$last(columns)$] [$first(rest(columns))$]"); st.SetAttribute("columns", "a");st.SetAttribute("columns", "b");st.SetAttribute("columns", "c");Console.WriteLine(st.ToString()); //output: [a] [c] [b]2.3 屬性的呈現(attribute render)
我們知道屬性最後在模板中的呈現都是調用屬性的ToString()方法,但是我們有時候想在模板呈現的時候改變某個屬性的呈現,那麼我們該怎麼做呢?試想一下,我們對於DateTime,我們只想顯示日期,而不想顯示具體的時間,那我們怎麼處理呢?一個辦法是建立一個類型,然後重寫ToString方法,但這樣改動有點大,因為你需要在Model中所有用到這個DateTime的地方都要做類型轉換。
StringTemplate提供的一個介面IAttributeRenderer用來做這種工作,該介面只負責一個類型的呈現,我們可以向一個模板註冊這個呈現方式:
class DateTimeRender:IAttributeRenderer{ public string ToString(object o) { DateTime dt = (DateTime)o; return dt.ToString("yyyy.MM.dd"); }}StringTemplate st = new StringTemplate("$d$");st.SetAttribute("d", DateTime.Now);Console.WriteLine(st.ToString());st.RegisterAttributeRenderer(typeof(DateTime), new DateTimeRender()); //register the attribute render for DateTimeConsole.WriteLine(st.ToString());
3,StringTemplateGroup3.1 從檔案中載入模板
模板時常是寫在檔案中的,比如一些作為html頁面的模板。如果想從檔案中裝載模板我們可以使用StringTemplateGroup:
string rootDirectoryOfTemplate = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"Query");StringTemplateGroup group = new StringTemplateGroup("queryTemplate",rootDirectoryOfTemplate);StringTemplate st1 = group.GetInstanceOf("query"); //load ROOT_PATH/query.stst1.SetAttribute("func","sysdate()");Console.WriteLine(st1.ToString());StringTemplate st2 = group.GetInstanceOf("\\sub\\query"); //load ROOT_PATH/sub/query.stst2.SetAttribute("func", "sysdate()");Console.WriteLine(st2.ToString());3.2 命名模板(Group Files)
如果想模組化模板,一個方式是使用單獨的檔案。但是如果模板很短小這樣會產生很多檔案,StringTemplate提供了Group Files用來定義短小的模板模組,你可以通過給通過名稱來引用定義好的模板模組。命名模板的定義規則是:
templateName(arg1, arg2, ..., argN) ::= "single-line template"ortemplateName(arg1, arg2, ..., argN) ::= <<multi-line template>>ortemplateName(arg1, arg2, ..., argN) ::= <%multi-line template that ignores indentation and newlines%>
命名模板還提供了模板參數,你可以通過SetAttribute來設定模板的參數。樣本:
string groupDef = @"group mygroup;vardef(type,name) ::= ""<type> <name>;""method(type,name,args) ::= <<<type> <name>(<args; separator="","">) { <statements; separator=""\n"">}>>";StringTemplateGroup stg = new StringTemplateGroup(new StringReader(groupDef));var vardef = stg.GetInstanceOf("vardef");vardef.SetAttribute("type", "int");vardef.SetAttribute("name", "sum");Console.WriteLine(vardef.ToString()); //output: int sum;
4,子模板(subtemplate)4.1 匿名子模板(anonymous subtemplate)
我們可以對屬性或多值屬性應用匿名子模板,文法我們在多值屬性的迭代中已經見識了:$attr:{ <anonymous template> }$。匿名模板還可以嵌套:
StringTemplate st = new StringTemplate("$x:{<b>$it$</b>}:{<li>$it$</li>}; separator=\"\n\"$");st.SetAttribute("x", 1);st.SetAttribute("x", 2);st.SetAttribute("x", 3);Console.WriteLine(st.ToString());
這樣我們就可以對每個元素先應用<b>,然後在應用<li>。
對於多值屬性,我們還可以對每個屬性交替地應用不同的子模板,文法跟上面的就跟上面應用多個匿名模板很類似,將“:”變成“,”:
StringTemplate st = new StringTemplate("$x:{<red>$it$</red>},{<blue>$it$</blue>}; separator=\"\n\"$");st.SetAttribute("x", 1);st.SetAttribute("x", "jatsz");st.SetAttribute("x", 2);st.SetAttribute("x","git");Console.WriteLine(st.ToString());
4.2 條件包含子模板
$if(x)$...$elseif(y)$...$elseif(z)$...$else$...$endif$
除了false, null, empty(list,hastable)視作false以外,其他的值都被視作true。我們可以通過條件運算式來包含(include)不同的子模板:
我們共有三個模板檔案,分別位於Views目錄下的index.st, sub/login.st, sub/logout.st,檔案的內容如下:
===index.st$if(loggedin)$ $sub/logout(lnk=linkUrl)$$else$ $sub/login(lnk=linkUrl)$$endif$===sub/login.st<a href="$lnk$">Please click to login</a>==sub/logout.st<a href="$lnk$">Please click to logout</a>
我們的程式碼如下:
StringTemplateGroup stg = new StringTemplateGroup("index", AppDomain.CurrentDomain.BaseDirectory + "views");var stIndex = stg.GetInstanceOf("index");stIndex.SetAttribute("loggedin", true);stIndex.SetAttribute("linkUrl","http://example.com/logout");Console.WriteLine(stIndex.ToString());stIndex.Reset(); //or stIndex.RemoveAttribute("loggedin");stIndex.SetAttribute("loggedin", false);stIndex.SetAttribute("linkUrl", "http://example.com/login");Console.WriteLine(stIndex.ToString());
5,雜項5.1 未涉及的部分
- Group Interface
- Template Inheritance
5.2 連結
本文樣本使用的是StringTemplate v3版。
http://www.stringtemplate.org/
http://www.antlr.org/wiki/display/ST/StringTemplate+3.0+Printable+Documentation#StringTemplate30PrintableDocumentation-6Groupinterfaces