XML Processing
Structured XML data, such as an address list or a product catalog, can be edited in the WPF TextControl like a document. TX Text Control reads the XML file with the two files it refers to: a document type definition (DTD), the rules for which elements may appear where, and a CSS stylesheet, which formats the elements. Users edit the formatted result while every change is checked against the DTD, and code-behind reads, replaces, adds and removes elements by name.
The Enterprise edition includes XML editing; see Editions. Code on this page belongs in MainWindow.xaml.cs with the TextControl named textControl1. To use XML as the data of a mail merge instead, read JSON and XML.
MainWindow.xaml.cs needs these using directives:
using System.Text;
using System.Windows;
using TXTextControl;
The Three Files of an XML Document
The sample is an address list, address.xml. Its second and third lines name the stylesheet and the DTD:
<address>
<address_record>
<name>Paul</name>
<street>Baker Street</street>
<city>London</city>
<email>paul@hotmail.com</email>
</address_record>
<address_record>
<name>George</name>
<email>george@hotmail.com</email>
</address_record>
</address>
address.dtd requires at least one record and, in each record, a name and an email address, with an optional street and city between them:
<!ELEMENT address ( address_record+ )>
<!ELEMENT address_record ( name, street?, city?, email )>
<!ELEMENT name (#PCDATA )>
<!ELEMENT street (#PCDATA )>
<!ELEMENT city (#PCDATA )>
<!ELEMENT email (#PCDATA )>
The DTD doesn't have to be a file of its own. It can also be written inline, in square brackets inside the DOCTYPE declaration of the XML file, so that one file carries the data and its rules:
<address>
...
</address>
address.css turns the list into a table:
address { display: table }
address_record { display: table-row }
name, street, city, email { display: table-cell; border: 1px; font-size: 11pt }
This is what the WPF TextControl shows after loading the three files:

Only the stylesheet decides the layout. These rules show each name as a blue heading and the other elements as a list:
name { display: block; font-size: 14pt; color: blue }
street, city, email { display: list-item; font-size: 11pt }

Load an XML File and Handle Errors
Pass StreamType.XMLFormat to Load. Two events report problems with the file; attach them in MainWindow.xaml:
<WPF:TextControl x:Name="textControl1"
XmlInvalid="textControl1_XmlInvalid"
XmlNotWellFormed="textControl1_XmlNotWellFormed" />
The code-behind loads the file, shows whether the document is validated, and reports errors:
private void OpenXml_Click(object sender, RoutedEventArgs e)
{
textControl1.Load("address.xml", StreamType.XMLFormat);
statusText.Text = textControl1.XmlEditMode == XmlEditMode.Validate
? "Valid XML: changes are checked against the DTD"
: "Not validated";
}
private void textControl1_XmlInvalid(object sender, XmlErrorEventArgs e)
{
MessageBox.Show($"The document doesn't match its DTD: {e.Reason} (line {e.LineNumber}, position {e.LinePosition})");
}
private void textControl1_XmlNotWellFormed(object sender, XmlErrorEventArgs e)
{
MessageBox.Show($"The file isn't well-formed XML: {e.Reason} (line {e.LineNumber})");
}
| The file | Event | Result |
|---|---|---|
| Matches its DTD | None | Loaded with the CSS styles; XmlEditMode is Validate |
| Breaks a rule of the DTD | XmlInvalid | Loaded as a normal document, without validation |
| Contains invalid XML syntax | XmlNotWellFormed | Not loaded |
XmlErrorEventArgs describes the error with Reason, LineNumber, LinePosition, FilePosition and URL. Where a value is 0 or an empty string, the information isn't available. The statusText of the handler is a TextBlock in the window. Load(StreamType.XMLFormat) without a file name shows the Open dialog box filtered to XML files.
What Changes in the XML Editor Mode
While XmlEditMode is Validate, the editor protects the structure of the document:
- Text can be typed and deleted only in elements that contain text. A selection stays inside one element and expands to its whole text when the user drags across elements. TAB and SHIFT+TAB select the next and the previous element.
- Formatting commands change the style of the current element, because XML carries no formatting. In code, FormattingStylesDialog opens the style dialog, and InlineStyles and ParagraphStyles change the styles directly.
- Text pasted from another document loses its formatting.
Selection.LoadandSelection.Savecan't be used, because an XML document can't be part of another document.
Validate can't be set in code; loading a valid file switches it on. Setting XmlEditMode to NoValidate ends validation. The TextControl then works as a normal editor, and the document can't be saved as XML any more, because it may no longer be valid.
Work With Elements in Code
GetXmlElements takes an element name and returns an XmlElementCollection of XmlElement objects. When an element has sub-elements, its text consists of their texts, one per line, separated by \n. The handler below lists the names, rewrites the second record and inserts a third one:
private void EditElements_Click(object sender, RoutedEventArgs e)
{
StringBuilder names = new StringBuilder();
foreach (XmlElement name in textControl1.GetXmlElements("name"))
{
names.AppendLine(name.Text);
}
statusText.Text = names.ToString();
XmlElementCollection records = textControl1.GetXmlElements("address_record");
// One line per sub-element: name and email
records.GetItem(2).Text = "George\ngeorge@textcontrol.com";
// -1 ignores the "before" position; 2 inserts after the second record
XmlElement newRecord = new XmlElement("address_record", "Ringo\nAbbey Road\nLondon\nringo@hotmail.com");
if (!records.Add(newRecord, -1, 2))
{
MessageBox.Show("The DTD doesn't allow this record here.");
}
}
The code works as follows:
- GetItem returns the element with a one-based index or
null; without a parameter, the element at the input position. - XmlElement.Text reads and writes the text, one line per sub-element.
- The XmlElement constructor takes the name and, optionally, the text of the new element.
- XmlElementCollection.Add places the element before or after a position in the collection, where 0 stands for the element at the input position and -1 ignores the parameter. If the DTD doesn't allow the element there,
AddreturnsfalseandXmlInvalidoccurs.
Remove deletes the element with a one-based index, or with 0 the one at the input position. For sub-elements, AddChild, GetChildItem and RemoveChild are available; the DTD determines where a new child goes. Any change that would break the DTD fails, such as removing the only remaining record.
Save the XML File and Its Styles
The XML file receives the elements and their text only. Formatting belongs in the stylesheet, which is saved in a separate call:
private void SaveXml_Click(object sender, RoutedEventArgs e)
{
textControl1.Save("address.xml", StreamType.XMLFormat);
textControl1.Save("address.css", StreamType.CascadingStylesheet);
}
The DTD is never changed or saved, and TX Text Control can't create an XML document from scratch; it edits XML files that come with a DTD.
Next Steps
- Stylesheets: the formatting styles that shape XML documents.
- Loading Documents: the other formats
Loadreads. - JSON and XML: XML as data for a mail merge.
- Saving and Converting Documents: save documents in other formats.