网页模板解释器1

这里所说的模板指的是生成静态页面时的模板,同样以下论述都是以此为前提,并且也是以解决此问题为唯一目的的。

在做CMS的时候,经常要用到模板,比如:

1 <html>
2     <head>
3         <title>{title}</title>
4     </head>
5     <body>
6         <div>{content}</div>
7     </body>
8 </html>

我们使用此模板是需要吧里面的标签替换成我们需要的内容:

1public 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,传递给下级的内容参数,可一通过它获得上级传下来的内容。

 1namespace Simple.Helper
 2{
 3    public class TemplateArgs
 4    {
 5        public string[] Url getset; }
 6        public string[] Content getset; }
 7        public string[] Sql getset; }
 8        public string[] Other getset; }
 9    }

10}

接下来我们开始开发模板,第一个模板我称为Config模板

 1<?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解释器

 1public 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.cn
28                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模板

 1    <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解释器

 1/// <summary>
 2/// 替换Sql参数
 3/// </summary>
 4/// <param name="html">需要替换的文本</param>
 5/// <param name="template">XML模板的XElement</param>

 6public 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 TemplateArgs
15            {
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模板

 1<!-- 模板一 -->
 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解释器

 1/// <summary>
 2/// 替换Template参数
 3/// </summary>
 4/// <param name="html">需要替换的文本</param>

 5public 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 TemplateArgs
21                {
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,这个测试网站的所有前台文件都是用模板生成的。

posted on 2009-05-08 14:56  知秋  阅读(406)  评论(1)    收藏  举报

导航