以前做站中使用的是StringTemplate, 不过StringTemplate比较庞大,而且一般情况下只用到其中一两条.另外当模板数目很多时(比方2千个)那么基于StringTemplate缓存处理的方式需要大量内存,而不使用缓存,每次使用完后就释放又严重影响性能,当然可以考虑设计一个缓存队列,按使用频率来找到一个平衡点.
用StringTemplate来做文档生成(如代码,html页面)还是挺不错的,下面介绍的模板处理中的循环处理其标签是参考StringTemplate来做的,以期能最少改动原来的模板.
模板页代码:
============================
<html>
<head>
<title>$PageTitle$</title>
</head>
<body>
<span>推荐企业</span>
$CompanyList:{
<h3>$it.CompanName$</h3>
<ul>$ProductList:{ <a href="$it.Url$>it.ProductName</a> }$</ul>
}$
<h3>推荐资讯</h3>
<ul>$CommendNews:{$it.Title$} </ul>
</boyd>
</html>
===========================
上面模板中包括简单的标签替换如,$PageTitle$,包括单层循环如,推荐资讯这里,另外包含本文讨论的重点嵌套循环处理,在推荐企业这里, 这个循环想要显示这样的数据: 一组企业列表,而每个企业包含一些产品(企业自己设置为推荐的产品),
可以看下面页地址中的"会员推荐"版块(算是广告吗?),地址:http://www.gyzs.net/memberspace/index.aspx.当然所有情况下(出于应用目的而不是学习)模板中只需要用到简单替换跟单层循环替换,一些动态的东西可以使用iframe或者js(ajax)来处理,即使用到多层嵌套循环的情况也可以通过各种方法转化成简单的标签,或单层循环---如果模板处理引擎太强了,那为什么不直接使用编程语言呢如-asp,perl,php这些?
处理思路
单层循环
如果只有单层循环,那么我们可以使用正规表达式来获取$Name:{xxxx}$中大括号间的模板内容,然后循环数据集合内的每条记录,记录的每个字段,替换获模板内的对应标签,最后将这一系列替换后的文本拼接,替换总模板中的$Name:{xxxx}$就好了.
多层循环
对应循环嵌套的情况,不知道是否有高人用规则表达公式简单搞定,我这里的思路是使用栈,遇到":{"标签压栈,而遇到"}$"则弹出一个, 另外为体现出循环的层次关系需要将文档中的多层循环构建成树结构, 完成文档解树的建立后则可以结合数据进行替换处理了,如上面的企业列表数据其需要如下的结构:
//============填充数据结构表示
class Product{ stirng ProductName; }
class Company{ List<Product> Products; string companyName}
List<Company> list=new List<Company>();
//==========================
当每次弹出一个节点时(root除外),当前栈顶的元素即是父节点,完成树的构建后为方便替换,这里把树中父节点中(有子节点的任一层次节点)文本中的大段循环摸板替换成$ProductList:{}$这样的空标签,完成这个处理使用了队列进行自顶向下逐层的处理,当然也可以使用递归方式实现.
//===============替换父节点中的循环模板========
private void ReplaceLoopTag(Node node)
{
//无子类的表示是最内的循环,不需要替换
foreach (Node child in node.Childs)
{
node.Text = node.Text.Replace(child.Text, "$" + child.Name + ":{}$");
//注意使用先序遍历
ReplaceLoopTag(child);
}
}
//===============树节点定义=====
public class Node
{
public string Text;
public string Name;
public int StartIndex = 0;
public int EndIndex = 0;
private Node _Parent;
public Node Parent
{
set
{
_Parent = value;
if (_Parent != null)
{
_Parent.Childs.Add(this);
}
}
get { return _Parent; }
}
private List<Node> _Childs;
public List<Node> Childs
{
get
{
if (_Childs == null) _Childs = new List<Node>();
return _Childs;
}
}
}
//解析文档的实现,文章末尾下载代码中的实现方式使用indexOf进行向后查找分割标志,在改成KMP查找后速度有一定提升,每次能减少0.7毫秒(对比测试,数据只有相对意义),但考虑其它因素(如数据库等),单纯在解析时使用KMP算法总体性能提升不大,而下面Parse_1给出的代码相对来说更好理解,另外KMP算法复杂度是0(M+N)而普通查找是0(M*N)在查找标志是":{"或"}$"的情况下,N取2.
//===============
private Node Parse_1()
{
Node root = new Node();
root.EndIndex = content.Length;
root.StartIndex = -1;
root.Text = content;
root.Name = "ParsedDocument";
Stack<Node> stack = new Stack<Node>();
stack.Push(root);
int pointer = 0;
pointer = content.IndexOf(BFlag, StringComparison.InvariantCultureIgnoreCase);
//不存在任何循环标签
if (pointer < 0) { stack.Pop(); return root; }
while (pointer < content.Length - 1)
{
string c = content.Substring(pointer, 2);
if (c == BFlag)
{
int startIndex = 0;
string tagName = GetTagName(pointer, out startIndex);
Node node = new Node();
node.StartIndex = startIndex; //$开始以获取完整段落
node.Name = tagName;
stack.Push(node);
pointer += 2;
}
else if (c == EFlag)
{
//弹出一个节点
Node n = stack.Pop();
n.EndIndex = pointer;
n.Text = content.Substring(n.StartIndex, n.EndIndex + 2 - n.StartIndex);
if (stack.Count > 0)
{
n.Parent = stack.Peek();
}
}
pointer++;
}
stack.Pop(); if (stack.Count > 0) throw new Exception("标签为配对");
//替换大段循环文本为标签
Queue<Node> queue = new Queue<Node>();
queue.Enqueue(root);
while (queue.Count > 0)
{
Node node = queue.Dequeue();
foreach (Node child in node.Childs)
{
node.Text = node.Text.Replace(child.Text, "$" + child.Name + ":{}$");
queue.Enqueue(child);
}
}
return root;
}
//======================
代码下载:https://files.cnblogs.com/wdfrog/TemplateEngine.rar
注意,只支持简单标签替换跟循环标签替换,循环允许嵌套,集合数据需要IList类型,简单标签替换不支持反射,具体前参考代码,做适当调整后可以满足简单应用