• CMS系统模版引擎设计(3):Label基类的设计


    上节讲了页面的整个生产流程,大家都期待第三篇,也就是生产的核心内容——Label的替换。说实话,我很有压力啊:)一个人一个实现思路,所以...可能你不能接受。
    我的标签分为2种,一种是配置变量标签(就是站点和系统的Config),用 %变量名%表示,在初始化Labels之前是要执行替换的。另外一种就是数据调用的Label咯。看下风格:
    //简单的循环列表
    {Article:List Top="10" CategoryId="5"}
    <a href ="/details/[field:FileName/]" target="_blank">[field:Title/]</a>
    {/Article:List}
    //引用用户控件模版,CategoryId是需要传递的参数
    {System:Include TemplateId="5" CategoryId="7"/}
    //详情页模版
    {Article:Model ArticleId="Url(articleid)"}
    <h1>[field:Title/]</h1>
    {/Article:Model}
    {Artcile:Model name="PostTime" dateformat="yyyy年MM月dd日"/}
    大家可以看出点端倪了吧,格式都是统一的。我来说下:
    Article:List:是Article模块下的List标签
    Top :调用条数
    CategoryId:分类ID
    当然还支持其他的属性比如Skip,Class,Cache等等,这些属性关键是看List标签的支持度。
    下面的<a>...</a>当然是循环部分,而[field:FieldName/]则是具体的字段,接着是关闭标签。
    但例如System模块的Include标签却没有内容部分。
    而详情页的字段展示和列表不同,他的字段可以任意位置摆放。所以可以下面的那个Model虽没有ID也可以输出:) 这些七七八八的细节比较多。
    我们如何解释这些标签代码呢?
    其实这些都是文本,依靠文本执行代码就得靠反射了。所以得反射!是的,Article是程序集(或者是命名空间),而List其实就是个类。List又包含了好多参数,还包含了循环体,所以参数其实也是类(Parameter),而循环体里有[field]其实也是类(Field)。呵呵,一切皆是类。
    那么,各种的标签都是类,我们需要抽象出他们的公共部分作为基类,或许还要设计些接口?
    根据我们提到的所有信息里,目前能想到的就是Id,Parameters,Fields,Cache,Html和GetHtml()方法。
    从上面的标签里我们有看到include会给子模版里的标签传参,所以Parameters应该是可变的,Fields也最好可变的,所以数组都不合适。另外循环的时候要替换Field,所以Fields最好是键值对集合(k/v)。Parameters也存成K/V合适吗?暂时也这么存吧。
    每个标签在网页里出现的目的是什么?转换成Html,哪怕他是空(或许是在某些条件下输出的是空),那么我们设计成为virtual函数还是抽象成接口呢? 首先说虚函数的意义,就是子类可以去覆盖,但也可以直接使用,而接口则是必须实现。如果设计成接口,就算不输出的标签也要多去实现,那不是很烦。所以暂时我们设计成虚函数,或许我们的决定是错的。 另外GetHtml感觉名称不够准确,因为每个Label都有原始的Html代码,所以改名为 GetRenderHtml()。

        /// <summary>
        /// Label基类
        /// </summary>
        public class Label
        {
            /// <summary>
            /// ID,一般用于缓存的Key
            /// </summary>
            public string ID { get; set; }
            /// <summary>
            /// 原始的HTML代码
            /// </summary>
            public string Html { get; set; }
            /// <summary>
            /// 标签的参数
            /// </summary>
            public IDictionary<string,Parameter> Parameters { get; set; }
            /// <summary>
            /// 标签的字段
            /// </summary>
            public IDictionary<string, Field> Fields { get; set; }
            /// <summary>
            /// 缓存
            /// </summary>
            public Cache Cache { get; set; }
            /// <summary>
            /// 获取需要呈现的HTML
            /// </summary>
            /// <returns></returns>
            public virtual string GetRenderHtml()
            {
                return string.Empty;
            }
        }
    

    大家是否觉得Parameters和Fields很难看呢?因为关于他们的操作(获取某个parameter,删除,增加,枚举等)还很多,所以应该单独封装,而且万一哪天发现IDictionary不合适,所以封装是合适的。所以改成了,
    public ParameterCollection Parameters { get; set; }
    public FieldCollection Fields { get; set; }
    那么怎么在页面里发现这些Label,并实例化他们呢? 当然是强大的正则了。
    {((?<a>\w+):(?<c>\w+))(?<p>[^}]*)((/})|(}(?<t>(?>(?<o>{\1[^}]*})|(?<-o>{/\1})|(?:(?!{/?\1)[\s\S]))*)(?(o)(?!)){/\1}))
    懂正则的朋友我想说:你懂的:)。字符串被分为了4个组分别是assembly,class,parameters,template。
    而Label的ParameterCollection和FiledCollection则需要从<parameters>组和<template>组再次使用正则获取。
    Parameter的正则:(?<name>\w+)=(?<value>("([^"]+)")|('[^']+')|([^\s\}]+))
    Field的正则:\[field:(?<name>[\w\.]+)(?<parameters>[^]]+)?/\]
    我说下嵌套的实现思路:
    1、递归Template找到所有的Label,被嵌套的必须有ID号
    2、当替换外层Label每行数据时,需要把当前行的数据DataItem传递给里层的Label,里层的Label实例可以通过FindLabel(id)来找到。是不是觉得有点像Repeater啊?哈哈。
    3、外层Label的Template是需要Replace掉内层Label的Html的。不然Field就乱了。
    说了这么多不如看代码明白,那就创建个LabelFactory类,负责Label的生产。

       public class LabelFactory
        {
            /// <summary>
            /// 匹配Label的正则
            /// </summary>
            private static readonly Regex LabelRegex = new Regex(@"{((?<a>\w+):(?<c>\w+))(?<p>[^}]*)((/})|(}(?<t>(?>(?<o>{\1[^}]*})|(?<-o>{/\1})|(?:(?!{/?\1)[\s\S]))*)(?(o)(?!)){/\1}))");
    
            /// <summary>
            /// 根据模版获取其包含的所有Label
            /// </summary>
            /// <param name="template">模版</param>
            /// <param name="preInit">Label初始化前需要的工作</param>
            /// <returns></returns>
            public static IList<Label> Find(string template, Action<Label> preInit)
            {
                var ms = LabelRegex.Matches(template);
                if (ms.Count == 0) return null;
    
                var list = new List<Label>();
                foreach (Match m in ms)
                {
                    var label = Create(m.Groups[0].Value, m.Groups["a"].Value, m.Groups["c"].Value, m.Groups["p"].Value, m.Groups["t"].Value);
                    //订阅事件
                    if (preInit != null)
                    {
                        label.PreInit += preInit;
                    }
                    //查找Label的子Label,如果存在则会替换Label的TemplateString
                    var labels = Find(label.TemplateString);
                    if (labels != null)
                    {
                        label.TemplateString = label.TemplateString.Replace(labels[0].TemplateString, string.Empty);
                    }
    
                    //label.Init();
                    list.Add(label);
    
                    if (labels != null)
                        list.AddRange(labels);
                }
                return list;
            }
    
            /// <summary>
            /// 重载上面的Find,一般情况下使用该方法,除非需要特殊处理某些标签
            /// </summary>
            /// <param name="template"></param>
            /// <returns></returns>
            public static IList<Label> Find(string template)
            {
                return Find(template, null);
            }
    
            /// <summary>
            /// 反射创建一个Label
            /// </summary>
            /// <param name="template">标签的原始HTML,用于替换使用</param>
            /// <param name="a">程序集名称</param>
            /// <param name="c">标签类名称</param>
            /// <param name="p">标签参数</param>
            /// <param name="t">标签的模版</param>
            /// <returns></returns>
            private static Label Create(string template, string a, string c, string p, string t)
            {
                var assembly = Assembly.Load(a);
                var label = assembly.CreateInstance(c, true) as Label;
                label.Html = template;
                label.TemplateString = t;
                label.ParameterString = p;
                return label;
            }
        }
    

    这代码只是比较简单的,异常肯定是有的,我只是写思路:)
    细心的朋友会发现Label又增加了些新内容,是的,这是在设计过程中的填充和修改。没有人一开始就考虑的十分周全,这是一个正常的设计过程。看看Label的改动,增加了几个属性,一个preinit事件,和一个初始化方法init给定一段html代码,里面会包含若干个label,所以find会返回一个list,另外我们还需要一个Create方法类反射每一个label。
    在实例化一个label后,还需要继续看这个label是否嵌套了label,所以要对该label的template继续find,如此递归。。如果能找到label,则把父亲的template里最先发的label的template替换掉。不然初始化Fields的时候会出问题。
    为什么设计了一个事件?
    因为Include标签是需要传参给里面的label的,所以在label初始化之前可能会改动label的parameterString和templateString:) 希望您能理解。

            /// <summary>
            /// 原始的HTML代码
            /// </summary>
            public string Html { get; set; }
            /// <summary>
            /// Label的Parameter字符串
            /// </summary>
            public string ParameterString { get; set; }
            /// <summary>
            /// Label的模版
            /// </summary>
            public string TemplateString { get; set; }
            /// <summary>
            /// 初始化之前的事件
            /// </summary>
            public event Action<Label> PreInit;
            /// <summary>
            /// 初始化Label
            /// </summary>
            public virtual void Init()
            {
                if (PreInit != null)
                {
                    PreInit(this);
                }
                //初始化所有参数
                Parameters = new ParameterCollection(ParameterString);
                //初始化所有字段
                Fields = new FieldCollection(TemplateString);
            }
    


    好了,写了太久了,大家和我都消化消化,休息下:)后面继续讲Parameters和Fields的设计。


  • 相关阅读:
    第二周学习总结
    调查问卷
    第五周学习总结
    基于DevExpress的项目窗体统一换肤
    一个Linq to Sql 的关联小问题,搞死人
    VC中类型转换(转载)
    保证一个程序只运行一次
    给所有的Control添加发送键盘事件Tab事件,实现回车键自动跳转到下一个控件[基于Shark Xu]
    遍历进程
    CSS Friendly ASP.NET 2.0 Control Adapters (Beta 2.0)在处理URL时的一个Bug
  • 原文地址:https://www.cnblogs.com/mad/p/1874081.html
Copyright © 2020-2023  润新知