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的設計。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章