如何改进这个简单的模板渲染类?

时间:2011-01-08 05:11:33

标签: c# parsing templates

我刚写了一个简单的类。它只需要一串输入,如

  

您好 @name ,今天你看 @adjective

并用字典中的值替换@variables。例如,传入new Dictionary<string,object>{{"name","Ralph"},{"adjective","stunning"}}会给出:

  拉尔夫你好,今天你看起来很棒!

这是班级:

class Template
{
    List<object> nodes = new List<object>();

    public Template(string content)
    {
        for (int i = 0; i < content.Length; ++i)
        {
            char ch = content[i];

            if (ch == '@')
            {
                var match = Regex.Match(content.Substring(i + 1), @"\w+");
                if (match.Success)
                {
                    nodes.Add(new Variable(match.Value));
                    i += match.Value.Length;
                }
                else
                {
                    throw new Exception(string.Format("Expected variable name after @ symbol at character {0}", i));
                }
            }
            else
            {
                nodes.Add(ch.ToString());
            }
        }
    }

    public string Render(Dictionary<string,object> dict)
    {
        var sb = new StringBuilder();
        foreach (var item in nodes)
        {
            if (item is Variable)
            {
                sb.Append(dict[((Variable)item).Name]);
            }
            else
            {
                sb.Append(item);
            }
        }
        return sb.ToString();
    }

    class Variable
    {
        public readonly string Name;
        public Variable(string name)
        {
            Name = name;
        }
    }
}

这是解决这个问题的好方法吗?我想在构造函数中尽可能多地进行处理,以便我可以有效地重复渲染模板而无需重新分析它。

现在我循环遍历整个节点列表,寻找变量节点,以便我可以替换它们。也许有一种方法可以“跳过”这些节点?那会有帮助吗?

另外,我正在逐个字符地解析它,但后来我使用正则表达式(我想从下一个字符开始,所以我使用.Substring来获取字符串的其余部分)来获得“块”的文字。我不确定如何使用正则表达式获取整个变量名称?

这门课程要复杂得多,所以我想在进一步学习之前确保我有正确的方法。

我关注的是:

  1. 从当前位置提取一大块文本并推进计数器的好方法是什么?例如,一旦我确定下一位文本实际上应该是变量名,就提取变量名。
  2. 我应该如何存储节点(在这种情况下,只是文本和变量节点),以便我可以快速一遍又一遍地渲染模板?

4 个答案:

答案 0 :(得分:2)

怎么样:

s = s.Replace("@name", name);
s = s.Replace("@Adjective", adjective);

另外,我会避免在解析文本时抛出异常。由于用户字符串几乎可以包含任何内容,因此最好尽可能智能地尝试处理意外数据。

答案 1 :(得分:1)

您几乎肯定希望为节点使用某种关联容器,我是否可以推荐使用Dictionary<string, Node>而不是List?此外,我认为目前无法设置这些节点。而且你也可以继续将节点作为一个类,同时你就可以了。

至于使用char解析char然后使用正则表达式,是的,这可能并不理想,但编写tokenizer / lexer也不是最简单的任务。需要时修复它!

答案 2 :(得分:1)

如果您要多次使用该模板,我建议分两步解析模板,首先将文本分割为文字文本和字段对象,然后将它们存储在列表中,然后每次需要渲染它只需循环遍历列表并直接输出任何文字文本,并通过字典调用每个字段。

我们使用我们自己的temlpate引擎做了类似的事情,从正则表达式替换到预解析列表,并且性能提高了大约20倍:D。

您当前的代码非常适合拆分原始字符串。

示例:

List<Node> nodes = var list = new List<node> {new node {Type=ntypes.literal,Value="Hello "}, new node{Type=ntypes.field,Value="Name"}};

Enum NodeType {
    field,
    literal
}
class Node {
    public enum NodeType;
    public string Value;
}

如果您只使用它,您仍然可以拆分它,但是然后将数组保存为eaxmple JSON或序列化它,以便您可以非常轻松地重新创建列表而无需使用正则表达式。

如果数组包含带有(type,value)的元素,你可以随着时间的推移添加更多的智能,多个不同的字典或者像@@ substring(@field,10)那样的简单函数然后被解析和执行;)

但是对于更复杂的情况,寻求真正的解析器解决方案会更好,因为随着复杂性的增加,所有简单的解决方案都会变得笨拙而牺牲性能。

我们最终转向基于Antlr的解析器,目前正在查看渲染阶段的表达式树。

答案 3 :(得分:0)

不是循环遍历字符串的每个字符,而应该只对整个字符串执行RegEx并返回一组Matches。