Download as pdf or txt
Download as pdf or txt
You are on page 1of 6

INFO-3138 Programming with Declarative Languages

Tutorial 9: XPath in C#
Purpose
In previous lessons we’ve examined XPath in isolation to be able to focus on the syntax of this
tool without getting distracted by context. As discussed earlier however, XPath isn’t particularly
useful on its own. In this tutorial we’ll look at integrating XPath into a simple C# console
program that selects different sets of food elements from Nutrition.xml.

Background and Overview of the Example


In C# you can run XPath expressions using an object of the System.Xml.XmlDocument class
which implements the DOM. It includes the SelectNodes() and SelectSingleNode() methods
which accept an XPath expression and return one or more matching nodes. However, a more
powerful solution is the System.Xml.XPath.XPathNavigator. This class has a method called
Select() to run regular queries that return node sets as well as an Evaluate() method that will
run expressions which return aggregate values like counts, sums or averages. We’ll be trying-
out both classes (XmlDocument and XPathNavigator) in this example.
Here are the key steps we’ll use to create the application:
1. Load the data in Nutrition.xml into the DOM using an object of type XmlDocument. This
is necessary because we must first have an XmlDocument that references all the data in
the DOM before we can used any of the XPath techniques in this example.
2. Use the XmlDocument object’s SelectNodes() method and the XPath expression
“//daily-values/*” to retrieve all the “nutritional substances” reported in the XML file
(total-fat, saturated-fat, cholesterol, sodium, carb, fiber and protein).
3. Display a menu to let the user choose between selecting all foods in the XML file or
some foods based on a user-selected criterion.
4. If the user chooses all foods then we’ll use the XPath expression “//food/name” (note
that this expression doesn’t include a predicate) and an XPathNavigator object’s Select()
method to select the name elements for all the foods in the file.
5. If the user chooses some foods then we’ll:
a. Prompt the user to select one of the nutrition substances, an operator and a
value and we’ll use these selections to create an XPath expression that includes a
predicate. For example, if the user selects “sodium”, “more than” and “100”
we’ll build the XPath expression “//food[sodium >100]/name”.
b. Use the constructed XPath expression and an XPathNavigator object’s Select()
method to retrieve the names of all foods that match the selected predicate.
We’ll also:
c. Construct another XPath expression to return the average amount of the
selected nutritional substance in all the selected foods and we’ll use the

Tutorial on XPath in C# Page 1 S2023


INFO-3138 Programming with Declarative Languages

XPathNavigator’s Evaluate() method to run this expression. For example,


“sum(//food[sodium>100]/sodium) div count(//food[sodium>100])”.
You should try the various XPath expressions noted in the above overview using
codebeautify.org/Xpath-Tester# to verify that they return the expected results!

Code the Example


Download and extract the archive XPath and C Sharp Example - Starting Code then open the
extracted solution in Visual Studio. Note that the solution is partly completed and already loads
the file Nutrition.xml into the DOM and attempts to obtain any required user inputs. Read
through the Main() method at the top of the Program class. The five steps outline in the
Background and Overview section above are indicated in the comments. Some of these steps
are incomplete; they call methods that haven’t been fully implemented. It should build and run
but won’t yet report any foods in the output. What’s still needed is all the bits involving XPath.
We’ll add those now, step-by-step!
Retrieve all the “nutritional substances”:
This is step 2 from the Background and Overview section and must be implemented by
completing the GetParameters() helper method. To complete this method, add the following
code to the method body:
// Obtain all child elements of daily-values
XmlNodeList nodes = doc.SelectNodes("//daily-values/*")!;

// For each child element returned ...


foreach (XmlElement paramElem in nodes)
{
// Append the element name to _parameters
_parameters.Add(paramElem.Name);
// Append the value of the element's "units" attribute to _parameterUnits
_parameterUnits.Add(paramElem.GetAttribute("units"));
}

Note that doc is references a populated instance of XmlDocument (the DOM). We’re using the
SelectNodes() method of this class to run the XPath expression “//daily-values/*”. This returns
an XmlNodeList collection containing all matching nodes. These are the child elements of the
daily-values element. Two other member variables of the Program class, _parameters and
_parameterUnits, are used to store the names of the elements (the nutritional substances) and
the unit of measurement used for each substance. Notice that in the foreach hear the current
node in the node list (nodes[i]) is cast to an XmlElement interface so that the GetAttributes()
method can be used to retrieve the value of the current element’s “units” attribute.
If you rebuild and run the example after completing this code there shouldn’t be any change in
the output. However, if you run it through the debugger you can use a watch to verify that the

Tutorial on XPath in C# Page 2 S2023


INFO-3138 Programming with Declarative Languages

_parameters and _parameterUnits lists are populated in Main() after the call to
GetParameters().
Retrieve and display all the food names:
This is step 4 outlined in the Background and Overview section. Since we’ll be using an
XPathNavigator object to perform the XPath query, we’ll facilitate this by adding the following
using statement with the others at the top of the Program module:
using System.Xml.XPath;

This namespace isn’t required to run an XPath expression via the XmlDocument object.
Now complete the DisplayAll() helper method by adding the following code to the end of the
method body:
XPathNavigator nav = doc.CreateNavigator()!;
XPathNodeIterator nodeIt = nav.Select("//food/name");
while (nodeIt.MoveNext() && nodeIt.Current is not null)
Console.WriteLine($"\t{nodeIt.Current.Value}");

The XPathNavigator object must be created from the XmlDocument (DOM) object that contains
the data. This is done using the factory method CreateNavigator(). The navigator’s Select()
method will run an regular XPath expression that returns a node set. The node set is returned in
the form of an XPathNodeIterator. This is a “forward-only” iterator that won’t point to the first
item until MoveNext() is called on the iterator. MoveNext() will move to the next node in the
node set every time it’s called or will return false if there are no more nodes. The Current
property of the iterator returns another XPathNavigator object that references the current
node in the node set. This XPathNavigator object includes properties and methods for
accessing as well as for editing the current node. In the code above we’ve used the Value
property to access the text content of the current name element.
If you rebuild and run the program now, then select the option A from the main menu you
should get the following output:
All Foods (no predicate used)...

Avocado Dip
Bagels, New York Style
Beef Frankfurter, Quarter Pound
Chicken Pot Pie
Cole Slaw
Eggs
Hazelnut Spread
Potato Chips
Soy Patties, Grilled
Truffles, Dark Chocolate

Tutorial on XPath in C# Page 3 S2023


INFO-3138 Programming with Declarative Languages

Retrieve some food names based on the user’s criterion:


This part of the example relates to steps 5a and 5b outlined in the Background and Overview
section. It involves a greater quantity of code because we can’t just hard-code an XPath
expression. Instead we have to piece it together based on user inputs. The expression will have
the general form //food[criterion]/name where criterion will get replaced with a predicate
expression such as sodium>100 or protein<=8. Much of the expression-building work is already
completed at the top of the DisplaySome() helper method. The existing code obtains user
inputs and initializes the following local variables:

• param – the name of the nutritional substance to use in the predicate


• unit – the unit of measurement associated with the substance for use in the output
• op – the operator to be used in the predicate
• opText – a worded version of the operator for displaying a meaningful menu to the user
• limit – a numeric value to use in the predicate
So if the user wants to select all foods that contain less than 100 mg of sodium (sodium<100),
then param should be “sodium”, op should be “<” and limit should be 100.
To start with, modify the DisplaySome() helper method by adding the following code to the end
of the method body:
// Build main XPath query
string query1 = $"//food[{param + op + limit}]/name";

This builds the XPath query, inserting the predicate based on user inputs, and assigns it to the
string variable query1.
Beneath this add the following code:
// Execute the main query and report the results
XPathNavigator nav = doc.CreateNavigator()!;
XPathNodeIterator nodeIt = nav.Select(query1);

if (nodeIt.Count > 0)
{
while (nodeIt.MoveNext() && nodeIt.Current is not null)
Console.WriteLine($"\t{nodeIt.Current.Value}");
}
else
Console.WriteLine("\tNo foods match that criterion.");

This code is similar to what we used in DisplayAll(). We added the if statement so that if the
query returns an empty node set then there will be special output to explain this to the user.
If you rebuild and run the program now, then select the option S from the main menu and
enter the inputs shown below you should get the same output:

Tutorial on XPath in C# Page 4 S2023


INFO-3138 Programming with Declarative Languages

First create a criterion like 'Foods containing exactly 100 g of sodium'.

1. total-fat
2. saturated-fat
3. cholesterol
4. sodium
5. carb
6. fiber
7. protein

Select one substance from the list above by entering its number: 4

1. exactly
2. less than
3. no more than
4. more than
5. no less than

Select one operator from the list above by entering its number: 2

Enter sodium amount in mg: 100

Foods containing less than 100 mg of sodium (predicate used)...

Cole Slaw
Eggs
Hazelnut Spread
Truffles, Dark Chocolate

Retrieve the average amount of the nutritional substance in the selected foods:
This part of the example represents step 5c outlined in the Background and Overview section.
We’re including it to illustrate how XPath aggregate expressions may be executed using an
XPathNavigator object. In this case the aggregate expression will compute the average about of
the selected nutritional substance in the foods that were retrieved in the previous section.
In the DisplaySome() helper method, add the following code to the bottom of the if branch of
the if – else statement:
// Build aggregate XPath query to get the average amount of the substance
// in the foods
string query2 = "sum(//food[" + param + op + limit + "]/" + param + ")"
+ " div " + "count(//food[" + param + op + limit + "])";

// Execute the aggregate query and report the result


Console.WriteLine($"\n\tAverage amount of {param} in these foods is {nav.Evaluate(
query2)} {unit}.");

Again, we had to build a suitable XPath expression from the param, op and limit variables.
XPath’s sum() function is used to add-up the amount of the nutritional substance in all the
selected foods that satisfy the predicate. XPath’s count function is used to determine how
many foods there are in this node set. XPath’s div operator divides the sum by the count to

Tutorial on XPath in C# Page 5 S2023


INFO-3138 Programming with Declarative Languages

return the average. Note that the XPathNavigator’s Evaluate() method is used here to execute
the XPath expression since it returns an aggregate value rather than a node set. This code was
added inside the if statement so that no average will be calculated if there are no foods to be
averaged which would cause a divide-by-zero error.
If you again rebuild and run the program, providing all the same user inputs you should have
following additional line of output at the bottom:

Average amount of sodium in these foods is 27.5 mg.

Now try the practice exercise accompanying this tutorial.

Tutorial on XPath in C# Page 6 S2023

You might also like