Export XFDF
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:
<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>
| Element | Attribute | Content |
|---|---|---|
xfdf | xmlns | The root element, in the namespace http://ns.adobe.com/xfdf/ |
f | href | The PDF document the values belong to |
fields | All form fields | |
field | name | One form field, identified by its name |
value | The 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
OnorOff, 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 exampleYes. - 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
HashSetwrites each field name once, because a name can occur several times in the array. A field without a value gets afieldelement without avalueelement. - 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.Loadreads the XFDF file, and the loop collects thenameattribute and thevalueelement of eachfieldin 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.AdobePDFsaves 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.