在上一篇文章中介绍了dasBlog模板引擎的两个概念Theme和Macro。这一篇文章介绍dasBlog模板引擎的运行过程。
先简单概括一下dasBlog模板引擎的原理。它提供了一个ShareBasePage的页基类,所有的页面类都从这个类派生,在这个类里,进行页面状态的传递和最终页面的生成。可以说,这是整个模板引擎的一个全局控制者。生成的方法是:在ShareBasePage里读取相应的template文件,顺序提取其中的HTML代码和宏进行相应的处理后添加到页面的PlaceHolder中。HTML代码是被添加到Literal后添加,取得宏表达式后,会转换成相应的Control后再添加到PlaceHolder。这就是页面生成的整个过程。
我们先来看看SharedBasePage的构造函数。从里面两个函数名就可以看出,进行的是状态加载和页面初始化的任务。
{
Init += new EventHandler(this.SessionLoad);
Init += new EventHandler(this.SetupPage);
}
SharedBasePager中的Property添加了自定义的PersistentPageStateAttribute属性之后,每次页面加载完成,该属性都会被保存在Cookie中,第一次开始加载的时候又会被读出来。这个读取来的工作就在SessionLoad里完成了,而保存的工作就在SessionStore里完成了。
而SetupPage做的是些全球和参数处理方面的操作。在SetupPage中还完成了一个很重要的工作,加载了Macros类,这个类里包含了一些全局宏。所谓全局宏是指:网站名称,Trackback排行榜等在任何一个页面都会被显示的内容。相对的,有继承自Macros类的DayMacros(显示某日文章列表的页面的宏的集合),ItemMacros(显示单篇文章的页面宏的集合),EditMacros(文章编辑页面的宏的集合)。
当SharedBasePage初始化完成之后,就开始正式进入页面生成步骤了。在SharedBasePage里ProcessTemplate方法是做这个工作的。以下是它的源码:
{
TemplateProcessor templateProcessor = new TemplateProcessor();
string path = Request.PhysicalApplicationPath;
string templateString = GetPageTemplate(path);
Match match = findBodyTag.Match(templateString);
if ( match.Success )
{
// this section splits the template into a header, body and footer section
// (all above and including <body>, everything between <body></body> and all below and including </body>
//找到<body>的位置
int indexBody = templateString.IndexOf("</body>");
if ( indexBody == -1 )
{
indexBody = templateString.IndexOf("</BODY>");
}
//取得<head>部分
// the header template contains everything above and including the body tag
string headerTemplate=templateString.Substring(0,match.Index+match.Length);
// insert necessary headtags and fix stylesheet relative links
// fix any relative css link tags
//转换地址
headerTemplate = hrefRegEx.Replace(headerTemplate, String.Format("href=\"{0}themes", Utils.GetBaseUrl()));
string baseTag = String.Format("<base href=\"{0}\"></base>\r\n", Utils.GetBaseUrl());
string linkTag = String.Format("<link rel=\"alternate\" type=\"application/rss+xml\" title=\"{2}\" href=\"{0}\" />\r\n<link rel=\"alternate\" type=\"application/atom+xml\" title=\"{2}\" href=\"{1}\" />\r\n", Utils.GetRssUrl(),Utils.GetAtomUrl(),HttpUtility.HtmlEncode(siteConfig.Title));
int indexHead = headerTemplate.IndexOf("</head>");
if ( indexHead == -1 )
{
indexHead = headerTemplate.IndexOf("</HEAD>");
}
// 给<head>部分添加css等
// therefore it must close with a closing angle bracket, but it's better to check
if ( headerTemplate[headerTemplate.Length-1] == '>' )
{
// if that's so, we want to inject the reading order designator if we're right-to-left
// or it's explicitly specified
string pageReadingDirection = coreStringTables.GetString("page_reading_direction");
if ( pageReadingDirection != null && pageReadingDirection.Length > 0 )
{
if (pageReadingDirection == "RTL") this.readingDirection = TextDirection.RightToLeft;
headerTemplate = headerTemplate.Substring(0, headerTemplate.Length-1) + " dir=\"" + pageReadingDirection + "\">";
}
}
string bodyTemplate,footerTemplate;
if( indexBody != -1 )
{
bodyTemplate=templateString.Substring(match.Index+match.Length,indexBody-(match.Index+match.Length));
footerTemplate=templateString.Substring(indexBody);
}
else
{
bodyTemplate=templateString.Substring(match.Index+match.Length);
footerTemplate="";
}
// 对headerTemplate进行宏处理,添加到页面的PlaceHolder中
templateProcessor.ProcessTemplate( this, headerTemplate, ContentPlaceHolder, macros );
BaseHtmlForm mainForm = new BaseHtmlForm();
mainForm.ID = "mainForm";
ContentPlaceHolder.Controls.Add(mainForm);
// 对bodyTemplate进行宏处理,添加到mainForm中
templateProcessor.ProcessTemplate( this, bodyTemplate, mainForm, macros );
// 对footerTemplate进行宏处理,添加到页面的PlaceHolder中
if ( footerTemplate.Length > 0 )
{
templateProcessor.ProcessTemplate( this, footerTemplate, ContentPlaceHolder, macros );
}
}
else
{
// if the page is just an unrecognizable mess of tags, process in one shot.
templateProcessor.ProcessTemplate( this, templateString, ContentPlaceHolder, macros );
}
}
在这个方法里,只是进行了整个生成流程的控制,第一步工作当然是取得模板文件了。
模板文件的获取是在Theme类里面完成了的。Theme里的OpenTemplate()函数返回一个TextReader对象,在SharedBasePage里读成string,页面生成的过程就是对这个string进行操作了。而真正生成页面的工作是在TemplateProcessor这个类里完成的。而TemplateProcessor里起主要作用的其实也只是一个ProcessTemplate方法,其代码如下:
{
int lastIndex = 0;
MatchCollection matches = templateFinder.Matches(templateString);
foreach( Match match in matches )
{
//添加宏之外的其它字符串
if ( match.Index > lastIndex )
{
contentPlaceHolder.Controls.Add(new LiteralControl(templateString.Substring(lastIndex,match.Index-lastIndex)));
}
Group g = match.Groups["macro"];
Capture c = g.Captures[0];
Control ctrl = null;
object targetMacroObj = macros;
string captureValue = c.Value;
//Check for a string like: <%foo("bar", "bar")|assemblyConfigName%>
int assemblyNameIndex = captureValue.IndexOf(")|");
//是否是自定义宏
if (assemblyNameIndex != -1) //use the default Macros
{
//The QN minus the )|
//取得自定义assemblyName
string macroAssemblyName = captureValue.Substring(assemblyNameIndex+2);
//The method, including the )
//取得宏名称
captureValue = captureValue.Substring(0,assemblyNameIndex+1);
//生成自定义宏对象
try
{
targetMacroObj = MacrosFactory.CreateCustomMacrosInstance(page, entry, macroAssemblyName);
}
catch (Exception ex)
{
page.LoggingService.AddEvent(new EventDataItem(EventCodes.Error,String.Format("Error executing Macro: {0}",ex.ToString()),string.Empty));
}
}
try
{
//执行宏,返回一个对象
ctrl = InvokeMacro(targetMacroObj,captureValue) as Control;
if (ctrl != null)
{
contentPlaceHolder.Controls.Add(ctrl);
}
else
{
page.LoggingService.AddEvent(new EventDataItem(EventCodes.Error,String.Format("Error executing Macro: {0} returned null.",captureValue),string.Empty));
}
}
catch (Exception ex)
{
string error = String.Format("Error executing macro: {0}. Make sure it you're calling it in your BlogTemplate with paratheses like 'myMacro()'. Macros with parameter lists and overloads must be called in this way. Exception: {1}",c.Value, ex.ToString());
page.LoggingService.AddEvent(new EventDataItem(EventCodes.Error,error,string.Empty));
}
lastIndex = match.Index+match.Length;
}
if ( lastIndex < templateString.Length)
{
//将宏添加到PlacheHolder中
contentPlaceHolder.Controls.Add(new LiteralControl(templateString.Substring(lastIndex,templateString.Length-lastIndex)));
}
}
这里的做的工作就是匹配所有类似<%SiteName%>这样的字符串,把它转化成相应的Control,我把它叫做“执行宏”,执行宏的工作在这里进行:
以下是InvodeMacro代码:
{
//子字符串的开始位置
int subexStartIndex = 0;
//子字符串的结束位置
int subexEndIndex = 0;
object subexObject = obj;
try
{
do
{
subexEndIndex = expression.IndexOfAny(new char[]{'.','(','['}, subexStartIndex);
if ( subexEndIndex == -1 ) subexEndIndex=expression.Length;
//取得当次循环处理的宏的字符串表达式
string subex = expression.Substring(subexStartIndex,subexEndIndex-subexStartIndex);
//为下次循环做准备
subexStartIndex = subexEndIndex+1;
MemberInfo memberToInvoke;
//搜索与子字符串匹配的对象或方法
MemberInfo[] members = subexObject.GetType().FindMembers(
MemberTypes.Field|MemberTypes.Method|MemberTypes.Property,
BindingFlags.IgnoreCase|BindingFlags.Instance|BindingFlags.Public,
new MemberFilter(this.IsMemberEligibleForMacroCall), subex.Trim() );
string[] arglist=null;
if ( members.Length == 0 )
{
throw new MissingMemberException(subexObject.GetType().FullName,subex.Trim());
}
//当当前宏带有参数时,取得当前宏的参数
if ( subexEndIndex<expression.Length && (expression[subexEndIndex] == '[' || expression[subexEndIndex] == '(') )
{
arglist = SplitArgs(expression,subexEndIndex, ref subexStartIndex);
}
//SDH: We REALLY need to refactor this whole Clemens thing - it's getting hairy.
memberToInvoke = null;
if ( members.Length > 1 )
{
foreach(MemberInfo potentialMember in members)
{
MethodInfo potentialMethod = potentialMember as MethodInfo;
if(potentialMethod != null)
{
ParameterInfo[] parameters = potentialMethod.GetParameters();
if(parameters != null && parameters.Length > 0)
{
//当参数数量相同时判定为要执行的宏
if(parameters.Length == arglist.Length)
{
memberToInvoke = potentialMember;
break;
}
}
}
}
}
//如果不存在该方法,则使用第一个
if(memberToInvoke == null)//Previous behavior, use the first one.
{
memberToInvoke = members[0];
}
//当要执行的宏是一个属性,类似这样的东西Lupin.或者LUPIN[a,b]
if ( memberToInvoke.MemberType == MemberTypes.Property &&
(subexEndIndex==expression.Length ||
expression[subexEndIndex] == '.' ||
expression[subexEndIndex] == '[' ))
{
PropertyInfo propInfo = memberToInvoke as PropertyInfo;
if ( subexEndIndex<expression.Length && expression[subexEndIndex] == '[' )
{
System.Reflection.ParameterInfo[] paramInfo = propInfo.GetIndexParameters();
if ( arglist.Length > paramInfo.Length )
{
throw new InvalidOperationException(String.Format("Parameter list length mismatch {0}",memberToInvoke.Name));
}
object[] oarglist = new object[paramInfo.Length];
for( int n=0;n<arglist.Length;n++)
{
oarglist[n] = Convert.ChangeType(arglist[n],paramInfo[n].ParameterType);
}
subexObject = propInfo.GetValue(subexObject,oarglist);
}
else
{
subexObject = propInfo.GetValue(subexObject,null);
}
}
else if ( memberToInvoke.MemberType == MemberTypes.Field &&
(subexEndIndex==expression.Length ||
expression[subexEndIndex] == '.'))
{
FieldInfo fieldInfo = memberToInvoke as FieldInfo;
subexObject = fieldInfo.GetValue(subexObject);
}
else if ( memberToInvoke.MemberType == MemberTypes.Method &&
subexEndIndex<expression.Length && expression[subexEndIndex] == '(' )
{
MethodInfo methInfo = memberToInvoke as MethodInfo;
System.Reflection.ParameterInfo[] paramInfo = methInfo.GetParameters();
if ( arglist.Length > paramInfo.Length &&
!(paramInfo.Length>0 && paramInfo[paramInfo.Length-1].ParameterType == typeof(string[])))
{
throw new InvalidOperationException(String.Format("Parameter list length mismatch {0}",memberToInvoke.Name));
}
object[] oarglist = new object[paramInfo.Length];
for( int n=0;n<arglist.Length;n++)
{
if ( n == paramInfo.Length-1 &&
arglist.Length>paramInfo.Length)
{
string[] paramsArg = new string[arglist.Length-paramInfo.Length+1];
for( int m=n;m<arglist.Length;m++)
{
paramsArg[m-n] = Convert.ChangeType(arglist[n],typeof(string)) as string;
}
oarglist[n] = paramsArg;
break;
}
else
{
oarglist[n] = Convert.ChangeType(arglist[n],paramInfo[n].ParameterType);
}
}
subexObject = methInfo.Invoke(subexObject,oarglist);
}
}
while(subexEndIndex<expression.Length && subexStartIndex < expression.Length);
return subexObject==null?new LiteralControl(""):subexObject;
}
catch( Exception exc )
{
ErrorTrace.Trace(System.Diagnostics.TraceLevel.Error,exc);
throw;
}
//return new LiteralControl("");
}
这个函数相当于是个超简单的脚本解释引擎,具体的工作流程已经超出来本篇的讨论内容,而且再说下去这篇文章就太长了。。所以,就此打住。dasBlog的模板引擎的实现也解释得差不多了。