网页模板解释器1
这里所说的模板指的是生成静态页面时的模板,同样以下论述都是以此为前提,并且也是以解决此问题为唯一目的的。
在做CMS的时候,经常要用到模板,比如:
2 <head>
3 <title>{title}</title>
4 </head>
5 <body>
6 <div>{content}</div>
7 </body>
8 </html>
我们使用此模板是需要吧里面的标签替换成我们需要的内容:
public bool WriteTemplate(string title, string content)2
{3
string html = Simple.Helper.FileHelper.Read("");4
html = html.Replace("{title}", title);5
html = html.Replace("{content}", content);6
return Simple.Helper.FileHelper.Create(html, "");7
}
这样我们就生成了一个静态文件,由于数据结构的不同,我们需要为每一个对象设计一个不同的模板和一套相关的处理程序,这样显然很麻烦,而且对客户来说也不方便,以下我们就开发一种模板解释器,让它能够自动生成不同模板,只要我们了解数据结构我们就可以设计不同模板,而解释程序只有一份,不需要单独开发。
模板就是器有什么用,第一:以前的模板呢是有UI,数据结构需要程序员设计,用户改动的权限不大;第二:程序员自己也需要设计大量模板,这个工作很枯燥。现在我设计了一种智能的模板解释器,使得模板有自动处理的能力,有点像shtml,不过他还有数据处理,自动分页等一些功能。
先看一个类,只要记住这个类的机构就可以了,当然这里也只有显示了他的机构。这个机构是最基础的,表示了我们需要的一些参数,如要替换的内容或者向下级传递的参数。总共为四种参数:
URL,生成文件的参数如/{0}/{1}/{2}/Default.html;
Content,需要替换的文本参数如<div><ul><li>{0}</li></ul></div>;
Sql,传递给Sql语句的参数如select [title] from [topic] where [id]={0};
Other,传递给下级的内容参数,可一通过它获得上级传下来的内容。
namespace Simple.Helper2
{3
public class TemplateArgs4
{5
public string[] Url { get; set; }6
public string[] Content { get; set; }7
public string[] Sql { get; set; }8
public string[] Other { get; set; }9
}10
}接下来我们开始开发模板,第一个模板我称为Config模板
<?xml version="1.0" encoding="utf-8" ?>2
<Template>3
<Html>4
<Text URL="/[Config:Web|NewsPath]/[Config:DateTime|yyyMMdd]/Default.html">5
<![CDATA[6
[Config:Web|PageSize] 7
[Config:DateTime|yyyy-MM-dd]8
[Config:Include|Default.html]9
[Config:Url|http://simplebbs.cn]10
[Sql:List1|$|$]11
[Sql:List2|[Config:DateTime|yyyy-MM-dd]|$]12
]]>13
</Text>14
<sql ID="List1">15
<Command TYPE="NonQuery">update [list] set [IsNew]=0 where Data='[Config:DateTime|yyyy-MM-dd]'</Command>16
</sql>17
<sql ID="List2">18
<Command TYPE="NonQuery">update [list] set [IsNew]=0 where Data='(S:0)'</Command>19
</sql>20
</Html>21
</Template>在这个模板文件里包含了8个Config模板,这个模板是个最基础最先执行的模板,所以可以包含在其它任何一个模板里,但是不能自己包含自己。这个模板的作用有些类似于shtml页面,比如:
[Config:DateTime|yyyy-MM-dd]自动生成时间,并指定时间的格式是“yyyy-MM-dd”;
[Config:Web|PageSize]读取Web.Config中的数据 <add key="PageSize" value="10"/>;
[Config:Include|Default.html]和[Config:Url|http://simplebbs.cn]分别是包含远程、本地文件;
注意这里的两个Sql模板[Sql:List1|$|$]和[Sql:List2|[Config:DateTime|yyyy-MM-dd]|$]得到的结果是一样的,Sql模板我们在下面介绍。
这个模板可扩展性很强,想怎么扩展都可以。
Config解释器
public static string ReplaceConfig(this string text)2
{3
StringBuilder html = new StringBuilder(text);4
re = new Regex(@"\[Config:(.[^\|]*)\|(.[^\]]*)\]", RegexOptions.IgnoreCase);5
mc = re.Matches(text);6
if (mc.Count > 0)7
{8
foreach (Match c in mc)9
{10
if (c.Groups[1].Value == "Web")11
{12
//[Config:Web|?] ?表示Web.config<appSettings><add key="?" value=""/>的值13
html.Replace(c.Groups[0].Value, Simple.Utility.WebConfig.Load(c.Groups[2].Value).ToString());14
}15
else if (c.Groups[1].Value == "DateTime")16
{17
//[Config:DateTime|yyyy-MM-dd] 表示DateTime.Now.ToString("yyyy-MM-dd")18
html.Replace(c.Groups[0].Value, DateTime.Now.ToString(c.Groups[1].Value));19
}20
else if (c.Groups[1].Value == "Include")21
{22
//[Config:Include|?] ?表示本地文件23
html.Replace(c.Groups[0].Value, FileHelper.Read(c.Groups[2].Value));24
}25
else if (c.Groups[1].Value == "Url")26
{27
//[Config:Url|?] ?表示远程文件如:http://simplebbs.cn28
html.Replace(c.Groups[0].Value, FileHelper.Url(c.Groups[2].Value, System.Text.Encoding.UTF8));29
}30
}31
}32
return html.ToString();33
}
Sql模板
<Html>2
<Text URL="Default.html">3
<![CDATA[[Sql:List1|1|List1]]]>4
</Text>5
<Sql ID="List1" TYPE="Reader">6
<Command>select top 10 [Id], [Title] from [Topic] where [List]=(S:0)</Command>7
<Code>8
<![CDATA[9
<li>(O:0) | <a href="/[Config:Web|TopicPath]?Id=(C:0)" target="_blank">(C:1)</a></li>10
[Sql:List2|1|List2]11
]]>12
</Code>13
</Sql>14
<Sql ID="List2" TYPE="Reader">15
<Command>select top 10 [Id], [Title] from [Topic] where [List]=(S:0)</Command>16
<Code>17
<![CDATA[18
<li>(O:0) | <a href="/[Config:Web|TopicPath]?Id=(C:0)" target="_blank">(C:1)</a></li>19
[Sql:List3|3|$]20
]]>21
</Code>22
</Sql>23
<Sql ID="List3" TYPE="NonQuery">24
<Command>update [List] set [view]=[View]+1 where [Id]=(S:0)</Command>25
</Sql>26
</Html>27
</Template>
这个模板主要是用来执行Sql语句的,包含两种执行方式分别是:TYPE=Reader|NonQuery,执行方式对应于数据库中的两种执行方式。
模板样式为[Sql:ID|S1($S2)|O1($O2)],ID表示Sql模板对以的执行模板ID,比如这里的List1、List2等,S1表示传递给Command的参数,多个参数之间用字符“$”隔开,如果参数为空,请留一个“$”,O1表示Code参数。
参数的样式为:(S:0)(Type:Index)Type是参数类型。Index是索引。Type类型请参考TemplateArgs类中字段的第一个字母,可以大概的推断出参数意思。
Sql模板是可以无限制的嵌套的,这里就有LIst1->List2->List3,在Reader类型中会生成一个While循环,读取Code里的字符,生成代码并替换Sql模板
Sql解释器
/// <summary>2
/// 替换Sql参数3
/// </summary>4
/// <param name="html">需要替换的文本</param>5
/// <param name="template">XML模板的XElement</param>6
public static void ReplaceSql(ref StringBuilder html, XElement template)7
{8
re = new Regex(@"\[Sql:(.[^\|]*)\|(.[^\|]*)\|(.[^\]]*)\]", RegexOptions.IgnoreCase);9
mc = re.Matches(html.ToString());10
if (mc.Count > 0)11
{12
foreach (Match c in mc)13
{14
TemplateArgs args = new TemplateArgs15
{16
Sql = c.Groups[2].Value.SplitArgs(),17
Other = c.Groups[3].Value.SplitArgs()18
};19
StringBuilder code = new System.Text.StringBuilder();20
var sql = from s in template.Descendants("Sql") where (s.Attribute("ID").Value == c.Groups[1].Value) select s;21
foreach (var s in sql)22
{23
if (s.Element("Command").Value != "")24
{25
if (s.Attribute("TYPE").Value == "Reader")26
{27
using (SqlHelper mySql = new SqlHelper())28
{29
DbDataReader myReader = mySql.ExecuteDataReader(s.Element("Command").Value.ReplaceSql(args));30
while (myReader.Read())31
{32
List<string> argsList = new List<string>();33
for (int i = 0; i < myReader.FieldCount; i++) { argsList.Add(myReader[i].ToString()); }34
args.Content = argsList.ToArray<string>();35
code.Append(s.Element("Code").Value.ReplaceContent(args, template));36
}37
myReader.Close();38
}39
}40
else if (s.Attribute("TYPE").Value == "NonQuery")41
{42
using (SqlHelper mySql = new SqlHelper())43
{44
mySql.ExecuteNonQuery(s.Element("Command").Value.ReplaceSql(args));45
}46
}47
}48
}49
html.Replace(c.Groups[0].Value, code.ToString());50
}51
}52
}Template模板
<!-- 模板一 -->2
<Html>3
<Text URL="List.html">4
<![CDATA[[Sql:List1|1|$]]]>5
</Text>6
<Sql ID="List1" TYPE="Reader">7
<Command>select [Id], [TemplateId] from [List] where [Type]=(S:0) and [IsNew]=1</Command>8
<Code>9
<![CDATA[10
[Template:(C:1)|(C:0)|(C:0)][Sql:List2|(C:0)|$]11
]]>12
</Code>13
</Sql>14
<Sql ID="List2" TYPE="NonQuery">15
<Command>update [List] set [IsNew]=0 where [Id]=(S:0)</Command>16
</Sql>17
</Html>18
<!-- 模板二 -->19
<Html>20
<Text URL="List-(U:0).html">21
<![CDATA[[Sql:List1|1|List1]]]>22
</Text>23
<Sql ID="List1" TYPE="Reader">24
<Command>select [Id] from [List] where [Index]=(S:0)</Command>25
<Code>26
<![CDATA[27
<li>(C:0)</li>28
]]>29
</Code>30
</Sql>31
</Html>这里有两个模板,其实Template模板就是调用主函数,并赋值参数,生成另一个模板,这里可以看到生成一个模板需要那些参数,模板样式为[Template:ID|C1($C2)|U1($U2)],ID是这个模板的ID号,我把模板存在数据库中,通过此ID号就可以找到模板位置,并进行处理,C1就是下一个模板里的内容替换参数,U1是下一个模板的路径参数。
我们现在通过上面的例子来分析它的功能,首先我们假设有一个新闻目录的表结构如:ID|Name|Index|Type|IsNew,其中Index就是上级目录ID,我们这里就是要想下级模板传送此参数。
首先我们执行模板一中的[Sql:List1|1|$],传递参数1,即select [Id], [TemplateId] from [List] where [Type]=1 and [IsNew]=1,查找Type=1并IsNew=1的目录,IsNew表示此目录是否有更新,没有就没必要生成了,此后它执行[Sql:List2|(C:0)|$]表示它已经更新过了,这后调用模板[Template:(C:1)|(C:0)|(C:0)],(C:1)是下级模板的ID,第一个(C:0)是内容参数,第二个(C:0)是路径参数。
之后我们开始调用第二个模板,先看它的路径List-(U:0).html,它会生成如List-21.html的文件,第二个模板就是普通模板文件,大家现在应该能看得懂了,同样也可以在加Template模板,这样就可以生成多级目录了。
Template解释器
/// <summary>2
/// 替换Template参数3
/// </summary>4
/// <param name="html">需要替换的文本</param>5
public static void ReplaceTemplate(ref StringBuilder html)6
{7
re = new Regex(@"\[Template:(.[^\|]*)\|(.[^\|]*)\|(.[^\]]*)\]", RegexOptions.IgnoreCase);8
mc = re.Matches(html.ToString());9
if (mc.Count > 0)10
{11
foreach (Match c in mc)12
{13
string filePath = string.Empty;14
using (SqlHelper mySql = new SqlHelper())15
{16
filePath = mySql.ExecuteScalar(string.Format("select [Path] from [Simple_Template] where [Id]={0}", c.Groups[1].Value)).ToString();17
}18
if (filePath != string.Empty)19
{20
TemplateArgs args = new TemplateArgs21
{22
Url = c.Groups[2].Value.SplitArgs(),23
Content = c.Groups[3].Value.SplitArgs(),24
Sql = new string[] { },25
Other = new string[] { }26
};27
XmlTemplate(filePath, args);28
}29
html.Replace(c.Groups[0].Value, "");30
}31
}32
}通过上面的这基本模板我们可以生成大部分单页面文件了,但是还是缺少了一个重要的模板,那就是自动分页模板,这里篇幅有限,我在下一篇介绍自动分页模板,这个模板有点长,不过它同样实现了自动分页,并能自动生成所有分页文件,这样我们就不用为分页烦恼了。
以上模板,我们都可以改造成自己需要的样式和形式,作用还是比较方便的,在文章最后,我会给出全部源程序。
大家还可以访问http://simplebbs.cn,这个测试网站的所有前台文件都是用模板生成的。


浙公网安备 33010602011771号