Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

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?

share|improve this question
    
Sounds more suited to stack overflow, to me. –  Muad'Dib Jan 11 '11 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. –  Mark Jan 11 '11 at 20:47

1 Answer 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.

share|improve this answer
    
Hrm...this raises a number of questions, but using a class and splitting up the functionality is definitely possible. –  Mark Jan 11 '11 at 20:53

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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