自定義標簽大致可以理解為一個HTML代碼塊,也可以指向一個HTML文件。
在模板頁中,很多地方需要相同的內容,例如頁面的頭部、尾部等,這個時候,可以利用自定義標簽來定義HTML代碼,然后在模板頁中引用就可以了。
自定義標簽的格式如下:
{%@ pageTop%} 頁面頂部的頂部的自定義標簽;
自定義標簽中的HTML代碼,我用了XML來記錄,當然也有可視化的編輯界面來操作,在這里就不再綴述,只是說明一下原理即可,XML如下:
<Tag Uid="5895643"> <Name>PageTop</Name> <Type>page</Type> <Intro><![CDATA[所有頁面的頂部]]></Intro> <Page>PageTop.html</Page> <Context><![CDATA[<div id="pagetop">logo 信息化動力核心 </div> <div></div>]]></Context> </Tag>
上述代碼,標明了自定義標簽的唯一標識、名稱、類型(指定某個頁面)、介紹、html文件名、html代碼塊;
說到這里就簡單了,我們只需要把自定義標簽從模板頁中檢索出來,替換成HTML代碼就行了。代碼如下:
/// <summary> /// 將模板內容中的所有自定義標簽,替換為實際的值 /// </summary> /// <param name="html">要處理的模板對象</param> /// <returns></returns> public string Transact(TemplateBuider.PageTransact.Html html) { string patt = @"{%@\s*(\S[^\s%]+)\s*%}"; Regex rex = new Regex(patt, RegexOptions.Singleline); MatchCollection mc = rex.Matches(html.HtmlContext); for (int i = 0; i < mc.Count; i++) { Match ma = mc[i]; string key = ma.Groups[1].Value; //獲取當前標簽對象 Song.TemplateManager.Tags.CustomTag custom = _tags.Find(delegate(TemplateManager.Tags.CustomTag p) { return p.Name.ToLower() == key.ToLower(); }); if (custom == null) continue; //轉換自定義標簽中的路徑,使之與當前模板頁為相對路徑 string context =_replacePath(html, custom); //將自定義標簽合並到的模板 html.HtmlContext = Microsoft.VisualBasic.Strings.Replace(html.HtmlContext, ma.Value, context, 1, -1, Microsoft.VisualBasic.CompareMethod.Text); } return html.HtmlContext; }
上述代碼有一些是我系統中相關方法,大家不必關注,關鍵是正則表達式。
可這個時候,有個問題。各個模板頁並不在一個文件夾下,路徑各不相同,自定義標簽中的HTML如果有超鏈接,在模板頁引用后,如果只是簡單的替換,這些超鏈接就有可能出錯,找不到指定的內容。所以,我們必須將自定義標簽中的鏈接對象轉換成當前模板的路徑名。
思路是這樣的,首先找出自定義標簽的路徑,如果自定義標簽是指向HTML文件的,則按Html路徑;如果是純HTML代碼,則以當前模板庫的路徑為路徑;自定義標簽中的所有鏈接,包括超鏈接、CSS引用、Js引用、iframe等,轉換為相對於自定義標簽路徑的路徑;然后找當前模板頁的路徑;將所有的鏈接轉換為當前模板頁的路徑。
這中間牽涉到一個算法,從A文件到B文件的相對路徑。舉例說:A文件在/3/4/5/q/w/a.html,B文件在/3/4/6/s/b.html,如果A文件中寫一個超鏈接引用B文件,這個超鏈接怎么寫?
我這里寫了一個方法。計算兩個文件的相對路徑,代碼如下:
/// <summary> /// 計算兩個文件的相對路徑 /// </summary> /// <param name="baseFile">用於參照的文件,就從當前文件開始查找另一個文件</param> /// <param name="targetFile">目標文件,就是求它的相對路徑</param> /// <returns>返回targetFile相對於baseFile的相對路徑</returns> private string _getRelativePath(string baseFile, string targetFile) { baseFile = baseFile.Replace("\\", "/"); baseFile = baseFile.ToLower(); targetFile = targetFile.ToLower(); // while (baseFile.IndexOf("/") > -1 && targetFile.IndexOf("/") > -1) { string b = baseFile.Substring(0, baseFile.IndexOf("/")); string t = targetFile.Substring(0, targetFile.IndexOf("/")); if (b != t) break; baseFile = baseFile.Substring(baseFile.IndexOf("/") + 1); targetFile = targetFile.Substring(targetFile.IndexOf("/") + 1); } string path = ""; while (baseFile.IndexOf("/") > -1) { baseFile = baseFile.Substring(baseFile.IndexOf("/") + 1); path += "../"; } return path + targetFile; }
有了上面的方法,就好處理了,真正的超鏈接轉換,就是正則匹配處理了。方法如下:
/// <summary> /// 將模板頁中的路徑處理成相對於當前模板頁的路徑 /// </summary> /// <param name="html"></param> /// <param name="tag"></param> /// <returns></returns> private string _replacePath(TemplateBuider.PageTransact.Html html, TemplateManager.Tags.CustomTag tag) { string context = tag.Context; //處理自定義標簽中的超鏈接,使其相對於當前文件路徑 string cutomPath = _cutomPath(tag); //將超鏈接處理為相對於模板頁的路徑 string linkExpr = @"(?<=\s+)(?<key>href|src|action|background[^=""']*)=([""'])?(?<value>[^'"">]*)\1?"; Regex regex = new Regex(linkExpr, RegexOptions.Multiline | RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase); MatchCollection mc = regex.Matches(tag.Context); foreach (Match m in mc) { string link = m.Groups["value"].Value.Trim(); if (link == "") continue; //外網鏈接不處理,如Http://開頭的超鏈接 if (new Regex(@"[a-zA-z]+://[^\s]*", RegexOptions.Singleline).IsMatch(link)) continue; //根路徑不處理,如/manage/index.aspx,第一個字符是/ if (new Regex(@"^\/\w+.").IsMatch(link)) continue; //如果是參數標簽,則不處理 if (new Regex(@"^{.+").IsMatch(link)) continue; //將超鏈接轉換為相對於靜態化目錄的路徑 link = _getCutomLinkPath(cutomPath, link); //將超鏈接轉換為基於當前模板頁的相對路徑 link = _getRelativePath(html.TagetFile, link); link = m.Groups[2].Value + "=\"" + link + "\""; context = context.Replace(m.Value, link); } return context; }
在上述代碼中,根路徑不處理、站外鏈接不處理、以{開頭的鏈接不處理。
總結
我的這個自定義標簽功能並不強,雖然自定義標簽中也可以有其它組件,但其本質未變,只是HTML代碼塊的替換。本來想寫帶參數的自定義標簽呢,這樣就更類似於asp.net的用戶控件,精力有限,暫時先這樣吧。