StringTemplate属性范围 模板引用
属性范围:
StringTemplate的文档:
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 在group/supergroup链的map里面搜索
同一个模板,可能会被在多个模板中调用,那这个模板所需要的参数,需要慎重考虑如何设置。
1. 在模板嵌套链的上一层、上上一层...设置,通过模板调用自然传递下来。
2. 上一层模板使用参数方式传过来。
属性查找采用就近原则,即在搜索路径上,找到第一个就停止。所以需要关注属性的传递、覆盖问题。
StringTemplate里面有个Formal Argument的概念,还不清楚怎样使用,但可以肯定它跟属性的范围、传递相关的。
一个属性范围相关问题的解决方案
上面这样一个显示商品的html块,可以提取成一个模板item.st。当页面需要显示多个商品时怎么办?
$items: {<div>$item()$</div>}$这样的方法看似能够解决问题,但是它只适用于在一排显示4个、5个商品这样的情况,如果我需要在页面的不同位置显示这几个商品呢?
我的做法是提供一个界面定义item1.st、item2.st...,他们都使用item.st这个模板,并且让用户选择一个要显示的商品。item1.st、item2.st...的内容: $item()$,它们都只是调用一下item.st这个模板,程序根据item1关联的商品,可以把这个商品加载出来,把商品属性设置在item1上。这样,在开发时就可以在界面的任意位置使用类似$item1()$、$item2()$这样的方法,显示不同的商品了。
这种情况下,item.st类似于一个类,而item1.st、item2.st等类似于类的不同实例了。
模板引用:
创建一个StringTemplateGroup后,他会集中管理各个StringTemplate对象,StringTemplateGroup内部使用一个Hashtable缓存已经加载、定义的ST对象。
group.LookupTemplate(string name): 直接加载ST对象,或者从缓存中获取,将ST对象的直接引用返回给调用者。
group.GetInstanceOf(string name): 加载ST对象,或者从缓存中获取,将ST对象作一份DeepCopy(不包括Attributes、EnclosingInstance)返回给调用者。
这个区别就很明显了,如果对LookupTemplate返回的对象进行修改,StringTemplateGroup内部缓存的那个也被修改了;而GetInstanceOf则不会。有的时候你需要使用DeepCopy的实例,而有的时候需要直接使用缓存的那个,根据具体情况决定使用哪个方法了。
看一下这样一个需求:
a.st: $b()$
b.st: $c()$ - $attrB$
c.st: $attrC$
工作代码类似这样:
在这个简单的例子里面,可以在用a.SetAttribute()设置attrB、attrC属性来解决。不过我的情况比较复杂,必须要改变StringTemplate对象的这个行为,所以我修改了原代码来实现。
在StringTemplateGroup.cs里面
看源代码发现的另外一个问题:
StringTemplate会监控*.st文件,如果文件有修改,会重新加载相应的ST对象,用它把原来内部缓存的对象给覆盖掉。假如你为内部缓存的对象设置好属性,调用StringTemplate开始解析,这个时候st文件被修改了,那么设置的属性就被丢弃掉了。不过模板被修改,对当前的解析过程肯定有影响;不监控模板文件的变化,也不合适。我根据自己的情况,还是选择在覆盖的时候,把原来的属性copy过来,这样符合我的需求。
把StringTemplateGroup.cs文件下面的方法改成这样就可以了
其它问题:
从StringTemplate.Net源代码的一些主要处理过程来看,里面是存在不少缺陷的,一些处理方案比较粗糙,考虑的不够成熟完善,可能给系统带来隐患问题,特别是多线程的并发、冲突等方面的控制,基本就看不到一个全局清晰的解决思路。
所以,原来还以为可以全局共享同一个StringTemplateGroup对象,现在只能改成每个请求需要使用时,就新开一个StringTemplateGroup了,毕竟这样安全。
这一次在架构之前没有很好的掌握StringTemplate,吃了一点亏(加班、核心架构做了一次重大调整)。StringTemplate本身文档很少,要全面掌握它的使用方法,必须结合源代码,投入的精力比较大。
不过它的思想还是非常不错,一个开源项目要成熟下来,是需要大量的投入和时间,以及实践反馈的。基于这样一个状况,采用BSD协议也是它最好的一个选择了。现在看来,使用它解决我当前项目的问题,实在是最合适不过了,框架的核心部分简洁而又行云流水,扩展性非常强,StringTemplate本身的一些问题,后续再慢慢完善,也是非常值得的。
StringTemplate的文档:
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 在group/supergroup链的map里面搜索
同一个模板,可能会被在多个模板中调用,那这个模板所需要的参数,需要慎重考虑如何设置。
1. 在模板嵌套链的上一层、上上一层...设置,通过模板调用自然传递下来。
2. 上一层模板使用参数方式传过来。
属性查找采用就近原则,即在搜索路径上,找到第一个就停止。所以需要关注属性的传递、覆盖问题。
StringTemplate里面有个Formal Argument的概念,还不清楚怎样使用,但可以肯定它跟属性的范围、传递相关的。
一个属性范围相关问题的解决方案
上面这样一个显示商品的html块,可以提取成一个模板item.st。当页面需要显示多个商品时怎么办?
$items: {<div>$item()$</div>}$这样的方法看似能够解决问题,但是它只适用于在一排显示4个、5个商品这样的情况,如果我需要在页面的不同位置显示这几个商品呢?
我的做法是提供一个界面定义item1.st、item2.st...,他们都使用item.st这个模板,并且让用户选择一个要显示的商品。item1.st、item2.st...的内容: $item()$,它们都只是调用一下item.st这个模板,程序根据item1关联的商品,可以把这个商品加载出来,把商品属性设置在item1上。这样,在开发时就可以在界面的任意位置使用类似$item1()$、$item2()$这样的方法,显示不同的商品了。
这种情况下,item.st类似于一个类,而item1.st、item2.st等类似于类的不同实例了。
模板引用:
创建一个StringTemplateGroup后,他会集中管理各个StringTemplate对象,StringTemplateGroup内部使用一个Hashtable缓存已经加载、定义的ST对象。
group.LookupTemplate(string name): 直接加载ST对象,或者从缓存中获取,将ST对象的直接引用返回给调用者。
group.GetInstanceOf(string name): 加载ST对象,或者从缓存中获取,将ST对象作一份DeepCopy(不包括Attributes、EnclosingInstance)返回给调用者。
这个区别就很明显了,如果对LookupTemplate返回的对象进行修改,StringTemplateGroup内部缓存的那个也被修改了;而GetInstanceOf则不会。有的时候你需要使用DeepCopy的实例,而有的时候需要直接使用缓存的那个,根据具体情况决定使用哪个方法了。
看一下这样一个需求:
a.st: $b()$
b.st: $c()$ - $attrB$
c.st: $attrC$
工作代码类似这样:
StringTemplateGroup group = new StringTemplateGroup("test", @"c:\test");
StringTemplate a = group.GetInstanceOf("a");
StringTemplate b = group.LookupTemplate("b");
StringTemplate c = group.LookupTemplate("c");
b.SetAttribute("attrB","test attr B");
b.SetAttribute("attrC","test attr C");
Assert.AreEqual("test attr C - test attr B", a.ToString());
这段代码无法得到预期的结果,因为StringTemplate在a.ToString()方法解析过程中,使用GetInstanceOf方法获取b.st、c.st这两个模板对象,也就是使用了缓存对象的一个DeepCopy版本,没有包含Attributes。StringTemplate a = group.GetInstanceOf("a");
StringTemplate b = group.LookupTemplate("b");
StringTemplate c = group.LookupTemplate("c");
b.SetAttribute("attrB","test attr B");
b.SetAttribute("attrC","test attr C");
Assert.AreEqual("test attr C - test attr B", a.ToString());
在这个简单的例子里面,可以在用a.SetAttribute()设置attrB、attrC属性来解决。不过我的情况比较复杂,必须要改变StringTemplate对象的这个行为,所以我修改了原代码来实现。
在StringTemplateGroup.cs里面
public virtual StringTemplate GetEmbeddedInstanceOf(StringTemplate enclosingInstance, string name)
修改成public virtual StringTemplate GetEmbeddedInstanceOf(StringTemplate enclosingInstance, string name)
{
StringTemplate st = null;
// TODO: seems like this should go into LookupTemplate
if (name.StartsWith("super."))
{
// for super.foo() refs, ensure that we look at the native
// group for the embedded instance not the current evaluation
// group (which is always pulled down to the original group
// from which somebody did group.getInstanceOf("foo");
//modified by RicCC, 08.22.2007
//st = enclosingInstance.NativeGroup.GetInstanceOf(enclosingInstance, name);
st = enclosingInstance.NativeGroup.LookupTemplate(enclosingInstance, name);
}
else
{
//modified by RicCC, 08.22.2007
//st = GetInstanceOf(enclosingInstance, name);
st = LookupTemplate(enclosingInstance, name);
}
// make sure all embedded templates have the same group as enclosing
// so that polymorphic refs will start looking at the original group
st.Group = this;
st.EnclosingInstance = enclosingInstance;
return st;
}
方法里面的TODO注释也说明了作者在考虑应该使用LookupTemplate方法,应当是TODO忘了Review了,否则也不应当再把这个TODO留在这里。{
StringTemplate st = null;
// TODO: seems like this should go into LookupTemplate
if (name.StartsWith("super."))
{
// for super.foo() refs, ensure that we look at the native
// group for the embedded instance not the current evaluation
// group (which is always pulled down to the original group
// from which somebody did group.getInstanceOf("foo");
//modified by RicCC, 08.22.2007
//st = enclosingInstance.NativeGroup.GetInstanceOf(enclosingInstance, name);
st = enclosingInstance.NativeGroup.LookupTemplate(enclosingInstance, name);
}
else
{
//modified by RicCC, 08.22.2007
//st = GetInstanceOf(enclosingInstance, name);
st = LookupTemplate(enclosingInstance, name);
}
// make sure all embedded templates have the same group as enclosing
// so that polymorphic refs will start looking at the original group
st.Group = this;
st.EnclosingInstance = enclosingInstance;
return st;
}
看源代码发现的另外一个问题:
StringTemplate会监控*.st文件,如果文件有修改,会重新加载相应的ST对象,用它把原来内部缓存的对象给覆盖掉。假如你为内部缓存的对象设置好属性,调用StringTemplate开始解析,这个时候st文件被修改了,那么设置的属性就被丢弃掉了。不过模板被修改,对当前的解析过程肯定有影响;不监控模板文件的变化,也不合适。我根据自己的情况,还是选择在覆盖的时候,把原来的属性copy过来,这样符合我的需求。
把StringTemplateGroup.cs文件下面的方法改成这样就可以了
public virtual StringTemplate LookupTemplate(StringTemplate enclosingInstance, string name)
{
lock (this)
{
if (name.StartsWith("super."))
{
if (superGroup != null)
{
int dot = name.IndexOf('.');
name = name.Substring(dot + 1, (name.Length) - (dot + 1));
StringTemplate superScopeST =
superGroup.LookupTemplate(enclosingInstance, name);
return superScopeST;
}
throw new StringTemplateException(Name + " has no super group; invalid template: " + name);
}
StringTemplate st = (StringTemplate)templates[name];
//added by RicCC, 08.20.2007
IDictionary originalAttributes = null; //added
if (st != null)
{
// Discard cached template?
if (st.NativeGroup.TemplateHasChanged(name))
{
//added by RicCC, 08.20.2007, discard the cached template, but save it's attributes
originalAttributes = st.attributes; //added
templates.Remove(name);
st = null;
}
}
if (st == null)
{
// not there? Attempt to load
if (!templatesDefinedInGroupFile)
{
// only check the disk for individual template
st = LoadTemplate(name);
}
if (st == null && superGroup != null)
{
// try to resolve in super group
//st = superGroup.GetInstanceOf(name);
st = superGroup.GetInstanceOf(enclosingInstance, name);
// make sure that when we inherit a template, that it's
// group is reset; it's nativeGroup will remain where it was
if (st != null)
{
st.Group = this;
}
}
if (st != null)
{
// found in superGroup
// insert into this group; refresh will allow super
// to change it's def later or this group to add
// an override.
//added by RicCC, 08.20.2007
if (originalAttributes != null)
st.attributes = originalAttributes; //added, approach this by invoking SetAttribute() method?
templates[name] = st;
}
else
{
// not found; remember that this sucker doesn't exist
templates[name] = NOT_FOUND_ST;
string context = "";
if (enclosingInstance != null)
{
context = "; context is " + enclosingInstance.GetEnclosingInstanceStackString();
}
throw new TemplateLoadException(this, "Can't load template '" + GetLocationFromTemplateName(name) + "'" + context);
}
}
else if (st == NOT_FOUND_ST)
{
return null;
}
return st;
}
}
{
lock (this)
{
if (name.StartsWith("super."))
{
if (superGroup != null)
{
int dot = name.IndexOf('.');
name = name.Substring(dot + 1, (name.Length) - (dot + 1));
StringTemplate superScopeST =
superGroup.LookupTemplate(enclosingInstance, name);
return superScopeST;
}
throw new StringTemplateException(Name + " has no super group; invalid template: " + name);
}
StringTemplate st = (StringTemplate)templates[name];
//added by RicCC, 08.20.2007
IDictionary originalAttributes = null; //added
if (st != null)
{
// Discard cached template?
if (st.NativeGroup.TemplateHasChanged(name))
{
//added by RicCC, 08.20.2007, discard the cached template, but save it's attributes
originalAttributes = st.attributes; //added
templates.Remove(name);
st = null;
}
}
if (st == null)
{
// not there? Attempt to load
if (!templatesDefinedInGroupFile)
{
// only check the disk for individual template
st = LoadTemplate(name);
}
if (st == null && superGroup != null)
{
// try to resolve in super group
//st = superGroup.GetInstanceOf(name);
st = superGroup.GetInstanceOf(enclosingInstance, name);
// make sure that when we inherit a template, that it's
// group is reset; it's nativeGroup will remain where it was
if (st != null)
{
st.Group = this;
}
}
if (st != null)
{
// found in superGroup
// insert into this group; refresh will allow super
// to change it's def later or this group to add
// an override.
//added by RicCC, 08.20.2007
if (originalAttributes != null)
st.attributes = originalAttributes; //added, approach this by invoking SetAttribute() method?
templates[name] = st;
}
else
{
// not found; remember that this sucker doesn't exist
templates[name] = NOT_FOUND_ST;
string context = "";
if (enclosingInstance != null)
{
context = "; context is " + enclosingInstance.GetEnclosingInstanceStackString();
}
throw new TemplateLoadException(this, "Can't load template '" + GetLocationFromTemplateName(name) + "'" + context);
}
}
else if (st == NOT_FOUND_ST)
{
return null;
}
return st;
}
}
其它问题:
从StringTemplate.Net源代码的一些主要处理过程来看,里面是存在不少缺陷的,一些处理方案比较粗糙,考虑的不够成熟完善,可能给系统带来隐患问题,特别是多线程的并发、冲突等方面的控制,基本就看不到一个全局清晰的解决思路。
所以,原来还以为可以全局共享同一个StringTemplateGroup对象,现在只能改成每个请求需要使用时,就新开一个StringTemplateGroup了,毕竟这样安全。
这一次在架构之前没有很好的掌握StringTemplate,吃了一点亏(加班、核心架构做了一次重大调整)。StringTemplate本身文档很少,要全面掌握它的使用方法,必须结合源代码,投入的精力比较大。
不过它的思想还是非常不错,一个开源项目要成熟下来,是需要大量的投入和时间,以及实践反馈的。基于这样一个状况,采用BSD协议也是它最好的一个选择了。现在看来,使用它解决我当前项目的问题,实在是最合适不过了,框架的核心部分简洁而又行云流水,扩展性非常强,StringTemplate本身的一些问题,后续再慢慢完善,也是非常值得的。