我的標(biāo)簽分為2種,一種是配置變量標(biāo)簽(就是站點和系統(tǒng)的Config),用 %變量名%表示,在初始化Labels之前是要執(zhí)行替換的。另外一種就是數(shù)據(jù)調(diào)用的Label咯。看下風(fēng)格:
//簡單的循環(huán)列表
{Article:List Top="10" CategoryId="5"}
<a href ="/details/[field:FileName/]" target="_blank">[field:Title/]</a>
{/Article:List}
//引用用戶控件模版,CategoryId是需要傳遞的參數(shù)
{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日"/}
大家可以看出點端倪了吧,格式都是統(tǒng)一的。我來說下:
Article:List:是Article模塊下的List標(biāo)簽
Top :調(diào)用條數(shù)
CategoryId:分類ID
當(dāng)然還支持其他的屬性比如Skip,Class,Cache等等,這些屬性關(guān)鍵是看List標(biāo)簽的支持度。
下面的<a>...</a>當(dāng)然是循環(huán)部分,而[field:FieldName/]則是具體的字段,接著是關(guān)閉標(biāo)簽。
但例如System模塊的Include標(biāo)簽卻沒有內(nèi)容部分。
而詳情頁的字段展示和列表不同,他的字段可以任意位置擺放。所以可以下面的那個Model雖沒有ID也可以輸出:) 這些七七八八的細(xì)節(jié)比較多。
我們?nèi)绾谓忉屵@些標(biāo)簽代碼呢?
其實這些都是文本,依靠文本執(zhí)行代碼就得靠反射了。所以得反射!是的,Article是程序集(或者是命名空間),而List其實就是個類。List又包含了好多參數(shù),還包含了循環(huán)體,所以參數(shù)其實也是類(Parameter),而循環(huán)體里有[field]其實也是類(Field)。呵呵,一切皆是類。
那么,各種的標(biāo)簽都是類,我們需要抽象出他們的公共部分作為基類,或許還要設(shè)計些接口?
根據(jù)我們提到的所有信息里,目前能想到的就是Id,Parameters,F(xiàn)ields,Cache,Html和GetHtml()方法。
從上面的標(biāo)簽里我們有看到include會給子模版里的標(biāo)簽傳參,所以Parameters應(yīng)該是可變的,F(xiàn)ields也最好可變的,所以數(shù)組都不合適。另外循環(huán)的時候要替換Field,所以Fields最好是鍵值對集合(k/v)。Parameters也存成K/V合適嗎?暫時也這么存吧。
每個標(biāo)簽在網(wǎng)頁里出現(xiàn)的目的是什么?轉(zhuǎn)換成Html,哪怕他是空(或許是在某些條件下輸出的是空),那么我們設(shè)計成為virtual函數(shù)還是抽象成接口呢? 首先說虛函數(shù)的意義,就是子類可以去覆蓋,但也可以直接使用,而接口則是必須實現(xiàn)。如果設(shè)計成接口,就算不輸出的標(biāo)簽也要多去實現(xiàn),那不是很煩。所以暫時我們設(shè)計成虛函數(shù),或許我們的決定是錯的。 另外GetHtml感覺名稱不夠準(zhǔn)確,因為每個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>
/// 標(biāo)簽的參數(shù)
/// </summary>
public IDictionary<string,Parameter> Parameters { get; set; }
/// <summary>
/// 標(biāo)簽的字段
/// </summary>
public IDictionary<string, Field> Fields { get; set; }
/// <summary>
/// 緩存
/// </summary>
public Cache Cache { get; set; }
/// <summary>
/// 獲取需要呈現(xiàn)的HTML
/// </summary>
/// <returns></returns>
public virtual string GetRenderHtml()
{
return string.Empty;
}
}
大家是否覺得Parameters和Fields很難看呢?因為關(guān)于他們的操作(獲取某個parameter,刪除,增加,枚舉等)還很多,所以應(yīng)該單獨封裝,而且萬一哪天發(fā)現(xiàn)IDictionary不合適,所以封裝是合適的。所以改成了,
public ParameterCollection Parameters { get; set; }
public FieldCollection Fields { get; set; }
那么怎么在頁面里發(fā)現(xiàn)這些Label,并實例化他們呢? 當(dāng)然是強(qiáng)大的正則了。
{((?<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>[^]]+)?/\]
我說下嵌套的實現(xiàn)思路:
1、遞歸Template找到所有的Label,被嵌套的必須有ID號
2、當(dāng)替換外層Label每行數(shù)據(jù)時,需要把當(dāng)前行的數(shù)據(jù)DataItem傳遞給里層的Label,里層的Label實例可以通過FindLabel(id)來找到。是不是覺得有點像Repeater。抗。
3、外層Label的Template是需要Replace掉內(nèi)層Label的Html的。不然Field就亂了。
說了這么多不如看代碼明白,那就創(chuàng)建個LabelFactory類,負(fù)責(zé)Label的生產(chǎn)。
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>
/// 根據(jù)模版獲取其包含的所有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,一般情況下使用該方法,除非需要特殊處理某些標(biāo)簽
/// </summary>
/// <param name="template"></param>
/// <returns></returns>
public static IList<Label> Find(string template)
{
return Find(template, null);
}
/// <summary>
/// 反射創(chuàng)建一個Label
/// </summary>
/// <param name="template">標(biāo)簽的原始HTML,用于替換使用</param>
/// <param name="a">程序集名稱</param>
/// <param name="c">標(biāo)簽類名稱</param>
/// <param name="p">標(biāo)簽參數(shù)</param>
/// <param name="t">標(biāo)簽的模版</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;
}
}
這代碼只是比較簡單的,異?隙ㄊ怯械,我只是寫思路:)
細(xì)心的朋友會發(fā)現(xiàn)Label又增加了些新內(nèi)容,是的,這是在設(shè)計過程中的填充和修改。沒有人一開始就考慮的十分周全,這是一個正常的設(shè)計過程?纯碙abel的改動,增加了幾個屬性,一個preinit事件,和一個初始化方法init給定一段html代碼,里面會包含若干個label,所以find會返回一個list,另外我們還需要一個Create方法類反射每一個label。
在實例化一個label后,還需要繼續(xù)看這個label是否嵌套了label,所以要對該label的template繼續(xù)find,如此遞歸。。如果能找到label,則把父親的template里最先發(fā)的label的template替換掉。不然初始化Fields的時候會出問題。
為什么設(shè)計了一個事件?
因為Include標(biāo)簽是需要傳參給里面的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);
}
//初始化所有參數(shù)
Parameters = new ParameterCollection(ParameterString);
//初始化所有字段
Fields = new FieldCollection(TemplateString);
}
好了,寫了太久了,大家和我都消化消化,休息下:)后面繼續(xù)講Parameters和Fields的設(shè)計。