# Mail Merge: Merging Templates with Mustache Syntax in C#

> Learn how to merge templates using the mustache syntax in C# using TX Text Control .NET Server. Instead of using the built-in merge fields, this article shows how to use textual placeholders including merge blocks with the mustache syntax.

- **Author:** Bjoern Meyer
- **Published:** 2024-06-06
- **Modified:** 2025-11-16
- **Description:** Learn how to merge templates using the mustache syntax in C# using TX Text Control .NET Server. Instead of using the built-in merge fields, this article shows how to use textual placeholders including merge blocks with the mustache syntax.
- **6 min read** (1186 words)
- **Tags:**
  - ASP.NET
  - Mustache
  - Convert
- **Web URL:** https://www.textcontrol.com/blog/2024/06/06/mail-merge-merging-templates-with-mustache-syntax-in-csharp/
- **LLMs URL:** https://www.textcontrol.com/blog/2024/06/06/mail-merge-merging-templates-with-mustache-syntax-in-csharp/llms.txt
- **LLMs-Full URL:** https://www.textcontrol.com/blog/2024/06/06/mail-merge-merging-templates-with-mustache-syntax-in-csharp/llms-full.txt
- **GitHub Repository:** https://github.com/TextControl/MustacheMatcher

---

TX Text Control provides a powerful, MS Word compatible MailMerge 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 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](https://s1-www.textcontrol.com/assets/dist/blog/2024/06/06/a/assets/mergefields1.webp "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](https://s1-www.textcontrol.com/assets/dist/blog/2024/06/06/a/assets/mergefields2.webp "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](https://s1-www.textcontrol.com/assets/dist/blog/2024/06/06/a/assets/mergefields3.webp "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;
}
```

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 SubTextPart 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:

![Merge Fields in TX Text Control](https://s1-www.textcontrol.com/assets/dist/blog/2024/06/06/a/assets/fields.gif "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();
		}
	}
}
```

### 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.

---

## About Bjoern Meyer

As CEO, Bjoern is the visionary behind our strategic direction and business development, bridging the gap between our customers and engineering teams. His deep passion for coding and web technologies drives the creation of innovative products. If you're at a tech conference, be sure to stop by our booth - you'll most likely meet Bjoern in person. With an advanced graduate degree (Dipl. Inf.) in Computer Science, specializing in AI, from the University of Bremen, Bjoern brings significant expertise to his role. In his spare time, Bjoern enjoys running, paragliding, mountain biking, and playing the piano.

- [LinkedIn](https://www.linkedin.com/in/bjoernmeyer/)
- [X](https://x.com/txbjoern)
- [GitHub](https://github.com/bjoerntx)

---

## Related Posts

- [Programmatically Convert MS Word DOCX Documents to PDF in .NET C#](https://www.textcontrol.com/blog/2024/08/09/programmatically-convert-ms-word-docx-documents-to-pdf-in-net-c-sharp/llms.txt)
