A FreeMarker Converter in C#

Not long ago, I was asked if SparkPost supported FreeMarker templates. At the time, I had to say no, but I hate saying “no”, so I immediately init’d a new Git project and called it FreeMarker-Converter-in-C-Sharp. You can get to it here if you want to pull a copy.

You may ask “why C#?” and not PHP or NodeJS, or Go, or insert-language-choice-here. Well, why not? In this case, the customer I was speaking with worked in a C# environment, so it made the most sense. I also started on a PHP version and could finish it quickly if the need arose. A conversion tool like this can be ported to virtually any language that supports PCRE. It really does not matter what language you write it in, porting it to the language du jour should be trivial.

What’s a FreeMarker?

First let’s talk about FreeMarker and why you might care. FreeMarker is an open source Apache project that uses Java to link templates and substitution data with data tables to create highly dynamic documents. These documents can be then used for HTML pages, email and other things. In this particular case, the customer was using FreeMarker to create email messages in a similar way to how SparkPost uses templates to create dynamic messages. Unfortunately, the FreeMarker markup language is just different enough that it will not work natively in SparkPost. What is needed is a conversion tool for pre-processing FreeMarker templates into SparkPost templates.

SparkPost includes a full-featured template engine that provides things like data substitutions, conditional branching, and array iteration. FreeMarker has similar functionality, but the substitution language is significantly different. The meat of the project was in matching up the most used FreeMarker functions with corresponding SparkPost functions and then building a process to transform the FreeMarker template into a correct SparkPost template. The final step is to save the new template in a way that it can be used with SparkPost.

Template Transformation

As you take a look through the Git project, you’ll notice a few interesting things. There are FLAGS, INTERPOLATIONS, and COMMENTS to account for. FLAGS are used to tell Java there is an action happening, like an item list or if condition. COMMENTS are just comments in the document that should not be printed or interpreted in any way. INTERPOLATIONS are what SparkPost calls substitutions.

  • FLAGS look like this <#action … > and can be things like <#list …> or <#if …>
  • COMMENTS look like this <#– comment –>
  • INTERPOLATIONS look like this ${variable} or ${Recipient.lead.firstname} or ${Gears.unsubscribe()} or ${Recipient.lead.firstname[0]!””}.

That last one is interesting because the [0]!”” part actually means “only show this if it is a non-blank value”

There are also ASSIGNMENTS which are really just variable declarations.
IE: <#assign countrycontact=Recipient.contact.localname[0]!”” />

In SparkPost, that is equivalent to
“substitution_data” : {“countrycontact” :”Recipient.contact.localname”}

All of the most common FreeMarker functions have SparkPost equivalents, so the code really just has to find them using PCRE and replace them. For instance:

${Recipient.lead.firstname[0]!””} becomes {{Recipient.lead.firstname}}
<#assign myvalue=12 /> becomes “substitution_data” : {“myvalue” :”12″}
<#if> blah </#if> becomes {{if …}} blah {{end}}

This is all done with some fancy PCRE footwork like this:

// Replace if conditions
rgx = new Regex(@"<#if(.*)>([\s\n\r]*)(.*)([\s\n\r]*)</#if>");
text = rgx.Replace(text, "\r\n{{if $1}}\r\n $3 \r\n{{end}");

Then it is all reassembled into a SparkPost formatted transmission template ready to call with the Transmissions API.

The bulk of the transformation logic lives in a single C# file (program.cs), which is 221 lines of C# including comments. The code will create INBOX and OUTBOX folders if they don’t exist and then try to process everything in the INBOX as a FreeMarker template. Put your raw FreeMarker templates in the INBOX and run the code. Your converted SparkPost templates will be placed in the OUTBOX without damaging the originals.

Feel free to fork at will, pull requests welcome, and leave any comments or suggestions in the Github repo.