TX Text Control provides a powerful, MS Word compatible Mail
╰ 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 Merge
╰ 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:
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:
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:
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; | |
} |
These results and then used to replace them with actual merge fields:
foreach (var field in FindMergeFields(text)) | |
{ | |
ReplaceWithMergeField(textControl, field); | |
} |
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); | |
} |
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; | |
} |
The results are converted to Sub
╰ 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); | |
} |
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); | |
} |
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); |
The following screenshot shows the result of the conversion and the document after the merge fields have been populated:
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(); | |
} | |
} | |
} |
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.