CodeGym /Courses /C# SELF /Advanced work with XML: Xm...

Advanced work with XML: XmlDocument and XPath

C# SELF
Level 48 , Lesson 2
Available

1. Introduction

Imagine a giant XML that some SOAP service or banking API sent you. It has hundreds of nested elements, inconsistent structures, and you need to find just a single payment amount from last month. The serialization approach doesn't fit here — it's too clunky and you don't know in advance where to look. You need a "magnifying glass" for the XML tree and the skill to quickly walk its branches.

For these cases .NET has had a powerful tool for many years — the XmlDocument class, complemented by the query language XPath. Today you'll learn to:

  • Load XML into a DOM tree;
  • Navigate and find elements manually and with XPath;
  • Modify the contents of an XML document;
  • Add, remove and change nodes;
  • Use XPath for complex selections.

2. What is XmlDocument?

XmlDocument is a class from the System.Xml namespace. It's an implementation of the DOM (Document Object Model) — a representation of the whole XML file as an in-memory tree of objects.

A bit of theory and analogies

If XmlSerializer is like a LEGO constructor — turning an object into a set of bricks and back — then XmlDocument is like working with a real tree: it has a root, branches (elements), leaves (text nodes), and you can walk the tree, add branches, trim leaves and transplant them wherever you want.

Simple XML document loading

Say we have this XML:

<users>
  <user id="1">
    <name>Olga</name>
    <age>28</age>
  </user>
  <user id="2">
    <name>Igor</name>
    <age>35</age>
  </user>
</users>

Load it into memory:

using System.Xml;

string xml = @"
<users>
  <user id='1'>
    <name>Olga</name>
    <age>28</age>
  </user>
  <user id='2'>
    <name>Igor</name>
    <age>35</age>
  </user>
</users>";

XmlDocument doc = new XmlDocument();
doc.LoadXml(xml); // or doc.Load("path_to_file.xml");

After calling LoadXml (or Load if the XML is from a file) you have full access to the document contents.

3. Navigating the DOM tree

DOM tree structure

Every XML document after loading becomes a tree made of nodes of different types:

Node type Class in .NET Example
Document XmlDocument
<users>...</users>
Element XmlElement
<user>, <name>
Attribute XmlAttribute
id="1"
Text XmlText Olga, 28
Comment XmlComment
<!-- comment -->

To access the elements you need you'll have to "walk the tree" using properties like ChildNodes, Attributes, ParentNode, etc.

Get the root element

XmlElement root = doc.DocumentElement;
Console.WriteLine(root.Name); // users

Iterate child elements

foreach (XmlNode node in root.ChildNodes)
{
    if (node is XmlElement user)
    {
        // user is <user id="...">...</user>
        string id = user.GetAttribute("id");
        string name = user["name"].InnerText;
        string age = user["age"].InnerText;
        Console.WriteLine($"User {id}: {name}, age {age}");
    }
}

IMPORTANT: Access via user["name"] is only possible if there's a direct child element named <name> among the immediate descendants.

Access attributes and text

var firstUser = root.FirstChild as XmlElement;
string id = firstUser.GetAttribute("id"); // "1"
string name = firstUser["name"].InnerText; // "Olga"

4. Modifying the XML document

Change a value

Say Olga suddenly decided to "rejuvenate":

var olga = root.FirstChild as XmlElement;
olga["age"].InnerText = "22"; // now <age>22</age>

Add a new user

XmlElement newUser = doc.CreateElement("user");
newUser.SetAttribute("id", "3");

XmlElement name = doc.CreateElement("name");
name.InnerText = "Vasilisa";
XmlElement age = doc.CreateElement("age");
age.InnerText = "19";

newUser.AppendChild(name);
newUser.AppendChild(age);
root.AppendChild(newUser);

Remove an element

Remove the second user ("Igor"):

XmlNode userToDelete = root.SelectSingleNode("user[@id='2']");
if (userToDelete != null)
    root.RemoveChild(userToDelete);

Save changes

doc.Save("users_updated.xml");
// Or doc.OuterXml — to get the XML string

5. XPath — the language for searching and selecting in XML

Working with the DOM tree gets tedious if you need to search elements "by condition" — e.g. all users older than 25. That's what XPath is for — a navigation language for XML trees.

Basic XPath usage

XPath queries can be executed via methods SelectSingleNode (returns the first matching node) and SelectNodes (returns a node collection).

Example: find user with id = 1

XmlNode user = root.SelectSingleNode("user[@id='1']");
Console.WriteLine(user["name"].InnerText); // Olga

Example: find all users older than 25

XmlNodeList nodes = root.SelectNodes("user[age>25]");
foreach (XmlNode u in nodes)
{
    Console.WriteLine(u["name"].InnerText); // Will print Olga and Igor (if Igor hasn't been deleted)
}

XPath — short syntax

XPath expression What it does
/users/user
All <user> elements under root <users>
user[@id='3']
User with id=3
user[age>25]
Users older than 25
user/name
All <name> elements of all users
user[last()]
The last <user>
user[position()<3]
The first two <user> elements

More examples and syntax: XPath documentation.

Another example: selection by nested elements

Suppose the XML is more complex:

<library>
  <book>
    <title>Lord of the Flies</title>
    <author>
      <firstname>William</firstname>
      <lastname>Golding</lastname>
    </author>
  </book>
  <book>
    <title>Swann's Way</title>
    <author>
      <firstname>Marcel</firstname>
      <lastname>Proust</lastname>
    </author>
  </book>
</library>
XmlDocument doc = new XmlDocument();
doc.LoadXml(libraryXml);
XmlNodeList authors = doc.SelectNodes("/library/book/author/lastname");
foreach (XmlNode lastName in authors)
    Console.WriteLine(lastName.InnerText);

What will be printed:

Golding
Proust

6. XPath: filtering, logic, calculations

Logical filters

Print the names of all users whose age is between 18 and 30:

// [age>=18 and age<=30]
XmlNodeList youngUsers = root.SelectNodes("user[age>=18 and age<=30]");
foreach (XmlNode u in youngUsers)
    Console.WriteLine(u["name"].InnerText);

Working with attributes

Attributes are selected with @. Find users whose id starts with "1":

XmlNodeList nodes = root.SelectNodes("user[starts-with(@id, '1')]");

Counting nodes

You can't directly use the count() function in the C# SelectNodes method — it returns a collection, not a number. But you can do:

int count = root.SelectNodes("user").Count;

Nested filters

XmlNodeList nodes = doc.SelectNodes("/library/book[author/lastname='Golding']");

7. XPath and namespaces

If your XML uses namespaces (xmlns), things get a lot more interesting! For those cases use XmlNamespaceManager:

<catalog xmlns="http://books.example.com">
  <book>
    <title>Algorithms</title>
  </book>
</catalog>
doc.LoadXml(xml);
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("b", "http://books.example.com");

XmlNodeList books = doc.SelectNodes("/b:catalog/b:book", nsmgr);
foreach (XmlNode book in books)
    Console.WriteLine(book.SelectSingleNode("b:title", nsmgr)?.InnerText);

8. Modifying XML with the DOM

Adding nodes "on the fly"

Add a new <email> element to the first user:

XmlElement email = doc.CreateElement("email");
email.InnerText = "olga@gmail.com";
olga.AppendChild(email);

Changing attributes

olga.SetAttribute("id", "99"); // Now id="99"

Removing nodes using XPath

XmlNodeList nodesToRemove = root.SelectNodes("user[age<20]");
foreach (XmlNode node in nodesToRemove)
    root.RemoveChild(node);

9. Useful nuances

Searching and bulk structure changes

Suppose you receive a big XML with various <record type="..."> elements. You need to keep only those whose type attribute equals "customer", and add a child element <status>active</status> to each.

XmlNodeList customerNodes = root.SelectNodes("record[@type='customer']");
foreach (XmlElement record in customerNodes)
{
    XmlElement status = doc.CreateElement("status");
    status.InnerText = "active";
    record.AppendChild(status);
}

XML node tree (DOM)


users
├─ user (id="1")
│   ├─ name ("Olga")
│   └─ age ("28")
├─ user (id="2")
│   ├─ name ("Igor")
│   └─ age ("35")

XPath selection examples

XPath query What it returns
/users/user[1]
The first user (id="1")
/users/user[last()]
The last user (id="2")
/users/user[age>30]
All older than 30 (Igor)
/users/user[@id='2']
User with id="2"

Practical use cases

  • Integrations with "legacy" APIs. In many government and banking systems SOAP/XML still rule. The ability to quickly find and change something in a large XML response can save dozens of hours.
  • Data migration. When moving from one system to another you often need to parse XML and perform complex selections, transformations and bulk updates.
  • Import-export to Excel. In many B2B products input still comes as XML. There XPath is a fast way to get what you need without building a large data model.

10. Errors, nuances and common traps

NullReferenceException: If you try to access a non-existing element, e.g. el["something"].InnerText, and that child element doesn't exist at all. Always check for null.

XPath and context: Remember that an expression without a leading / searches among the descendants of the current node, while with / it starts from the document root. Different reference points can lead to no results even though the data exists.

Namespaces handling: If you don't use XmlNamespaceManager, XPath queries against XML with xmlns will return empty results.

Modifying while iterating: If you want to remove nodes based on the results of SelectNodes, first copy them into an array, then iterate — otherwise the collection will change during iteration and errors may occur.

Difference between text and element nodes: There can be text nodes between elements — spaces and line breaks that XML treats as content. For strict selection use XmlElement only or filter by NodeType.

2
Task
C# SELF, level 48, lesson 2
Locked
Searching for Elements Using XPath
Searching for Elements Using XPath
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION