在上一篇文章中介绍了dasBlog模板引擎的两个概念Theme和Macro。这一篇文章介绍dasBlog模板引擎的运行过程。
先简单概括一下dasBlog模板引擎的原理。它提供了一个ShareBasePage的页基类,所有的页面类都从这个类派生,在这个类里,进行页面状态的传递和最终页面的生成。可以说,这是整个模板引擎的一个全局控制者。生成的方法是:在ShareBasePage里读取相应的template文件,顺序提取其中的HTML代码和宏进行相应的处理后添加到页面的PlaceHolder中。HTML代码是被添加到Literal后添加,取得宏表达式后,会转换成相应的Control后再添加到PlaceHolder。这就是页面生成的整个过程。
我们先来看看SharedBasePage的构造函数。从里面两个函数名就可以看出,进行的是状态加载和页面初始化的任务。
public 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方法是做这个工作的。以下是它的源码:
public virtual void ProcessTemplate()

{
TemplateProcessor templateProcessor = new TemplateProcessor();
string path = Request.PhysicalApplicationPath;
//取得当前使用Template对应的文件内容
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等
headerTemplate = headerTemplate.Insert(indexHead, baseTag.ToString() + linkTag.ToString());

// 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 )

{
//取得template中的主体部分
bodyTemplate=templateString.Substring(match.Index+match.Length,indexBody-(match.Index+match.Length));
// 取得template中的footer部分
footerTemplate=templateString.Substring(indexBody);
}
else

{
bodyTemplate=templateString.Substring(match.Index+match.Length);
footerTemplate="";
}
// 对headerTemplate进行宏处理,添加到页面的PlaceHolder中
templateProcessor.ProcessTemplate( this, headerTemplate, ContentPlaceHolder, macros );
// 初始化一个Form
BaseHtmlForm mainForm = new BaseHtmlForm();
mainForm.ID = "mainForm";
// 将mainForm添加到PlaceHolder中
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 );
}
}
在这个方法里,只是进行了整个生成流程的控制,第一步工作当然是取得模板文件了。
string templateString = GetPageTemplate(path);
模板文件的获取是在Theme类里面完成了的。Theme里的OpenTemplate()函数返回一个TextReader对象,在SharedBasePage里读成string,页面生成的过程就是对这个string进行操作了。而真正生成页面的工作是在TemplateProcessor这个类里完成的。而TemplateProcessor里起主要作用的其实也只是一个ProcessTemplate方法,其代码如下:
public void ProcessTemplate(SharedBasePage page, Entry entry, string templateString, Control contentPlaceHolder, Macros macros)

{
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,我把它叫做“执行宏”,执行宏的工作在这里进行:
ctrl = InvokeMacro(targetMacroObj,captureValue) as Control;
以下是InvodeMacro代码:
private object InvokeMacro( object obj, string expression )

{
//子字符串的开始位置
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());
}

//当当前宏带有参数时,取得当前