An Objective-C templating engine designed to be easy to use (single class, simple interface) with features enough to handle the cases commonly faced by developers in need of a such a library. The engine sits on top of the advanced key-value syntax and parsing done through Apple's NSPredicate and NSExpression classes for conditions and value expressions in templates.
In most cases, you'll probably follow these steps to render a template:
- Create a DMTemplateEngine instance.
- Read your template from disk into an NSString.
- Set the template string on your DMTemplateEngine instance.
- Build an object (typically an NSDictionary) containing the data you'll be referencing in your template.
- Render your template against this object.
Like so:
DMTemplateEngine* engine = [DMTemplateEngine engine];
engine.template = @"Hello, my name is {% firstName %}.";
NSMutableDictionary* templateData = [NSMutableDictionary dictionary];
[templateData setObject:@"Dustin" forKey:@"firstName"];
NSString* renderedTemplate = [engine renderAgainst:templateData];
Rendered: Hello, my name is Dustin.
For conditional template output, DMTemplateEngine supports if, elseif, and else statements. Because DMTemplateEngine makes use of Foundation's NSPredicate class, conditional statements can make use of the same advanced key-value expressions.
{% if(person.contacts.@count == 0) %}
Please add some contacts.
{% elseif(person.contacts.@count < 3) %}
Add some more contacts.
{% else %}
You have enough contacts.
{% endif %}
Along with conditions, DMTemplateEngine also supports foreach loops. The syntax is fairly similar to that of the fast enumeration syntax found in Objective-C, except you don't specify a type, and the specified value to enumerate over can be a simple key-value, or a key-value expression, or an inline ASCII property list.
{% foreach(contact in person.contacts) %}
Contact name: {% contact.firstName %} {% contact.lastName %}
{% endforeach %}
{% foreach(contactName in {"Dustin", "Mary Ann", "Ollie"}) %}
Contact: {% contactName %}
{% endforeach %}
{% foreach(contactFirstName in person.contacts.firstName) %}
Contact: {% contactFirstName %}
{% endforeach %}
Within the scope of a foreach loop, the current iteration index is made available automatically.
{% foreach(contact in person.contacts) %}
Contact {% contactIndex+1 %}: {% contact.firstName %}
{% endforeach %}
It's common to want to process a template value before rendering it (e.g. trim whitespace, escape xml, etc.). DMTemplateEngine makes this possible through modifiers. Modifiers are specified by a sequence of characters surrounded by square brackets at the beginning of a template value tag.
Assuming a modifier is defined for the character 'w'.
{%[w] person.firstName %}
Escape XML modifier.
{%[e] person.firstName %}
Escape URL modifier.
http://www.website.com/profile?id={%[u] person.id %}
Human readable byte size modifier.
The file size is: {%[b] file.fileSize %}
Along with the built-in modifiers, you can define your own. Let's say you want to define a modifier that trims whitespace from a template value before rendering it. You do so by defining a block that takes the original template value string and outputs a modified version of it.
DMTemplateEngine* engine = [DMTemplateEngine engine];
[engine addModifier:'w' block:^(NSString* content) {
return [content stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}];
engine.template = @"First name is {%[w] firstName %}.";
NSString* renderedTemplate = [engine renderAgainst:templateData];
Rendered: First name is Dustin.
Multiple modifiers can also be applied to a template value. Multiple modifiers are applied in the order they are defined. So, if you wanted to trim the whitespace from and then escape a template value.
{%[we] person.firstName %}
To aid with debugging templates, you can log template expressions using the log method. Apple's NSLog method is used to output your logged template values, so expect them to appear in the same place.
{% log(person.firstName) %}
DMTemplateEngine makes use of Apple's NSPredicate and NSExpression classes to parse and evaluate expressions in templates run through it. As a result, you can make use of all the advanced expression features supported by both classes.
It's possible to quickly create an array of property values of another array's elements. So, if you have an array of objects each with a name property and you'd like to iterate over the lowercase version of each object's name, you'd simply write:
{% foreach(filename in files.name.lowercaseString) %}
Lowercase file name: {% filename %}
{% endforeach %}
Apple's NSExpression library supports invoking methods (optionally with arguments) on values through the function function. This allows you to run really any code against template values. The syntax is more archaic than that of DMTemplateEngine's modifiers, but can be very useful in one-off situations.
{% function(person.firstName, "substringToIndex:", 5) %}
{% function(person.firstName, "substringToIndex:", 5) %}
{% function(function(person.firstName, "substringToIndex:", 5), "uppercaseString") %}
{% function("".class, "pathWithComponents:", {"~", "dustin", "photo.jpg"}) %}
Copyright (c) 2014 Dustin Mierau
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.