TX Text Control .NET Server

Export XFDF

Topic

XFDF, the XML Forms Data Format, stores the values of a PDF form as XML, apart from the form itself. An XFDF file names the PDF document it belongs to and lists each field with its value, so systems can exchange form data, store it in a database or fill a form with it later. TX Text Control has no XFDF classes of its own: you create XFDF with System.Xml.Linq from the values that Forms.GetAcroFormFields returns, and you read it the same way to fill a form template.

How GetAcroFormFields returns the values of each field type is described in Import Form Data. The examples use Documents/order-form-filled.pdf, the order form of Form Fields after a customer filled it in.

How an XFDF File Is Built

XFDF is a part of Adobe's PDF specifications. A file with the values of the order form looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<xfdf xml:space="preserve" xmlns="http://ns.adobe.com/xfdf/">
  <f href="order-form-filled.pdf" />
  <fields>
    <field name="Customer">
      <value>Acme Corporation</value>
    </field>
    <field name="Product">
      <value>Priority Support - 12 Months</value>
    </field>
    <field name="TermsAccepted">
      <value>On</value>
    </field>
  </fields>
</xfdf>
ElementAttributeContent
xfdfxmlnsThe root element, in the namespace http://ns.adobe.com/xfdf/
fhrefThe PDF document the values belong to
fieldsAll form fields
fieldnameOne form field, identified by its name
valueThe value entered into the form field

XFDF can also hold annotations and bookmarks of a PDF document. The examples below write and read form field values only.

Export the Values of a PDF Form

The following Program.cs reads the fields of the filled order form and saves their values as Documents/order-form-filled.xfdf:

using System.Xml.Linq;
using TXTextControl.DocumentServer.PDF;
using TXTextControl.DocumentServer.PDF.AcroForms;

string pdfPath = "Documents/order-form-filled.pdf";
FormField[] fields = Forms.GetAcroFormFields(pdfPath);

XNamespace xfdfNamespace = "http://ns.adobe.com/xfdf/";
XElement fieldsElement = new XElement(xfdfNamespace + "fields");
HashSet<string> names = new HashSet<string>();

foreach (FormField field in fields)
{
    string? value;

    switch (field)
    {
        case FormTextField textField:
            value = textField.Value;
            break;

        case FormRadioButton:
            // The selected option of a radio group isn't available
            continue;

        case FormCheckBox checkBox:
            value = checkBox.IsChecked ? "On" : "Off";
            break;

        case FormChoiceField choiceField:   // combo boxes and list boxes
            value = choiceField.Value;
            break;

        default:                            // push buttons have no value
            continue;
    }

    // A name can occur more than once: write it once
    if (!names.Add(field.FieldName))
    {
        continue;
    }

    XElement fieldElement = new XElement(xfdfNamespace + "field", new XAttribute("name", field.FieldName));

    if (!string.IsNullOrWhiteSpace(value))
    {
        fieldElement.Add(new XElement(xfdfNamespace + "value", value));
    }

    fieldsElement.Add(fieldElement);
}

XDocument xfdf = new XDocument(
    new XDeclaration("1.0", "UTF-8", null),
    new XElement(xfdfNamespace + "xfdf",
        new XAttribute(XNamespace.Xml + "space", "preserve"),
        new XElement(xfdfNamespace + "f", new XAttribute("href", Path.GetFileName(pdfPath))),
        fieldsElement));

xfdf.Save("Documents/order-form-filled.xfdf");
Console.WriteLine(xfdf);

The code works as follows:

  • Forms.GetAcroFormFields returns the fields of the PDF. Each type provides its value differently: FormTextField.Value holds the text, FormCheckBox.IsChecked the state and FormChoiceField.Value the selected item.
  • A check box is written as On or Off, the names of the two states in PDF forms that TX Text Control creates. PDF forms created with other tools can name the checked state differently, for example Yes.
  • FormRadioButton is tested before FormCheckBox, from which it derives, and skipped, because no member tells which option of the group is selected. Push buttons have no value and are skipped as well.
  • The HashSet writes each field name once, because a name can occur several times in the array. A field without a value gets a field element without a value element.
  • A date form field of TX Text Control is a text field in the PDF, so its date is written as the text the form displayed.

To offer the export in a web application, read the uploaded PDF with the byte array overload of GetAcroFormFields, as Accept Uploaded Forms in ASP.NET Core shows, and return the XFDF document instead of JSON.

Fill a Form Template From XFDF

TX Text Control doesn't apply XFDF to an existing PDF. To create a PDF with the values of an XFDF file, fill your template of the form and save it. The following program reads Documents/order-form-filled.xfdf, fills Documents/order-form.tx and saves the result as a fillable PDF:

using System.Globalization;
using System.Xml.Linq;
using TXTextControl;

XNamespace xfdfNamespace = "http://ns.adobe.com/xfdf/";
XDocument xfdf = XDocument.Load("Documents/order-form-filled.xfdf");

// Field name and value; a field without a value element gets an empty string
Dictionary<string, string> values = new Dictionary<string, string>();

foreach (XElement field in xfdf.Root?.Element(xfdfNamespace + "fields")?.Elements(xfdfNamespace + "field")
                           ?? Enumerable.Empty<XElement>())
{
    string? name = field.Attribute("name")?.Value;

    if (!string.IsNullOrEmpty(name))
    {
        values[name] = field.Element(xfdfNamespace + "value")?.Value ?? string.Empty;
    }
}

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

    // The main text, text frames, headers and footers
    foreach (IFormattedText textPart in tx.TextParts)
    {
        foreach (FormField formField in textPart.FormFields)
        {
            if (string.IsNullOrEmpty(formField.Name) || !values.TryGetValue(formField.Name, out string? value))
            {
                continue;
            }

            switch (formField)
            {
                case CheckFormField checkFormField:
                    checkFormField.Checked = value == "On";
                    break;

                case SelectionFormField selectionFormField:
                    // -1 selects nothing if the value isn't one of the items
                    selectionFormField.SelectedIndex = Array.IndexOf(selectionFormField.Items, value);
                    break;

                case DateFormField dateFormField:
                    // Parse the date with the culture the form displays it in
                    dateFormField.Date = DateTime.TryParse(value, CultureInfo.GetCultureInfo("en-US"),
                        DateTimeStyles.None, out DateTime date) ? date : null;
                    break;

                default:
                    if (!string.IsNullOrWhiteSpace(value))
                    {
                        formField.Text = value;
                    }
                    break;
            }
        }
    }

    tx.Save("Documents/order-form-from-xfdf.pdf", StreamType.AdobePDF);
}

The code works as follows:

  • XDocument.Load reads the XFDF file, and the loop collects the name attribute and the value element of each field in the XFDF namespace.
  • The loop over the form fields of all text parts sets each value through the property of its form field type, as in Transfer the Values Into a Form Template. A check box is checked when its XFDF value is On.
  • StreamType.AdobePDF saves a fillable PDF with the values. Flatten the fields before saving to fix the values, as Flatten Forms describes.

The blog article Export and Import Adobe PDF XFDF XML Files in .NET C# explains XFDF and packages the export and import in a helper class, XfdfProcessing. The class is sample code of the article and not part of TX Text Control. It's available in the XfdfProcessing repository on GitHub.

Next Steps

  • Import Form Data: read the values of filled PDF forms as JSON or a C# model.
  • Flatten Forms: turn a filled form template into a document with fixed values.
  • AcroForms: create the PDF forms whose data you export.