3

Preface

I'm writing a template language (don't bother trying to talk me out of it), and in it, there are two kinds of user-extensible nodes. TemplateTags and TemplateDirectives.

A TemplateTag closely relates to an HTML tag -- it might look something like

div(class="green") { "content" }

And it'll be rendered as

<div class="green">content</div>

i.e., it takes a bunch of attributes, plus some content, and spits out some HTML.

TemplateDirectives are a little more complicated. They can be things like for loops, ifs, includes, and other such things. They look a lot like a TemplateTag, but they need to be processed differently. For example,

@for($i in $items) {
   div(class="green") { $i }
}

Would loop over $items and output the content with the variable $i substituted in each time.

So.... I'm trying to decide on a way to define these directives now.

Template Tags

The TemplateTags are pretty easy to write. They look something like this:

[TemplateTag]
static string div(string content = null, object attrs = null)
{
    return HtmlTag("div", content, attrs);
}

Where content gets the stuff between the curly braces (pre-rendered if there are variables in it and such), and attrs is either a Dictionary<string,object> of attributes, or an anonymous type used like a dictionary. It just returns the HTML which gets plunked into its place. Simple! You can write tags in basically 1 line.

Template Directives

The way I've defined them now looks like this:

[TemplateDirective]
static string @for(string @params, string content)
{
    var tokens = Regex.Split(@params, @"\sin\s").Select(s => s.Trim()).ToArray();
    string itemName = tokens[0].Substring(1);
    string enumName = tokens[1].Substring(1);
    var enumerable = data[enumName] as IEnumerable;
    var sb = new StringBuilder();
    var template = new Template(content);
    foreach (var item in enumerable)
    {
        var templateVars = new Dictionary<string, object>(data) { { itemName, item } };
        sb.Append(template.Render(templateVars));
    }
    return sb.ToString();
}

(Working example). Basically, the stuff between the ( and ) is not split into arguments automatically (like the template tags do), and the content isn't pre-rendered either.

The reason it isn't pre-rendered is because you might want to add or remove some template variables or something first. In this case, we add the $i variable to the template variables,

var templateVars = new Dictionary<string, object>(data) { { itemName, item } };

And then render the content manually,

sb.Append(template.Render(templateVars));

Question

I'm wondering if this is the best approach to defining custom Template Directives. I want to make it as easy as possible. What if the user doesn't know how to render templates, or doesn't know that he's supposed to?

Maybe I should pass in a Template instance pre-filled with the content instead? Or maybe only let him tamper w/ the template variables, and then automatically render the content at the end?

OTOH, for things like "if" if the condition fails, then the template wouldn't need to be rendered at all. So there's a lot of flexibility I need to allow in here.

Thoughts?

2
  • Sounds more suited to stack overflow, to me.
    – Muad'Dib
    Jan 11, 2011 at 20:32
  • @Maud'Dib: Had it there but wasn't receiving any responses :p I think its more of a discussiony type question, which Programmers is more apt for.
    – mpen
    Jan 11, 2011 at 20:47

1 Answer 1

1

I would consider creating a generic interface.

public interface ITemplateDirective
{
    void Execute(Dictionary<string, object> data, TextWriter writer);
}

Then you can define some basic directives:

public sealed class IfTemplateDirective : ITemplateDirective
{
    public IfTemplateDirective(
        Func<string, bool> predicate, 
        string value, 
        BlockTemplateDirective block)
    {
        // ...
    }

    public void Execute(Dictionary<string, object> data, TextWriter writer)
    {
        if (_predicate(_value)) 
        {
            _block.Execute(data, writer);
        }
    }
}

Clearly this is a bit contrived but it is how I would at least start to approach it.

1
  • Hrm...this raises a number of questions, but using a class and splitting up the functionality is definitely possible.
    – mpen
    Jan 11, 2011 at 20:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.