百度空间 | 百度首页 
               
 
查看文章
 
[转]StringTemplate.NET 文档(其它)
2008-09-23 15:49

模板继承(Template Inheritance)

Recall that a StringTemplateGroup is a collection of related templates such as all templates associated with the look of a web site. If you want to design a similar look for that site (such as for premium users), you don't really want to cut-n-paste the original template files for use in the new look. Changes to the original will not be propogated to the new look.

Just like you would do with a class definition, a template group may inherit templates from another group, the supergroup. If template t is not found in a group, it is looked up in the supergroup, if present. This works regardless of whether you use a group file format or load templates from the disk via a StringTemplateGroup object. Currently you cannot use the group file syntax to specify a supergroup. I am investigating how this should work. In the meantime, you must explicitly set the super.

From my unit tests, here is a simple inheritance of a template, bold:

StringTemplateGroup supergroup = new StringTemplateGroup("super"); StringTemplateGroup subgroup = new StringTemplateGroup("sub"); supergroup.DefineTemplate("bold", "<b>$it$</b>"); subgroup.SuperGroup = supergroup; StringTemplate st = new StringTemplate(subgroup, "$name:bold()$"); st.SetAttribute("name", "Terence"); String expecting = "<b>Terence</b>";

The supergroup has a bold definition but the subgroup does not. Referencing $name:bold()$ works because subgroup looks into its supergroup if it is not found.

You may override templates:

supergroup.DefineTemplate("bold", "<b>$it$</b>"); subgroup.DefineTemplate("bold", "<strong>$it$</strong>");

And you may refer to a template in a supergroup via super.template():

StringTemplateGroup group = new StringTemplateGroup(...); StringTemplateGroup subGroup = new StringTemplateGroup(...); subGroup.SuperGroup = group; group.DefineTemplate("page", "$font()$:text"); group.DefineTemplate("font", "Helvetica"); subGroup.DefineTemplate("font", "$super.font()$ and Times"); StringTemplate st = subGroup.GetInstanceOf("page");

The string st.ToString() results in "Helvetica and Times:text".

Just like object-oriented programming languages, StringTemplate has polymorphism. That is, template names are looked up dynamically relative to the invoking templates group. The classic demonstration of dynamic message sends in Java, for example, would be the following example that catches my students all the time: ;)

class A {   public void page() {bold();}   public void bold() {Console.Out.WriteLine("A.bold");} } class B extends A {   public void bold() {Console.Out.WriteLine("B.bold");} } ... A a = new B(); a.page();

This prints "B.bold" not "A.bold" because the receiver determines how to answer a message not the type of the variable. So, I have created a B object meaning that any message, such as bold(), invoked will first look in class B for bold().

Similarly, a template's group determines where it starts looking for a template. In this case, both super and sub groups define a bold template mirroring the Java above. Because I create template st as a member of the subGroup and reference to bold starts looking in subGroup even though page is the template referring to bold.

StringTemplateGroup group = new StringTemplateGroup("super"); StringTemplateGroup subGroup = new StringTemplateGroup("sub"); subGroup.SuperGroup = group; group.DefineTemplate("bold", "<b>$it$</b>"); group.DefineTemplate("page", "$name:bold()$"); subGroup.DefineTemplate("bold", "<strong>$it$</strong>"); StringTemplate st = subGroup.GetInstanceOf("page"); st.SetAttribute("name", "Ter"); String expecting = "<strong>Ter</strong>";

StringTemplate group maps also inherit. If an attribute reference is not found, StringTemplate looks for a map in its group with that name. If not found, the super group is checked.

模板及变量查找规则(Template And Attribute Lookup Rules)

模板查找规则

When you request a named template via StringTemplateGroup.GetInstanceOf() or within a template, there is a specific sequence used to locate the template.

If a template, t, references another template and t is not specifically associated with any group, t is implicitly associated with a default group whose root directory is ".", the current directory. The referenced template will be looked up in the current directory.

If a template t is associated with a group, but was not defined via a group file format, lookup a referenced template in the group's template table. If not there, look for it on the disk under the group's root dir (it might have appeared dynamically on disk). If not found, recursively look at any supergroup of the group. If not found at all, record this fact and don't look again on the disk until refresh interval.

If the template's associated group was defined via a group file, then that group is searched first. If not found, the template is looked up in any supergroup. The refresh interval is not used for group files because the group file is considered complete and enduring.

变量范围规则

A StringTemplate is a list of chunks, text literals and attribute expressions, and an attributes table. To render a template to string, the chunks are written out in order; the expressions are evaluated only when asked to during rendering. Attributes referenced in expressions are looked up using a very specific sequence similar to an inheritance mechanism.

When you nest a template within another, such as when a page template references a searchbox template, the nested template may see any attributes of the enclosing instance or its enclosing instances. This mechanism is called dynamic scoping. Contrast this with lexical scoping used in most programming languages like C# where a method may not see the variables defined in invoking methods. Dynamic scoping is very natural for templates. For example, if page has an attribute/value pair font/Times then searchbox could reference $font$ when nested within a page instance.

Reference to attribute a in template t is resolved as follows:

  1. Look in t's attribute table
  2. Look in t's arguments
  3. Look recursively up t's enclosing template instance chain
  4. Look recursively up t's group / supergroup chain for a map

This process is recursively executed until a is found or there are no more enclosing template instances or super groups.

When using a group file format to specify templates, you must specify the formal arguments for that template. If you try to access an attribute that is not formally defined in that template or an enclosing template, you will get a InvalidOperationException.

When building code generators with StringTemplate, large heavily nested template tree structures are commonplace and, due to dynamic attribute scoping, a nested template could inadvertently use an attribute from an enclosing scope. This could lead to infinite recursion during rendering and other surprises. To prevent this, formal arguments on template t hide any attribute value with that name in any enclosing scope. Here is a test case that illustrates the point.

String templates =         "group test;" +newline+         "block(stats) ::= \"{$stats$}\""         ; StringTemplateGroup group = new StringTemplateGroup(new StringReader(templates)); StringTemplate b = group.GetInstanceOf("block"); b.SetAttribute("stats", group.GetInstanceOf("block")); String expecting ="{{}}";

Even though block has a stats value that refers to itself, there is no recursion because each instance of block hides the stats value from above since stats is a formal argument.

Sometimes self-recursive (hence infinitely recursive) structures occur through programming error and they are nasty to track down. If you turn on "lint mode", StringTemplate will attempt to find cases where a template instance is being evaluated during the evaluation of itself. For example, here is a test case that causes and traps infinite recursion.

String templates =         "group test;" +newline+         "block(stats) ::= \"$stats$\"" +         "ifstat(stats) ::= \"IF true then $stats$\"\n"         ; StringTemplate.SetLintMode(true); StringTemplateGroup group = new StringTemplateGroup(new StringReader(templates)); StringTemplate b = group.GetInstanceOf("block"); StringTemplate ifstat = group.GetInstanceOf("ifstat"); b.SetAttribute("stats", ifstat); // block has if stat ifstat.SetAttribute("stats", b); // but make the "if" contain block try {     String result = b.ToString(); } catch (InvalidOperationException ise) {     ... }

The nested template stack trace from ise.getMessage() will be similar to:

infinite recursion to <ifstat([stats])@4> referenced in <block([stats])@3>; stack trace: <ifstat([stats])@4>, attributes=[stats=<block()@3>]> <block([stats])@3>, attributes=[stats=<ifstat()@4>], references=[stats]> <ifstat([stats])@4> (start of recursive cycle) ...

设置表达式分割符(Setting the Expression Delimiters)

By default, expressions in a template are delimited by dollar signs: $...$. This works great for the most common case of HTML generation because the attribute expressions are clearly highlighted in the text. Sometimes, with other formats like SQL statement generation, you may want to change the template expression delimiters to avoid a conflict and to make the expressions stand out.

As of 2.0, the start and stop strings are limited to either $...$ or <...> (unless you build your own lexical analyzer to break apart templates into chunks).

To use the angle bracket delimiters you must create a StringTemplateGroup:

StringTemplateGroup group = new StringTemplateGroup("sqlstuff", "C:/temp", typeof(AngleBracketTemplateLexer)); StringTemplate query = new StringTemplate(group, "Select <column> FROM <table>;"); query.SetAttribute("column", "name"); query.SetAttribute("table", "User");

All templates created through the group or in anyway associated with the group will assume your the angle bracket delimiters. It's smart to be consistent across all files of similar type such as "all HTML templates use $...$" and "all SQL templates use <...>".

缓存

By default templates are loaded from disk just once. During development, however, it is convenient to turn caching off. Also, you may want to turn off caching so that you can quickly update a running site. You can set a simple refresh interval using StringTemplateGroup.SetRefreshInterval(...). When the interval is reached, all templates are thrown out. Set interval to 0 to refresh constantly (no caching). Set the interval to a huge number like Integer.MAX_INT to have no refreshing at all.

输出过滤器

2.0 introduced the notion of a StringTemplateWriter. All text rendered from a template goes through one of these writers before being placed in the output buffer. I added this primarily for auto-indentation for code generation, but it also could be used to remove whitespace (as a compression) from HTML output. If you don't care about indentation, you can simply implement write():

public interface StringTemplateWriter {     void PushIndentation(String indent);      String PopIndentation();      void Write(String str); }

Here is a "pass through" writer that is already defined:

/** Just pass through the text */ public class NoIndentWriter : AutoIndentWriter  {     public NoIndentWriter(Writer outw) :base(outw)      {     }      public void Write(String str) {      outw.Write(str);     } }

Use it like this:

StringWriter out = new StringWriter(); StringTemplateGroup group = new StringTemplateGroup("test"); group.DefineTemplate("bold", "<b>$x$</b>"); StringTemplate nameST = new StringTemplate(group, "$name:bold(x=name)$"); nameST.SetAttribute("name", "Terence"); // write to 'out' with no indentation nameST.Write(new NoIndentWriter(out)); Console.Out.WriteLine("output: "+out.ToString());

Instead of using nameST.ToString(), which calls Write with a string write and returns its value, manually invoke Write with your writer.

If you want to always use a particular output filter, then use

StringTemplateGroup.setStringTemplateWriter(Type userSpecifiedWriterType);

The StringTemplate.ToString() method is sensitive to the group's writer class.

自动缩进

StringTemplate默认进行自动缩进。要禁用自动缩进, 用NoIndentWriter替换默认的AutoIndentWriter.

在最简单的情况下,缩进看起来就像一个简单的列数:

My dogs' names    $names; separator="\n"$ The last, unindented line

将输出:

My dog's names   Fido   Rex   Stinky The last, unindented line

where the last line gets "unindented" after displaying the list. StringTemplate tracks the characters to the left of the $ or < rather than the column number so that if you indent with tabs versus spaces, you'll get the same indentation in the output.

When there are nested templates, StringTemplate tracks the combined indentation:

// <user> is indented two spaces main(user) ::= << Hi \t$user:quote(); separator="\n"$ >>  quote ::= " '$it$'"

In this case, you would get output like:

Hi \t 'Bob' \t 'Ephram' \t 'Mary'

where the combined indentation is tab plus space for the attribute references in template quote. Expression $user$ is indented by 1 tab and hence any attribute generated from it (in this case the $attr$ of quote()) must have at least the tab.

Consider generating nested statement lists as in C. Any statements inside must be nested 4 spaces. Here are two templates that could take care of this:

function(name,body) ::= << void $name$() $body$ >>  slist(statements) ::= << {     $statements; separator="\n"$ }>>

Your code would create a function template instance and an slist instance, which gets passed to the function template as attribute body. The following C# code:

StringTemplate f = group.GetInstanceOf("function"); f.SetAttribute("name", "foo"); StringTemplate body = group.GetInstanceOf("slist"); body.SetAttribute("statements", "i=1;"); StringTemplate nestedSList = group.GetInstanceOf("slist"); nestedSList.SetAttribute("statements", "i=2;"); body.SetAttribute("statements", nestedSList); body.SetAttribute("statements", "i=3;"); f.SetAttribute("body", body);

将输出类似下面的文本:

void foo() {     i=1;     {         i=2;     }     i=3; }

Indentation can only occur at the start of a line so indentation is only tracked in front of attribute expressions following a newline.

The one exception to indentation is that naturally, $if$ actions do not cause indentation as they do not result in any output. However, the subtemplates (THEN and ELSE clauses) will see indentations. For example, in the following template, the two subtemplates are indented by exactly 1 space each:

$if(foo)$  $x$ \t\t$else  $y$ $endif$

例子

看看Antlr.StringTemplate.Tests.TestStringTemplate.cs这个代码文件, 里面有好多的测试例子.

例子:填充表格

The manner in which a template engine handles filling an HTML table with data often provides good insight into its programming and design strategy. It illustrates the interaction of the model and view via the controller. Using StringTemplate, the view may not access the model directly; rather the view is the passive recipient of data from the model.

First, imagine we have objects of type User that we will pull from a simulated database:

public class User {     String name;     int age;     public User(String name, int age) {         this.name = name;         this.age = age;     }     public String Name { get { return name; } }     public int    Age  { get { return age; }  } }

Our database is just a static list:

static User[] users = new User[] {     new User("Boris", 39),     new User("Natasha", 31),     new User("Jorge", 25),     new User("Vladimir", 28) };

Here is my simple overall page design template, page.st:

<html> <head> <title>$title$</title> </head> <body> <h1>$title$</h1>  $body$  </body> </html>

The body attribute of page.st will be set to the following template users.inline.st by my web server infrastructure (part of the controller):

<table border=1> $users:{   <tr>     <td>$it.name$</td><td>$it.age$</td>   </tr> }$ </table>

Again, it is the default attribute passed to a template when you apply that template to an attribute or attributes. it.name gets the name property, if it exists, from the it object. That is, StringTemplate uses reflection to call the Name property on the incoming object. By using reflection, I avoid a type dependence between model and view.

Now, imagine the server and templates are set up to format data. My page definition is part of the controller that pulls data from the model (the database) and pushes into the view (the template). That is all the page definition should do--interpret the data and set some attributes in the view. The view only formats data and does no interpretation.

public class UserListPage : SamplePage  {     /** This "controller" pulls from "model" and pushes to "view" */     public void GenerateBody(StringTemplate bodyST)     {         User[] list = users; // normally pull from database         // filter list if you want here (not in template)         bodyST.SetAttribute("users", list);     }      public String Title { get {return "User List"; } } }

Notice that the controller and model have no HTML in them at all and that the template has no code with side-effects or logic that can break the model-view separation. If you wanted to only see users with age < 30, you would filter the list in GenerateBody() rather than alter your template. The template only displays information once the controller pulls the right data from the model.

Pushing factorization further, you could make a row.st component in order to reuse the table row HTML:

<tr>   <td>$it.name$</td><td>$it.age$</td> </tr>

Then the user list template reduces to the more readable:

<table border=1> $users:row()$ </table>

Naturally, you could go one step further and make another component for the entire table (putting it in file table.st):

<table border=1> $elements:row()$ </table>

then the body template would simply be:

$table(elements=users)$

国际化与本地化

StringTemplate provides a simple and effective method for localizing web pages. The goal is to alter a page based upon the locale; that is, page strings or other content must change depending on a locale. This article not only illustrates how to make a pages change text depending on locale, it shows how the same site may easily have two different skins (site "looks").

This technique works well in practice for real sites. Schoolloop.com is a case in point. Click on the link that says "en espanol" to flip the site into Spanish mode. The exam same templates are used; all strings are pulled from a serious of resource bundles. There is no duplication of pages to change the strings.

多皮肤

First let's look at multiple skins in order to show how templates are loaded for this example.

Multiple site looks are organized into their own directories. A StringTemplateGroup object rooted at that directory will load templates directly from there. In my example, I made two skins, blue and red. Here is how the group is loaded:

string skin="blue"; // get a template group rooted at appropriate skin string absoluteSkinRootDirectoryName = Path.Combine(new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory).Parent.FullName, skin); StringTemplateGroup templates = new StringTemplateGroup("test", absoluteSkinRootDirectoryName);

Then, when you ask for an instance of a page, it pulls from whichever directory skin is set to:

StringTemplate page1ST = templates.GetInstanceOf("page1");

Here is page 1 in the blue skin:

<html> <title>$strings.page1_title$</title> <body> <font color=blue> <p>$strings.intro$  <p>$strings.page1_content$ </font> </body> <html>

and here is page 2:

<html> <title>$strings.page2_title$</title> <body> <font color=blue> <p>$strings.page2_content$ </font> </body> <html>

For the red here is page 1:

<html> <title>$strings.page1_title$</title> <body> <font color=red> <h1>$strings.page1_title$</h1>  <p>$strings.intro$ <hr> <p>$strings.page1_content$ </font> </body> <html>

and page 2:

<html> <title>$strings.page2_title$</title> <body> <font color=red> <h1>$strings.page2_title$</h1> <hr> <p>$strings.page2_content$ </font> </body> <html>

The thing to note is that there is no text, just formatting in these page templates. Those strings from the strings attribute are used for all text that could change per locale.

本地化模板字符串

Once the code knows how to load templates, the locale must dictate which strings are displayed. The templates clearly have attribute references pulling from a strings table (a hash table). For this example, I'm assuming that I have a few different strings and that I'm storing them in resource files called Content.Strings.resx. The langauge specific alternatives are named Content.Strings[].resx where language-code is the two-letter language code. Here are the name/value entries stored in Content.Strings.resx:

intro=Welcome to my test page for internationalization with StringTemplate page1_title=Page 1 testing I18N page2_title=Page 2 testing I18N page1_content=This is page 1's simple page content page2_content=This is page 2's simple page content

and here are the related entries in the Content.Strings.fr.resx file:

intro=Bienvenue sur la page de test d'internationalisation avec StringTemplate page1_title=Page 1 test de I18N page2_title=Page 2 test de I18N page1_content=Le contenu de la page 1 page2_content=Le contenu de la page 2

To load these per the current locale is pretty easy:

// allow them to override language from argument on command line String language = defaultLanguage; if ( args.length>0 ) {     language = args[0]; } Thread.CurrentThread.CurrentUICulture = new CultureInfo(language); ResourceManager resMgr = new ResourceManager("ST.Examples.i18n.Content.Strings", typeof(ResourceWrapper).Assembly); ResourceWrapper strings = new ResourceWrapper(resMgr);

The strings properties object is just a wrapper around the ResourceManager so you can directly pass to StringTemplate templates as an attribute:

class ResourceWrapper {
ResourceManager mgr;
public ResourceWrapper(ResourceManager mgr)
{ this.mgr = mgr; }
public string intro
{ get { return mgr.GetString("intro"); } }
....... .......
public string page2_content
{ get { return mgr.GetString("page2_content"); } } }
StringTemplate page1ST = templates.GetInstanceOf("page1");
page1ST.SetAttribute("strings", strings);

To generate the page, just say:

Console.Out.WriteLine(page1ST);

The output will be (for the en locale):

<html> <title>Page 1 testing I18N</title>
<body>
<font color=blue>
<p>Welcome to my test page for internationalization with StringTemplate
<p>This is page 1's simple page content </font>
</body>
<html>

If I change the locale to fr then without changes templates, the following is generated:

<html> <title>Page 1 test de I18N</title> <body> <font color=blue> <p>  <p>Le contenu de la page 1 </font> </body> <html>

源代码及编译

Here is the C# code, different strings, and different skins:

You naturally need ANTLR too; get it here.

You should compile this example with the VS.NET project file. The command-line options are:

i18n [ <language_code> [<skin_dir>] ]
For example:
i18n  i18n fr-CA  i18n fr-FR red

总结

In summary, the key element to demonstrate here is that you do not have to duplicate all of your templates to change what they say. You can leave the formatting alone and, with a simple hashtable pulled from a data file, push in the proper strings per the locale. I also took the opportunity to show off just how easy it is to make multiple site skins.

StringTemplate 语法

StringTemplate has multiple grammars that describe templates at varying degrees of detail. At the grossest level of granularity, the group.g grammar accepts a list of templates with formal template arguments. Each of these templates is broken up into chunks of literal text and attribute expressions via template.g. The default lexer uses $...$ delimiters, but the angle.bracket.template.g lexer provides <...> delimiters. Each of the attribute expression chunks is processed by action.g. It builds trees (ASTs) representing the operation indicated in the expression. These ASTs represent the "precompiled" templates, which are evaluated by the tree grammar eval.g each time a StringTemplate is rendered to string with ToString().

The grammar files are:

  • group.g: read a group file full of templates
  • template.g: break an individual template into chunks
  • angle.bracket.template.g: <...> template lexer
  • action.g: parse attribute expressions into ASTs
  • eval.g: evaluate expression ASTs during ToString()

Anything outside of the StringTemplate start/stop delimiters is ignored.

A word about Strings. Strings are double-quoted with optional embedded escaped characters that are translated (escapes are not translated outside of strings; for example, text outside of attribute expressions do not get escape chars translated except \\$, \\< and \\>).

STRING: '"' (ESC_CHAR | ~'"')* '"'     ;

The translated escapes are:

ESC_CHAR : '\\' ( 'n' |   'r' |  't' | 'b' | 'f' | '"' | '\\' )      ;

but other escapes are allowed and ignored.

Please see the actual grammar files for the formal language specification of StringTemplate's various components.

一个普遍的语法问题(A Common Syntax Question)

Why have a template like:

Check this faq entry: $link(url="/faq/view?ID="+id, title="A FAQ")$.

instead of

Check this faq entry: $link(url="/faq/view?ID="+$id$, title="A FAQ")$.

using $id$ instead of id for that attribute reference? The simplest answer is that you are already within the scope of an attribute expression between the $...$ and hence StringTemplate knows that id must be a reference to an attribute by the grammar alone. The template delimiters mark the beginning and end of what StringTemplate cares about.

Another way to look at it is the following. Surely syntax (i) makes the most sense:

(i) Select $col+blort$ FROM $table$

(ii) Select $$col$+$blort$$ FROM $table$

(iii) Select $col$+$blort$ FROM $table$

Syntax (ii) is rather verbose and redundant wouldn't you say? Syntax (iii) doesn't even work because the + is outside the realm of StringTemplate delimiters.

鸣谢

Tom Burns (CEO jGuru.com) co-designed StringTemplate and listened to me think out loud incessantly. I would also like to thank Monty Zukowski for planting the template "meme" in my head back in the mid 1990's. Loring Craymer and Monty both helped me hone these ideas for use in source-to-source language translation. Matthew Ford has done a huge amount of thinking about StringTemplate and has submitted numerous suggestions and patches. Anthony Casalena at http://www.squarespace.com has been a big help beta-testing StringTemplate. Thanks to the users who have submitted suggestions, bugs, sample code, etc...

转载自 Kiyeer‘s Blog

原文地址 http://kiyeer.net/article.asp?id=810


类别:.net dev tips | 添加到搜藏 | 浏览() | 评论 (2)
 
最近读者:
 
网友评论:
1
2008-09-23 17:39 | 回复
日 弄个这么长的来看- -!!!
 
2
2008-09-25 11:07 | 回复
谁让StringTemplate的文档写的那么差劲来~ 只好拿第3放的这个文档来看~
 
发表评论:
姓 名:
网址或邮箱: (选填)
内 容:
验证码: 请点击后输入四位验证码,字母不区分大小写
      

     

©2009 Baidu