TX Text Control provides a powerful, MS Word compatible MailMerge TX Text Control .NET Server for ASP.NET
DocumentServer Namespace
MailMerge Class
The MailMerge class is a .NET component that can be used to effortlessly merge template documents with database content in .NET projects, such as ASP.NET web applications, web services or Windows services.
engine that can be used to merge data into templates. Inserted merge fields are populated with data from a data source such as a database or a JSON object. The MergeField TX Text Control .NET Server for ASP.NET
DocumentServer.Fields Namespace
MergeField Class
The MergeField class implements the MS Word specific MERGEFIELD field.
class represents a merge field in a document and can be inserted using the out-of-the-box UI provided by the Document Editor.

The following screenshot shows the UI to insert a merge field in the Document Editor:

Merge Fields in TX Text Control

Mustache Syntax

But what if you have existing templates in other formats, such as TXT or HTML, that don't support MS Word merge fields?

The mustache syntax is a simple template language that can be used to insert values into a template. The following example shows a simple template with merge fields using the mustache syntax:

Merge Fields in TX Text Control

A merge field is enclosed in double curly braces. It consists entirely of plain text. The name of the merge field is the key that is used to look up the value in the data source.

Merge Blocks

Mustache also supports merge blocks that can be used to repeat content based on a collection of items. The following example shows a simple template with a merge block:

Merge Fields in TX Text Control

A merge block is enclosed in double curly braces with a hash symbol and ends with a slash. The merge block name is the key used to look up the collection in the data source.

Using Mustache Syntax with MailMerge

In order to use templates created with the mustache syntax in TX Text Control, the template must be converted into a TX Text Control compatible format. The MustacheMatcher class uses regular expressions to find merge fields and merge blocks.

Where is the code?

The complete code of the MustacheMatcher class is available at the end of this article.

The following code finds all merge fields in a string:

private static List<FieldInfo> FindMergeFields(string input)
{
const string pattern = @"\{\{(?!#|/)(.*?)\}\}";
var matches = new List<FieldInfo>();
foreach (Match match in Regex.Matches(input, pattern))
{
matches.Add(new FieldInfo
{
StartIndex = match.Index,
EndIndex = match.Index + match.Length,
FieldName = Regex.Replace(match.Groups[1].Value, @"\s+", "")
});
}
return matches;
}
view raw test.cs hosted with ❤ by GitHub

These results and then used to replace them with actual merge fields:

foreach (var field in FindMergeFields(text))
{
ReplaceWithMergeField(textControl, field);
}
view raw test.cs hosted with ❤ by GitHub
private static void ReplaceWithMergeField(ServerTextControl textControl, FieldInfo field)
{
textControl.Select(field.StartIndex, field.EndIndex - field.StartIndex);
var selectionText = textControl.Selection.Text;
var mergeField = new MergeField
{
Name = field.FieldName,
Text = selectionText,
ApplicationField =
{
DoubledInputPosition = true,
HighlightMode = HighlightMode.Activated
}
};
textControl.Selection.Text = string.Empty;
textControl.ApplicationFields.Add(mergeField.ApplicationField);
}
view raw test.cs hosted with ❤ by GitHub

The same is done for merge blocks, which can also be nested at different hierarchy levels:

private static List<BlockInfo> FindMergeBlocks(string input)
{
const string pattern = @"\{\{(#foreach\s+\w+|\s*\/foreach\s+\w+)\}\}";
var matches = new List<BlockInfo>();
var stack = new Stack<Match>();
foreach (Match match in Regex.Matches(input, pattern))
{
if (match.Value.StartsWith("{{#foreach"))
{
stack.Push(match);
}
else if (match.Value.StartsWith("{{/foreach") && stack.Count > 0)
{
var startMatch = stack.Pop();
var startVar = ExtractVariableName(startMatch.Value);
var endVar = ExtractVariableName(match.Value);
if (startVar == endVar)
{
matches.Add(new BlockInfo
{
StartIndex = startMatch.Index + 1,
EndIndex = match.Index + 1 + match.Length,
BlockName = startVar
});
}
}
}
matches.Sort((x, y) => x.StartIndex.CompareTo(y.StartIndex));
return matches;
}
view raw test.cs hosted with ❤ by GitHub

The results are converted to SubTextPart TX Text Control .NET Server for ASP.NET
TXTextControl Namespace
SubTextPart Class
A SubTextPart object represents a user-defined part of a TX Text Control document.
objects, the element used for merge blocks:

foreach (var block in FindMergeBlocks(text))
{
AddSubTextPart(textControl, block);
}
view raw test.cs hosted with ❤ by GitHub
private static void AddSubTextPart(ServerTextControl textControl, BlockInfo block)
{
var subTextPart = new SubTextPart("txmb_" + block.BlockName, 1, block.StartIndex, block.EndIndex - block.StartIndex);
textControl.SubTextParts.Add(subTextPart);
}
view raw test.cs hosted with ❤ by GitHub

Using the MustacheMatcher

The following code snippet shows how to use the MustacheMatcher class to convert a template with mustache syntax into a TX Text Control compatible format:

using TXTextControl.ServerTextControl serverTextControl = new TXTextControl.ServerTextControl();
serverTextControl.Create();
serverTextControl.Load(document, TXTextControl.BinaryStreamType.InternalUnicodeFormat);
MustacheMatcher.Convert(serverTextControl);
view raw test.cs hosted with ❤ by GitHub

The following screenshot shows the result of the conversion and the document after the merge fields have been populated:

Merge Fields in TX Text Control

Full Sources

The following code shows the complete MustacheMatcher class:

namespace TXTextControl.DocumentServer.Fields
{
using System.Collections.Generic;
using System.Text.RegularExpressions;
using TXTextControl;
public class TagInfo
{
public int StartIndex { get; set; }
public int EndIndex { get; set; }
}
public class FieldInfo : TagInfo
{
public string FieldName { get; set; }
}
public class BlockInfo : TagInfo
{
public string BlockName { get; set; }
}
public class MustacheMatcher
{
public static void Convert(ServerTextControl textControl)
{
var text = textControl.Text.Replace("\r\n", "\n");
// Process merge fields
foreach (var field in FindMergeFields(text))
{
ReplaceWithMergeField(textControl, field);
}
// Process merge blocks
foreach (var block in FindMergeBlocks(text))
{
AddSubTextPart(textControl, block);
}
// Process special elements
RemoveSpecialElements(textControl, text);
}
private static void ReplaceWithMergeField(ServerTextControl textControl, FieldInfo field)
{
textControl.Select(field.StartIndex, field.EndIndex - field.StartIndex);
var selectionText = textControl.Selection.Text;
var mergeField = new MergeField
{
Name = field.FieldName,
Text = selectionText,
ApplicationField =
{
DoubledInputPosition = true,
HighlightMode = HighlightMode.Activated
}
};
textControl.Selection.Text = string.Empty;
textControl.ApplicationFields.Add(mergeField.ApplicationField);
}
private static void AddSubTextPart(ServerTextControl textControl, BlockInfo block)
{
var subTextPart = new SubTextPart("txmb_" + block.BlockName, 1, block.StartIndex, block.EndIndex - block.StartIndex);
textControl.SubTextParts.Add(subTextPart);
}
private static void RemoveSpecialElements(ServerTextControl textControl, string text)
{
var matchElements = FindSpecialElements(text);
int indexOffset = 0;
foreach (var tag in matchElements)
{
textControl.Select(tag.StartIndex - indexOffset, tag.EndIndex - tag.StartIndex);
indexOffset += textControl.Selection.Length;
textControl.Selection.Text = string.Empty;
}
}
private static List<FieldInfo> FindMergeFields(string input)
{
const string pattern = @"\{\{(?!#|/)(.*?)\}\}";
var matches = new List<FieldInfo>();
foreach (Match match in Regex.Matches(input, pattern))
{
matches.Add(new FieldInfo
{
StartIndex = match.Index,
EndIndex = match.Index + match.Length,
FieldName = Regex.Replace(match.Groups[1].Value, @"\s+", "")
});
}
return matches;
}
private static List<TagInfo> FindSpecialElements(string input)
{
const string pattern = @"\{\{(#|\/)(.*?)\}\}";
var matches = new List<TagInfo>();
foreach (Match match in Regex.Matches(input, pattern))
{
matches.Add(new TagInfo
{
StartIndex = match.Index,
EndIndex = match.Index + match.Length
});
}
return matches;
}
private static List<BlockInfo> FindMergeBlocks(string input)
{
const string pattern = @"\{\{(#foreach\s+\w+|\s*\/foreach\s+\w+)\}\}";
var matches = new List<BlockInfo>();
var stack = new Stack<Match>();
foreach (Match match in Regex.Matches(input, pattern))
{
if (match.Value.StartsWith("{{#foreach"))
{
stack.Push(match);
}
else if (match.Value.StartsWith("{{/foreach") && stack.Count > 0)
{
var startMatch = stack.Pop();
var startVar = ExtractVariableName(startMatch.Value);
var endVar = ExtractVariableName(match.Value);
if (startVar == endVar)
{
matches.Add(new BlockInfo
{
StartIndex = startMatch.Index + 1,
EndIndex = match.Index + 1 + match.Length,
BlockName = startVar
});
}
}
}
matches.Sort((x, y) => x.StartIndex.CompareTo(y.StartIndex));
return matches;
}
private static string ExtractVariableName(string tag)
{
var startIndex = tag.StartsWith("{{#foreach") ? 10 : 11;
var length = tag.Length - startIndex - 2;
return tag.Substring(startIndex, length).Trim();
}
}
}
view raw test.cs hosted with ❤ by GitHub

Conclusion

Using the mustache syntax with TX Text Control's MailMerge engine is a powerful way to merge data into templates that are not based on MS Word merge fields. The MustacheMatcher class can be used to convert templates with mustache syntax into a TX Text Control compatible format.