TX Text Control .NET for WPF

JSON and XML

Topic

Data that arrives as text needs no classes in your application: a JSON string from a web service goes straight into MailMerge, and an XML file takes one extra step through a DataSet. This applies whether the WPF application receives the data, reads it from disk or lets users open a file in the Reporting tab. The sections below give the rules of each format, the exceptions for invalid JSON and the cultures that parse numbers and dates written as text.

The examples use invoice.json of the Getting Started tutorial Create a Template and a window with a TextControl named textControl1 that shows the template invoice.docx.

Merge JSON Data

MergeJsonData takes the JSON as a string and analyzes it like MergeObjects analyzes objects:

  • One object or an array. The JSON holds a single object or an array of objects with the same structure, and the template is merged once per object.
  • The first object defines the structure. Tables, columns and relations come from the property names and nested objects of the object, or of the first object in the array.
  • Nested objects and arrays. A nested object provides values for fields with dot notation, such as Customer.City, and an array the records of the merge block with its name, such as Items.
  • Images. An image placeholder can receive an image given as a Base64 string, as Images and Barcodes describes.

Invalid JSON makes MergeJsonData throw an exception derived from ParserException in the TXTextControl.DocumentServer.Json namespace:

ExceptionCause
UnexpectedEndOfInputExceptionThe JSON string is incomplete
UnexpectedTokenExceptionThe JSON contains an unexpected character
InvalidNumberFormatExceptionA number has an invalid format
InvalidCodePointExceptionA hexadecimal Unicode code point is invalid
UnknownLiteralNameTokenExceptionAn unknown character sequence stands where true, false or null is expected
UnknownParseErrorExceptionThe parser encountered another error

Catching the base class handles every case. In a button's Click handler in MainWindow.xaml.cs:

using System.IO;
using System.Windows;
using TXTextControl.DocumentServer;
using TXTextControl.DocumentServer.Json;
private void mergeJsonButton_Click(object sender, RoutedEventArgs e)
{
    using (MailMerge mailMerge = new MailMerge { TextComponent = textControl1 })
    {
        try
        {
            mailMerge.MergeJsonData(File.ReadAllText("invoice.json"));
        }
        catch (ParserException exception)
        {
            MessageBox.Show(this, $"Invalid JSON ({exception.GetType().Name}): {exception.Message}");
        }
    }
}

With valid data, the editor shows the merged invoice. Remove a closing bracket from invoice.json to see the message with the name of the exception.

Merge XML Data

XML goes through a DataSet: DataSet.ReadXml reads the file, and Merge merges its master table, as for any DataSet. The invoice as invoice.xml, copied to the output folder:

<?xml version="1.0" encoding="utf-8"?>
<Invoices>
  <Invoice>
    <Company>Contoso Ltd.</Company>
    <Name>Jane Miller</Name>
    <InvoiceNumber>INV-1001</InvoiceNumber>
    <Items>
      <Description>Consulting</Description>
      <Quantity>2</Quantity>
      <Price>150.00</Price>
    </Items>
    <Items>
      <Description>Training</Description>
      <Quantity>1</Quantity>
      <Price>480.00</Price>
    </Items>
  </Invoice>
</Invoices>

From the repeated elements, ReadXml creates the tables Invoice and Items. Because Items is nested in Invoice, it adds a key column Invoice_Id to both and a relation between them, which gives the Items block the items of each invoice:

using System.Data;
using System.Globalization;
using System.Windows;
using TXTextControl.DocumentServer;
private void mergeXmlButton_Click(object sender, RoutedEventArgs e)
{
    // Nested elements become related tables
    DataSet dataSet = new DataSet();
    dataSet.ReadXml("invoice.xml");

    using (MailMerge mailMerge = new MailMerge
    {
        TextComponent = textControl1,
        // The prices in the XML file are written the US English way
        DataSourceCulture = CultureInfo.GetCultureInfo("en-US")
    })
    {
        mailMerge.Merge(dataSet.Tables["Invoice"], true);
    }
}

Flat XML, whose tables are linked by key columns instead of nesting, needs relations added after ReadXml, as DataSets and DataTables shows. Microsoft's Inferring Relationships documents how ReadXml derives relations.

Parse Numbers and Dates With the Right Culture

Without a schema, ReadXml reads every value as a string, and JSON often carries dates as strings. MailMerge parses numbers and dates from the data with the culture in DataSourceCulture and formats them in the document with the culture in MergeCulture. Both default to the culture of the current thread, which follows the regional settings of the user's Windows account, so the same file can merge differently on two computers. Set them explicitly when the data has a fixed format:

mailMerge.DataSourceCulture = new CultureInfo("de-DE");   // how the data writes numbers
mailMerge.MergeCulture = new CultureInfo("en-US");        // how the document shows them

The properties must be set before the merge method is called. Merge Fields describes the same two properties for a single field.

Load JSON and XML Into the Reporting Tab

In the Select Data Source drop-down of the WPF Reporting tab, Load XML File and Load JSON File let users open a data file for template design; Data Sources shows the drop-down. In code, the DataSourceManager of the tab loads the same data: LoadJson a JSON string, LoadXmlFile an XML file and LoadXmlString XML as a string. Connect the handler with Loaded="textControl1_Loaded" on the TextControl element:

private void textControl1_Loaded(object sender, RoutedEventArgs e)
{
    ribbonReportingTab1.DataSourceManager.LoadXmlFile("invoice.xml");
}

A few records with the structure of the real data fill the drop-downs. The template designed with them merges with any data of that structure, as JSON, XML, objects or a DataSet.

Next Steps