MGTemplateEngine – Templates with Cocoa

MGTemplateEngine is a native Cocoa system for generating text output based on templates and data. It’s a close cousin of systems like Smarty, FreeMarker, Django‘s template language, and so on.

It’s ideal for Cocoa apps needing to generate text output using variable-substitution (with looping and/or conditional logic), including creating HTML pages (or for apps with WebKit-based UIs), generating invoices or other printable templates, mail merge, data export or any number of other things. It’s also great (in combination with WebKit) for letting your users create themes/styles for your application.

Here’s a sample template, and the corresponding sample output (with appropriate variables fed to the template engine).

The default syntax for markers (functions or language-constructs) is:

{% for 1 to 5 %} foo {% /for %}

and the default syntax for variables/expressions is:

{{ foo.bar | uppercase }}

The pipe-character indicates a filter is being applied; i.e. the value of “foo.bar” will then be fed to the “uppercase” filter before being displayed. You can apply filters to markers as well as variables.

The marker, variable and filter delimiters are completely customizable, so you’re not stuck with the defaults if you prefer different syntax. You can also write your own markers (language constructs/functions), filters (data-formatting functions) and even your own matchers (advanced; see documentation).

MGTemplateEngine offers the following features:

  • Native Cocoa implementation. It doesn’t use the Scripting Bridge or any external runtimes/frameworks, and as such the core engine itself has no requirements other than Mac OS X Leopard.
  • Very customizable. It’s very easy to define new markers (like functions or language-constructs) and new filters (data-formatting capabilities). You can also freely change the syntax of the markers and expressions to suit your own tastes, or to mimic your favorite other templating system.
  • Delegate system to keep you informed. MGTemplateEngine can optionally inform a delegate object of significant events during processing of a template, including beginning/ending blocks, or encountering errors.
  • Global and template-specific variables. You can define a set of variables which exist for the lifetime of the engine, and also specify variables which only apply to a certain template.
  • Access variables using familiar Key-Value Coding (KVC) key-paths, with enhancements. For example, if you had an NSDictionary containing an NSArray for the key “foo”, and that array contained 5 NSDictionaries, each of which had an NSString for the key “bar”, you could access the value of the fifth dictionary’s “bar” object using this syntax: foo.4.bar (remembering that array indices are zero-based!)

MGTemplateEngine requires Mac OS X 10.5 (Leopard) or later, and can be downloaded from my public Subversion repository at http://svn.cocoasourcecode.com/MGTemplateEngine

You can also read the documentation here:

Get in touch with me via email (details here) if you have feature requests or bug reports, and if you use MGTemplateEngine in your app, be sure to let me know so I can link to you. General praise and thanks can go into the comment box below. ;)

(Finally, and in a small voice, remember the donation link on my Cocoa source code page…)

I plan to continue improving MGTemplateEngine in the future, and I hope you’ll find it useful!

Update: Added the % operator to the if marker. Syntax is if x % y, which as you’d expect returns true if x is not evenly divisible by y, and false otherwise. Very handy within for loops for creating alternating rows, as shown in the (also updated) sample template.

Also added the cycle marker, which takes any number of arguments and will output the next one in the list each time the tag is encountered. Great within loops to cycle between a set of values (e.g. again for alternating row colors). Sample template updated to show this.

19 comments

  1. Matt, this looks quite useful… Have you considered a different scheme other than this big function?… Your original function has so many output parameters of which I would expect typically only one or two to be used at a time seems clunky to me.

    - (NSObject *)markerEncountered:(NSString *)marker withArguments:(NSArray *)args inRange:(NSRange)markerRange blockStarted:(BOOL *)blockStarted blockEnded:(BOOL *)blockEnded outputEnabled:(BOOL *)outputEnabled nextRange:(NSRange *)nextRange currentBlockInfo:(NSDictionary *)blockInfo newVariables:(NSDictionary **)newVariables;

    And instead pass in one object representing the engine doing the parsing with the input…

    - (NSObject *)markerEncountered:(NSString *)marker withArguments:(NSArray *)args inRange:(NSRange)markerRange byParser:(MGParser *)parser

    MGParser would supply the API functions which the MGMarker could call to “return” the object(s) found.

    Just curious if this was a conscious design choice.

    Thanks,
    - John Haney

  2. Hi John,

    It just evolved that way; it’s certainly something I’m considering refactoring a bit in future, because it’s quite a large method sig.

  3. Ooohhh… saucy! I’ve been wondering when something like this would be made. I haven’t needed it yet, but I’ve been on the verge and I’m sure there will come a day. I’m glad you count Freemarker as an influence, as it’s been my favorite templating engine to this point.

    Thanks Matt!
    Jeff

  4. Awesome stuff, Matt. I’ve got a project in mind that this will suit perfectly – thanks!

  5. Hi Matt,

    I haven’t had a chance to download it (yet) but this looks perfect — I’ve had to roll quite a few of my own, very imperfect, templating systems in the past but this looks more robust than what i’ve used before.

    Thanks

  6. Very nice, Matt. You seem to provide an endless supply of source code and ideas for apps.

  7. Do you have a build that works with UIKit/iPhone SDK? Would very much like to use this for iPhone dev, but there are dependencies on libs that aren’t available on the iPhone.

  8. This is great stuff, Would love to see it ported/built for the iphone. Any news on that?

  9. Hi all,

    MGTemplateEngine has been updated in the repository so that it should now build on iPhone. If you use the ICUTemplateMatcher matcher (the default one used in the sample code), you should be able to link against the libicucore library for iPhone builds. That library is here:

    /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/
    iPhoneOS2.1.sdk/usr/lib/libicucore.A.dylib

  10. Matt, been looking at the engine and I can’t figure one thing out. Is this possible?
    {% var exists/has data != nil %}

    {% /if %}

    Thanks

  11. Matt,
    What part of this engine requires Leopard? Would it be a simple job to rewrite so that the code will also run on Tiger (developed/compiled on Leopard), or are there core parts of the application that means it will not be able to work on Tiger at all?

  12. This is great. I’ve been looking for something like this.

  13. I found two memory leaks.

    consider following patch.

    diff libs/MGTemplateEngine/MGTemplateEngine.m MGTemplateEngine/MGTemplateEngine.m
    109c109
    self.matcher = nil;
    Common subdirectories: libs/MGTemplateEngine/MGTemplateEngine.xcodeproj and MGTemplateEngine/MGTemplateEngine.xcodeproj
    diff libs/MGTemplateEngine/MGTemplateStandardMarkers.m MGTemplateEngine/MGTemplateStandardMarkers.m
    615a616
    > [cycles release];

  14. Hi Matt
    Thx for your great work!

    I have question with HTML Template:
    I have for loop with and inside. Engine breaks by </.. tags. How can I fix that? I have tryed to change /for to endFor in variables without successes.

    Bests from Switzerland (Snow falls… :-( )

    Philippe

  15. Matt, please add my thanks to the many above for making this available.

    You mention that the engine is suitable for HTML generation. Do you know of an existing filter for replacing characters like ‘<' and '&' in variables with the equivalent HTML entities?

  16. [...] starters, here is MGTemplateEngine for your Cocoa output processing [...]

  17. [...] engine implemented for testing that I will either improve or replace. I would like to integrate MGTemplateEngine as a replacement, but I’ll need to see how much of it must be rewritten for Tiger.) This [...]

  18. hey man!
    thanks for the project but you have a 64-bit issue in the ICUTemplateMatcher.
    you should use NSUInteger for location just like NSRange does. int can’t hold the NSNotFound which is NSIntegerMax which leads to an infinite loop on 64-bit binaries.

    regards,
    reza

    diff –git a/Classes/MGTemplateEngine/ICUTemplateMatcher.m b/Classes/MGTemplateEngine/ICUTemplateMatcher.m
    index dbeb49a..3fe8112 100644
    — a/Classes/MGTemplateEngine/ICUTemplateMatcher.m
    +++ b/Classes/MGTemplateEngine/ICUTemplateMatcher.m
    @@ -151,7 +151,7 @@
    NSString *argsPattern = @”\”(.*?)(?<!\\\\)\"|'(.*?)(?<!\\\\)'|(\\S+)";
    NSMutableArray *args = [NSMutableArray array];

    - int location = 0;
    + NSUInteger location = 0;
    while (location != NSNotFound) {
    NSRange searchRange = NSMakeRange(location, [argString length] – location);
    NSRange entireRange = [argString rangeOfRegex:argsPattern options:RKLNoOptions

  19. Has anybody been able to compile this for the iPhone? Even after the posting change, I haven’t had any luck. There’s lots of dependencies on things that don’t seem to compile on the iPhone or come in arm binary versions. I would have love to have used this library!

Leave a comment