TX Text Control .NET Server

Merge Fields

Topic

A merge field looks like a simple placeholder, but it carries rules of its own: a number or date format, text before and after the value, the case of its letters. Beyond the basics, you apply these rules to all fields of a template, insert date fields, handle fields without data, replace the merged content in code and decide how form fields receive their values.

Field names, inserting fields in the Document Editor, MS Word and code and handling missing data are the subjects of Merge Fields in Getting Started. IF fields are covered in Conditional Content. The programs work with the files template.docx and data.json of Simple Mail Merge.

The MergeField Class

A merge field is an MS Word MERGEFIELD field, an ApplicationField in the document. The MergeField class in the TXTextControl.DocumentServer.Fields namespace is an adapter for it. The constructor without parameters creates a new field, and the constructor with an ApplicationField wraps an existing one. Its properties:

PropertySets
NameThe name that is matched with the data
TextThe text the field shows until it's merged
TextBefore, TextAfterText shown before and after the value
NumericFormatA format for numbers, the MS Word switch \#
DateTimeFormatA format for dates and times, the MS Word switch \@
TextFormatThe case of the letters: Uppercase, Lowercase, FirstCapital, TitleCase or None
DataSourceCulture, MergeCultureThe cultures that parse the value from the data and format it in the document

In the Document Editor, the JavaScript TXTextControl.MergeField object has matching properties for the text before and after, the numeric, date and time formats and the text format.

MailMerge doesn't use the properties Mapped and VerticalFormatting, or PreserveFormatting, which MergeField inherits from MailMergeFieldAdapter. The formatting of the field in the template, such as its font and color, is always kept.

Format Numbers and Dates

NumericFormat accepts the standard and custom numeric format strings of .NET. A custom format can have separate sections for positive and negative numbers and zero, separated by ;:

Format5000-5000
$##,###.00$5,000.00-$5,000.00
C2$5,000.00($5,000.00)
#,##0.00;(#,##0.00)5,000.00(5,000.00)

DateTimeFormat accepts the standard and custom date and time format strings of .NET, such as MMMM d, yyyy.

The currency symbol, the decimal separator and the names of months depend on the culture that formats the value. Keep numbers and dates as typed values in the data, put the formats into the template and set the cultures of the merge explicitly, so that the result doesn't depend on the regional settings of the server. The blog article Format Numeric String Values with MailMerge lists the format specifiers.

Set Formats on All Fields of a Template

Merge fields can be in the main text, in headers and footers and in text frames. ServerTextControl.TextParts contains all these parts, each with its own ApplicationFields collection. The following program sets formats on three fields of template.docx, wherever they are, and merges the template with fixed cultures:

using System.Globalization;
using TXTextControl;
using TXTextControl.DocumentServer;
using TXTextControl.DocumentServer.Fields;

string jsonData = File.ReadAllText("data.json");

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();

    LoadSettings loadSettings = new LoadSettings()
    {
        ApplicationFieldFormat = ApplicationFieldFormat.MSWord,
        LoadSubTextParts = true
    };

    tx.Load("template.docx", StreamType.WordprocessingML, loadSettings);

    // The main text, text frames, headers and footers
    foreach (IFormattedText textPart in tx.TextParts)
    {
        foreach (ApplicationField applicationField in textPart.ApplicationFields)
        {
            if (applicationField.TypeName != "MERGEFIELD")
            {
                continue;
            }

            MergeField mergeField = new MergeField(applicationField);

            switch (mergeField.Name)
            {
                case "Customer.CustomerName":
                    mergeField.TextFormat = TextFormatOptions.Uppercase;
                    break;

                case "AmountDue":
                    mergeField.TextBefore = "Amount due: ";
                    mergeField.NumericFormat = "$#,##0.00";
                    break;

                case "DueDate":
                    mergeField.DateTimeFormat = "MMMM d, yyyy";
                    break;
            }
        }
    }

    using (MailMerge mailMerge = new MailMerge
    {
        TextComponent = tx,
        DataSourceCulture = CultureInfo.InvariantCulture,
        MergeCulture = CultureInfo.GetCultureInfo("en-US")
    })
    {
        mailMerge.MergeJsonData(jsonData);
    }

    tx.Save("output.pdf", StreamType.AdobePDF);
}

The code works as follows:

  • Each text part implements IFormattedText, whose ApplicationFields contains the fields of that part. TypeName is MERGEFIELD for merge fields.
  • new MergeField(applicationField) wraps the field, and changes to its properties apply to the field in the document.
  • CultureInfo.InvariantCulture parses the numbers and dates of the JSON data, and the US English culture formats them in the document.

Insert Date Fields

A DATE field shows the current date in a format. During the merge, MailMerge fills it with the date of the merge. The DateField adapter creates such a field:

using TXTextControl;
using TXTextControl.DocumentServer;
using TXTextControl.DocumentServer.Fields;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();

    tx.Selection = new Selection { Text = "Printed on: " };

    DateField dateField = new DateField()
    {
        Format = "MMMM d, yyyy",
        Date = DateTime.Now
    };

    tx.ApplicationFields.Add(dateField.ApplicationField);

    using (MailMerge mailMerge = new MailMerge
    {
        TextComponent = tx,
        DateFieldUtcOffset = 60 // minutes
    })
    {
        mailMerge.MergeJsonData(File.ReadAllText("data.json"));
    }

    tx.Save("date.pdf", StreamType.AdobePDF);
}

DateField.Format is a .NET date and time format string, and Culture sets the culture that formats the date. MailMerge.DateFieldUtcOffset sets the offset from UTC in minutes for all date fields. With null, the default, the local date and time of the server are used.

In the Document Editor, users insert a DATE field with Date in the Insert Special Field drop-down of the Reporting tab. Your JavaScript code opens the dialog box for a new DATE field with TXTextControl.showMergeFieldDialog(TXTextControl.FieldType.DateField), as Conditional Content shows for IF fields.

Show a Text for Fields Without Data

A field without data is removed, because RemoveEmptyFields is true by default. To show a text instead, keep such fields and change their text in the FieldMerged event, which occurs for every field, whether it received data or not:

using (MailMerge mailMerge = new MailMerge
{
    TextComponent = tx,
    RemoveEmptyFields = false
})
{
    mailMerge.FieldMerged += (sender, e) =>
    {
        if (!e.Merged && e.MailMergeFieldAdapter.TypeName == "MERGEFIELD")
        {
            MergeField mergeField = new MergeField(e.MailMergeFieldAdapter.ApplicationField);
            mergeField.Text = "n/a";
        }
    };

    mailMerge.MergeJsonData(jsonData);
}

The arguments of FieldMerged tell you more about the field:

PropertyContains
MergedWhether the field received data
MailMergeFieldAdapterThe merged field, which can be changed
DataRowThe data record; e.DataRow["AmountDue"] returns the value of a column
MergedFieldThe merged field as a byte array in the TX Text Control format, which can be replaced
MergeBlockName, TableCellThe merge block and the table cell that contain the field, if any

Merge Formatted Text

A merge field receives plain text. To merge formatted text, such as HTML notes from a web form, replace the merged content in FieldMerged: load the text into a second ServerTextControl, save it in the TX Text Control format and assign the bytes to MergedField. With an HTML value in the invoice data, such as "Notes": "<p>Thank you for your <strong>business</strong>.</p>", and a merge field named Notes in the template:

mailMerge.FieldMerged += (sender, e) =>
{
    if (e.Merged && e.MailMergeFieldAdapter is MergeField mergeField && mergeField.Name == "Notes")
    {
        using (ServerTextControl htmlText = new ServerTextControl())
        {
            htmlText.Create();

            // The merged value is HTML: load it and replace the field with the result
            htmlText.Load(mergeField.Text, StringStreamType.HTMLFormat);

            byte[] data;
            htmlText.Save(out data, BinaryStreamType.InternalUnicodeFormat);
            e.MergedField = data;
        }
    }
};

The blog article MailMerge: Merging Hyperlinks using the FieldMerged Event replaces fields with hyperlinks in the same way and keeps the font and color of the field.

Keep Merge Fields After the Merge

By default, a merged field is removed and only its text stays in the document. When the merged document must be editable and merged again, for example to preview a template with data, assign MergedField to itself: the fields are then kept in the result.

mailMerge.FieldMerged += (sender, e) => e.MergedField = e.MergedField;

Merge Form Fields

Templates can contain form fields that users fill out later, for example in the Document Viewer. MailMerge.FormFieldMergeType decides whether they receive data:

ValueForm fields
None (default)Aren't merged
PreselectKeep their controls, and their content is preselected with the data where possible; fields without a matching column stay unchanged
ReplaceAre replaced with the data, so the result has no form fields; empty form fields are removed according to RemoveEmptyFields

The blog article Merging Form Fields using the MailMerge Class merges a form template with a check box, drop-down lists, a date and a text field and saves it as PDF. With Preselect, the fields stay fillable in Adobe Acrobat and show the merged values; with Replace, only the values remain as text:

The same form merged with Preselect, where the check box, drop-down lists and text fields stay fillable with the merged values, and with Replace, where the values are plain text

The following merge fills the form fields of form.tx, a template with form fields named like the data, and reports each field in the FormFieldMerged event:

using TXTextControl;
using TXTextControl.DocumentServer;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("form.tx", StreamType.InternalUnicodeFormat);

    using (MailMerge mailMerge = new MailMerge
    {
        TextComponent = tx,
        FormFieldMergeType = FormFieldMergeType.Preselect
    })
    {
        mailMerge.FormFieldMerged += (sender, e) =>
            Console.WriteLine($"{e.FormField.Name}: {(e.Merged ? "merged" : "no data")}");

        mailMerge.MergeJsonData(File.ReadAllText("data.json"));
    }

    tx.Save("form-prefilled.tx", StreamType.InternalUnicodeFormat);
}

A template can contain merge fields and form fields together: data that users must not change goes into merge fields, and values that users may correct into preselected form fields. The blog article Merging Form Fields using the MailMerge Class shows such a template.

Prefixes in Field Names

A dot in a field name addresses related data: Customer.CustomerName is the column CustomerName of the related table or nested object Customer, and longer paths reach further levels. The prefix image: has a special meaning: it marks a merge field that is merged as an image, as Images describes.

Inside a merge block, a prefix starts at the table of the block, not at the master table. The sample database of TX Text Control Words shows this with three tables: the orders in Sales_SalesOrderHeader, their order lines in Sales_SalesOrderDetail, related by SalesOrderID, and the products in Production_Product, related to each order line by ProductID:

Three related tables: Sales_SalesOrderHeader connected to Sales_SalesOrderDetail by SalesOrderID, and Sales_SalesOrderDetail connected to Production_Product by ProductID.

With Sales_SalesOrderHeader as the master table, an invoice template has these fields:

WhereFieldValue
Outside all blocksOrderDateA column of the order
In the merge block Sales_SalesOrderDetailOrderQty, UnitPrice, LineTotalColumns of the current order line, without a prefix
In the same blockProduction_Product.ProductNumber, Production_Product.NameColumns of the product of the current order line

Table row merge block Sales_SalesOrderDetail in TX Text Control Words with the fields ProductNumber, Name, OrderQty, UnitPrice and LineTotal and an image placeholder, highlighted in red

You pass only the master table to Merge, and MailMerge follows the relations to the order lines and their products. Nested Blocks shows a DataSet with such a relation in code, and image placeholders take prefixes in the same way, as Images shows.

Merged payment invoice with the customer address, the order date and due date, and a product table whose rows show the product code, name, quantity, prices and a photo of each bicycle, followed by the totals

Next Steps

  • Conditional Content: insert text with IF fields depending on the data.
  • Styling: format merged cells and rows in code.
  • Merge Blocks: repeat fields for each record of a collection.
  • Fields in Document Processing: the other kinds of fields.