XPath
XPath
XPath Fundamentals
Introduction to XPath
You are probably familiar with how hard it can be to locate an element in an XML file. Fortunately, the MSXML library provides various mechanisms through some classes and their methods. Such techniques are available only if you are using MSXML or the .NET Framework. To provide a common mechanism to locate the elements in an XML document, the W3C created the XPath language.
Introduction to XPath and .NET
Microsoft supports XPath in various ways, as a language in its own right, in the MSXML library, and in the .NET Framework. To support XPath, the .NET Framework provides the System.Xml.XPath namespace that contains various classes. As you should know already, the .NET Framework primarily supports the XML standards through the System.Xml namespace that contains such valuable classes as XmlDocument, XmlElement, XmlNode, and XmlNodeList (and many other important classes). The XmlDocument class gives access to the whole contents of an XML document, starting with the root node, which is represented by the DocumentElement property.
As you may know already, to open an XML file, you can pass its path to the Load() method of the XmlDocument class. Here is an example:
@{ string strVideosFile = Server.MapPath("~/App_Data/Videos.xml"); System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument(); if (System.IO.File.Exists(strVideosFile)) { xdVideos.Load(strVideosFile); } }
If the path is invalid or the file cannot be found, the compiler would throw a FileNotFoundException exception.
Otherwise, that is, if the file could be found, you can use the document as you see fit. For example, you can access its root through the XmlDocument.DocumentElement property:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
}
}
From that root, you can "scan" (or navigate) the XML document for any reason. The XmlDocument.DocumentElement property is of type XmlElement, which is derived from XmlLinkedNode, itself a child of the XmlNode class.
An XPath Expression
Introduction
As you have used operating systems before, you are familiar with the way the address of a file is formulated. Such an address is also referred to as the location. You can pass such a location to a method of a class, for example if you are performing file processing using one of the classes of the System.IO namespace. A file location is considered an expression. Such an expression contains words and operators (such as a period (.), a colon (:), or a slash (\ or /)). In the same way, the XPath language uses expressions to specify a path. Internally, there is a program called the XPath parser, or just the parser. That parser receives the expression and analyzes it. When the parser has finished doing its job, it sends its report to the C# compiler.
To give you the ability to use XPath, the XmlNode class is equipped with a method named SelectNodes. This method is overloaded with two versions. The syntax of one of the versions is:
public XmlNodeList SelectNodes(string xpath);
As you can see, the XmlNode.SelectNodes() method takes an XPath expression passed as a string. This can be illustrated as follows:
If the XmlNode.SelectNodes() method succeeds in what it is supposed to do, it returns a name, a value, a string, or a list of nodes, as an XmlNodeList collection. This can be done as follows:
@{ System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument(); xdVideos.Load(XML File); System.Xml.XmlElement xeVideo = xdVideos.DocumentElement; System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes(XPath Expression); }
Remember that the XmlNodeList class starts as follows:
public abstract class XmlNodeList : IEnumerable, IDisposable
Since XPath is a language, it has rules that an expression must follow. An expression that follows the rules is referred to as well-formed. Because we will use XPath in C#, two rules will apply: first, the rules of the XPath language, followed by the rules of the C# language. The primary rule of XPath is that the expression you formulate must be valid (be well-formed). If the expression doesn't follow the rules or the expression violates a rule (that is, if the expression is not well-formed, if you didn't follow the XPath rules), its parser concludes that the expression is not valid. The parser stops any processing and sends a report to the C# compiler. The C# compiler would not perform any further processing and it would throw an exception named XPathException.
The XPathException exception is defined in the System.Xml.XPath namespace. If you want, you can catch that exception and take appropriate measures. Of course, this class has a Message property.
If the XPath expression is valid, the parser hands the job to another program referred to as an interpreter. Its job is to produce the result requested by the XPath expression. The interpreter starts "scanning" the document that was initiated by the XmlDocument.DocumentElement object. We will see various types of operations that can be requested. For example, you may ask the interpreter to look for a certain element. Another operation may consist of comparing two values. The interpreter checks the document from beginning to end. If it doesn't find any element that responds to the expression, the interpreter sends a message to the C# compiler that no result was found. In this case, the XmlNode.SelectNodes() method returns null.
Starting from the beginning of the document, if the interpreter finds a node that responds to the XPath expression, it adds it to its list of results and continues checking the nodes in the document. When it reaches the end of the document, it gets its final list and sends it to the C# compiler. The compiler stores that list in an XmlNodeList collection and makes it the returned list of the XmlNode.SelectNodes() method. You can then use that list as you see fit:
@{ System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument(); xdVideos.Load(XML file); System.Xml.XmlElement xeVideo = xdVideos.DocumentElement; System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes(XPath expression); // You can now use the XmlNodeList value as you see fit }
Since the XmlNodeList class holds a collection, you can use a loop (while, do...while, for, or foreach) to visit each member of the list. Consider the following XML document whose file is named Videos.xml:
<?xml version="1.0" encoding="utf-8"?> <videos> <video> <title>Her Alibi</title> <director>Bruce Beresford</director> <length>94</length> <format>DVD</format> <rating>PG-13</rating> </video> <video> <title>The War of the Roses</title> <director>Danny DeVito</director> <cast-members> <actor>Michael Douglas</actor> <actor>Kathleen Turner</actor> <actor>Danny DeVito</actor> </cast-members> </video> <video> <title>The Distinguished Gentleman</title> <director>Jonathan Lynn</director> <cast-members> <actor>Eddie Murphy</actor> <actor>Lane Smith</actor> <actor>Sheryl Lee Ralph</actor> <actor>Joe Don Baker</actor> <actor>Victoria Rowell</actor> </cast-members> <cast-members> <actor>Charles S. Dutton</actor> <actor>Grant Shaud</actor> <actor>Kevin McCarthy</actor> <actor>Victor Rivers</actor> <actor>Chi McBride</actor> <actor>Noble Willingham</actor> </cast-members> <length>112</length> <format>DVD</format> <rating>R</rating> <year-released>1992</year-released> <categories> <genre>Comedy</genre> <genre>Politics</genre> <keywords> <keyword>satire</keyword> <keyword>government</keyword> <keyword>con artist</keyword> <keyword>lobbyist</keyword> <keyword>election</keyword> </keywords> </categories> </video> <video> <title>Duplex</title> <director>Danny DeVito</director> <cast-members> <narrator>Danny DeVito</narrator> </cast-members> </video> <video> <title>The Day After Tomorrow</title> <director>Roland Emmerich</director> <length>124</length> <categories> <genre>Drama</genre> <genre>Environment</genre> <genre>Science Fiction</genre> </categories> <format>BD</format> <rating>PG-13</rating> <keywords> <keyword>climate</keyword> <keyword>global warming</keyword> <keyword>disaster</keyword> <keyword>new york</keyword> </keywords> </video> <video> <title>Other People's Money</title> <director>Alan Brunstein</director> <year-released>1991</year-released> <cast-members> <actor>Danny DeVito</actor> <actor>Gregory Peck</actor> <actor>Penelope Ann Miller</actor> </cast-members> <cast-members> <actor>Dean Jones</actor> <actor>Piper Laurie</actor> </cast-members> <categories> <genre>Comedy</genre> <keywords> <keyword>satire</keyword> <keyword>female stocking</keyword> <keyword>seduction</keyword> </keywords> <genre>Business</genre> <keywords> <keyword>capitalism</keyword> <keyword>corporate take-over</keyword> <keyword>factory</keyword> <keyword>speech</keyword> <keyword>public speaking</keyword> </keywords> <genre>Drama</genre> <keywords> <keyword>play</keyword> <keyword>hostile take-over</keyword> <keyword>corporate raider</keyword> </keywords> </categories> </video> </videos>
The Root
As you may know already, every XML document starts with a root node. If you don't know the name of the root (this could happen if you are using an XML file created by someone else and you are not familiar with the document's structure), you can pass the argument to XmlElement.SelectNodes(string xpath) method as /. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/");
}
}
Both the XPath language and the .NET Framework provide various means to present the result of an XPath expression. As we will see in the next sections, the XPath language provides various operators such as the forward slash / to specify the path, or where to start considering the path, to a node. On the other hand, the XmlNode class is equipped with the InnerText, the InnerXml, and the OuterXml properties that produce various results as we will see.
As you should know already, the XmlNode.InnerText property produces the value of an element. The XmlNode.InnerXml property produces the tag (start and end) and the name of an element. The XmlNode.OuterXml property produces a node and its XML format, including the child nodes, if any, of the element. Here is an example that uses the XmlNode.OuterXml property:
<!DOCTYPE html> <html> <head> <title>Video Collection</title> </head> <body> <h1>Video Collection</h1> @{ string strVideosFile = Server.MapPath("~/App_Data/Videos.xml"); System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument(); if (System.IO.File.Exists(strVideosFile)) { xdVideos.Load(strVideosFile); System.Xml.XmlElement xeVideo = xdVideos.DocumentElement; System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/"); foreach (System.Xml.XmlNode xnVideo in xnlVideos) { <p>@xnVideo.OuterXml</p> } } } </body> </html>
This would produce:
An alternative to access the root node is to pass /* as a string to XmlElement.SelectNodes(). Here is an example:
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/*");
One more alternative is to pass the name of the root preceded by /. Here is an example:
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos");
This would produce the same result as above. Notice that the whole document is treated as one object (the result includes only one Result =).
Accessing the Child Nodes of the Root
In an XML document, the root can have 0 or more child nodes. To access the nodes that are direct children of the root, pass the expression as /root-name/child-of-root-name. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
This would produce:
Notice that this time, each child node of the root is considered an item of the resulting collection.
Accessing Other Children
To access a child node whose location is clear, start from the root and list the ancestry, separating the items by /. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video/director");
<ul>
@foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<li>@xnVideo.InnerText</li>
}
</ul>
}
}
This would produce:
You can use this technique to locate a node. You can check the results to find a particular node you are looking for.
Practical Learning: Accessing Child Nodes
using System.IO; using System.Xml; using System.Web.Mvc; namespace WaterDistribution2.Controllers { public class CustomersController : Controller { // GET: Customers public ActionResult Index() { // Get a reference to the XML DOM XmlDocument xdCustomers = new XmlDocument(); // This is the name and path of the XML file that contains the customers records string strFileCustomers = Server.MapPath("/WaterDistribution/Customers.xml"); // If a file that contains the customers records was previously created, ... if (System.IO.File.Exists(strFileCustomers)) { // ... open it using (FileStream fsCustomers = new FileStream(strFileCustomers, FileMode.Open, FileAccess.Read, FileShare.Read)) { // and store the records in the DOM xdCustomers.Load(fsCustomers); } /* If the Customers records exist, send them to the view. * If there is no file for the customers, indicate that the DOM is null. */ ViewBag.customers = xdCustomers.DocumentElement.ChildNodes.Count > 0 ? xdCustomers.DocumentElement.ChildNodes : null; } return View(); } // GET: Customers/Details/5 public ActionResult Details(int id) { XmlDocument xdWaterMeters = new XmlDocument(); string strFileWaterMeters = Server.MapPath("/WaterDistribution/WaterMeters.xml"); if (System.IO.File.Exists(strFileWaterMeters)) { using (FileStream fsWaterMeters = new FileStream(strFileWaterMeters, FileMode.Open, FileAccess.Read, FileShare.Read)) { xdWaterMeters.Load(fsWaterMeters); XmlElement xeWaterMeter = xdWaterMeters.DocumentElement; XmlNodeList xnlWaterMeters = xeWaterMeter.SelectNodes("/water-meters/water-meter/meter-id"); foreach (XmlNode xnWaterMeter in xnlWaterMeters) { if (xnWaterMeter.InnerText == id.ToString()) { ViewBag.WaterMeterID = xnWaterMeter.InnerText; ViewBag.MeterNumber = xnWaterMeter.NextSibling.InnerText; ViewBag.Make = xnWaterMeter.NextSibling.NextSibling.InnerText; ViewBag.Model = xnWaterMeter.NextSibling.NextSibling.NextSibling.InnerText; ViewBag.MeterSize = xnWaterMeter.NextSibling.NextSibling.NextSibling.NextSibling.InnerText; ViewBag.DateLastUpdate = xnWaterMeter.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.InnerText; ViewBag.CounterValue = xnWaterMeter.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.InnerText; } } } } return View(); } // GET: Customers/Create public ActionResult Create() { return View(); } // POST: Customers/Create [HttpPost] public ActionResult Create(FormCollection collection) { try { // TODO: Add insert logic here int meter_id = -1; XmlDocument xdWaterMeters = new XmlDocument(); string strFileWaterMeters = Server.MapPath("/WaterDistribution/WaterMeters.xml"); // Make sure a meter number was provided. If not, don't do nothing if (!string.IsNullOrEmpty(collection["MeterNumber"])) { // If an XML file for water meters was created already, ... if (System.IO.File.Exists(strFileWaterMeters)) { // ... open it ... using (FileStream fsWaterMeters = new FileStream(strFileWaterMeters, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite)) { // ... and put the records in the DOM xdWaterMeters.Load(fsWaterMeters); // We need the meters numbers. Therefore, we will use XPath to specify their path XmlNodeList xnlWaterMeters = xdWaterMeters.DocumentElement.SelectNodes("/water-meters/water-meter/meter-id"); // Check the whole list of meters numbers foreach (XmlNode xnWaterMeter in xnlWaterMeters) { // Every time, assign the current meter number to our meterNumber variable meter_id = int.Parse(xnWaterMeter.InnerText); } } } else { // If there is no XML file yet, create skeleton code for an XML document, ... xdWaterMeters.LoadXml("<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<water-meters></water-meters>"); // and set our meterNumber variable to 0 meter_id = 0; } } // Get ready to create an XML element named water-meter XmlElement xeWaterMeter = xdWaterMeters.CreateElement("water-meter"); // Increase the meterNumber variable by 1 meter_id += 1; // Create the markup of the XML water meter string strWaterMeter = "<meter-id>" + meter_id + "</meter-id>" + "<meter-number>" + collection["MeterNumber"] + "</meter-number>" + "<make>" + collection["Make"] + "</make>" + "<model>" + collection["Model"] + "</model>" + "<meter-size>" + collection["MeterSize"] + "</meter-size>" + "<date-last-update>" + collection["DateLastUpdate"] + "</date-last-update>" + "<counter-value>" + collection["CounterValue"] + "</counter-value>"; // Specify the markup of the new element xeWaterMeter.InnerXml = strWaterMeter; // Add the new node to the root xdWaterMeters.DocumentElement.AppendChild(xeWaterMeter); // Save the (new version of the) XML file using (FileStream fsWaterMeters = new FileStream(strFileWaterMeters, FileMode.Create, FileAccess.Write, FileShare.Write)) { xdWaterMeters.Save(fsWaterMeters); } return RedirectToAction("Index"); } catch { return View(); } } . . . No Change } }
Accessing the Child Nodes of an Element
A child is a node that appears immediately down after a node in the ancestry lineage. To access the child nodes of an element, follow its name with /*. With this operator, if the immediate child node of the element is:
Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("//video");
for (int i = 0; i < xnlVideos.Count; i++)
{
<p>@xnlVideos[i].OuterXml</p>
}
}
}
This would produce:
As stated already, if the last element of the expression you passed includes a child node that itself has child nodes, all those child nodes would be combined and produced as one object. If you want to get the individual nodes, pass their name after that of the element. Here is an example:
@{
int i = 0;
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("//video/categories/*");
<ul>
@while(i < xnlVideos.Count)
{
<li>@xnlVideos[i].InnerText</li>
i++;
}
</ul>
}
}
This would produce:
Practical Learning: Accessing the Child Nodes of an Element
public ActionResult Edit(int id) { XmlDocument xdWaterMeters = new XmlDocument(); string strFileWaterMeters = Server.MapPath("/WaterDistribution/WaterMeters.xml"); if (System.IO.File.Exists(strFileWaterMeters)) { using (FileStream fsWaterMeters = new FileStream(strFileWaterMeters, FileMode.Open, FileAccess.Read, FileShare.Read)) { xdWaterMeters.Load(fsWaterMeters); XmlElement xeWaterMeter = xdWaterMeters.DocumentElement; XmlNodeList xnlWaterMeters = xeWaterMeter.SelectNodes("//meter-id"); foreach (XmlNode xnWaterMeter in xnlWaterMeters) { if (xnWaterMeter.InnerText == id.ToString()) { ViewBag.WaterMeterID = xnWaterMeter.InnerText; ViewBag.MeterNumber = xnWaterMeter.NextSibling.InnerText; ViewBag.Make = xnWaterMeter.NextSibling.NextSibling.InnerText; ViewBag.Model = xnWaterMeter.NextSibling.NextSibling.NextSibling.InnerText; ViewBag.MeterSize = xnWaterMeter.NextSibling.NextSibling.NextSibling.NextSibling.InnerText; ViewBag.DateLastUpdate = xnWaterMeter.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.InnerText; ViewBag.CounterValue = xnWaterMeter.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.NextSibling.InnerText; } } } } return View(); }
Accessing the Grand-Children of a Node
A grand-child is a node that appears down after the child node in the ancestry lineage. To access the grandchildren of a node, separate its name and that of the grand-child name with /*/. Here is an example:
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/*/director");
This example is based on the root and it produces the same result as seen above. Otherwise, if you are starting from another level, make sure you specify it. Here is an example:
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video/*/actor");
Accessing Specific Nodes
If you have many elements that share the same name in your XML document, to access those elements, pass their name preceded by //. Here is an example:
@{
int i = 0;
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("//director");
<ul>
@while(i < xnlVideos.Count)
{
<li>@xnlVideos[i].InnerText</li>
i++;
}
</ul>
}
}
This would produce the same result as above. You can also precede // with a period as follows:
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes(".//director");
Consider the following example:
@{
int i = 0;
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("//cast-members");
<ul>
@while(i < xnlVideos.Count)
{
<li>@xnlVideos[i].OuterXml</li>
i++;
}
</ul>
}
}
This would produce:
Notice that the results in each section, those belonging to the same parent node, are treated as one object. If you pass the common name of the nodes that are at the end of their ancestry, they would be treated individually. Consider the following example:
@{
int i = 0;
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("//actor");
<ul>
@do
{
<li>@xnlVideos[i].InnerText</li>
i++;
} while (i < xnlVideos.Count);
</ul>
}
}
This would produce:
In the same way, you can use the // operator to specify where to start considering a path to a child or grand-child node. Here is an example:
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("//video/*/actor");
In the same way, you can combine operators (separators) to get to a node. For example, to get the child of a node X that itself is a grandchild, simple follow that X node with / and the name of the child. Here is an example:
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("//videos/*/cast-members/actor");
XPath and Arrays
Introduction
If you pass an expression that accesses many nodes on the same level and the expression produces many results, as you know already, the results are stored in an XmlNodeList collection. Based on the XPath standards, those results are stored in an array.
Accessing a Node by its Position
When accessing the results of an XPath expression, each result uses a specific position in the resulting array. The position corresponds to the index of arrays seen in C#, except that the indexes start at 1 (in most C-based arrays, which includes C#, the indexes of arrays start at 0). The first result is positioned at index 1, the second at index 2, until the end.
To access an individual result of the XPath expression, use the square brackets as done in C-based languages. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[1]");
foreach(System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>First Video @xnVideo.OuterXml</p>
}
}
}
This would produce:
The array technique can be very valuable if the results are made of single values, such as the names of people. Consider the following example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("//videos/video/cast-members/actor");
<ul>
@foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<li>@xnVideo.InnerText</li>
}
</ul>
<hr />
xnlVideos = xeVideo.SelectNodes("//videos/video/cast-members/actor[1]");
<ul>
@foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<li>@xnVideo.InnerText</li>
}
</ul>
}
}
This would produce the first actor in each section (each child of the root):
If you pass an index that is not found, such as one that is higher than the number of results, the method would return nothing (no exception would be thrown).
Once you have accessed a node by its position, if that node has at least one child node, you can access that child node using / and its name. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos//video[2]/cast-members");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
This would produce:
Accessing the Lineage by Position
If the element on which you apply the square brackets has child nodes, you can apply the square brackets on the child nodes to get one at a position of your choice. Here is an example:
@{ string strVideosFile = Server.MapPath("~/App_Data/Videos.xml"); System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument(); if (System.IO.File.Exists(strVideosFile)) { xdVideos.Load(strVideosFile); System.Xml.XmlElement xeVideo = xdVideos.DocumentElement; System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos[1]/video[3]"); foreach (System.Xml.XmlNode xnVideo in xnlVideos) { <p>@xnVideo.OuterXml</p> } } }
This would produce:
In the same way, you can apply the square brackets to any child node of an element. Here is an example:
@{ string strVideosFile = Server.MapPath("~/App_Data/Videos.xml"); System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument(); if (System.IO.File.Exists(strVideosFile)) { xdVideos.Load(strVideosFile); System.Xml.XmlElement xeVideo = xdVideos.DocumentElement; System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos[1]/video[4]/categories[1]/genre[3]"); foreach (System.Xml.XmlNode xnVideo in xnlVideos) { <p>@xnVideo.InnerXml</p> } } }
The rules to remember are:
Using these features of arrays applied to XPath, you can access any node. For example, you can apply the square brackets to a certain node and access it by its position. If the node at that position has child nodes, you can use the name of those child nodes and apply the square brackets on that name to indicate what node you want to access. Consider the following example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
// This expression first gets the 2nd video.
// Then it gets the 3rd actor of the cast-members section
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[2]/cast-members/actor[3]");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.InnerText</p>
}
}
}
This would produce:
The First Child Node of the Root
As mentioned already, all the child nodes of an element are stored in an array. In fact, if you pass the name of the root as your XPath expression, this is equivalent to applying [1] to it. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos[1]");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.InnerXml</p>
}
}
}
Accessing a Node by its Name
By using arrays, the XPath language makes it possible to get a collection of nodes based on their common name. To get only the nodes that have a specific section, pass the name of that section to the square brackets of the parent element. From our XML document, imagine you want only the videos that have a section named cast-members. Here is an example of getting them:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[cast-members]");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
This would produce:
Notice that, in our XML document, only some of the videos include the list of actors. If you pass a name that doesn't exist, the interpreter would produce an empty result (no exception would be thrown).
Locating a Specific Node by its Name
Considering the following XPath expression:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video/cast-members[actor]");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
This would produce all cast-members sections of all videos (only the cast-members sections). If you want to get only a specific child node, assign its value to the name in the square brackets. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video/cast-members[actor = 'Penelope Ann Miller']");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
Based on our XML document, the result would produce only the cast-members sections that include Penelope Ann Miller.
Accessing Grand-Children of a Node by its Name
After accessing a node by name, if it has at least one child node, you can access it/them by passing its/their name after the parent and /. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[categories]/keywords");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
Accessing a Node by its Value
If you want to locate a specific child node, in the square brackets, type .= and the value of the node. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video/cast-members/actor[. = 'Eddie Murphy']");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.InnerText</p>
}
}
}
Accessing the Nodes that Have Specific Grand-Children
To get only the parent nodes that have child nodes that in turn have certain child nodes, in the square brackets, type the names of the child node and grand-child separated by /. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[categories/keywords]");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
From our XML file, this would produce only the videos that have a categories section but that categories section must have a keywords section as child, which excludes a video where the categories and their keywords are children on the same level:
Accessing the Grand-Children by Position
Our XML document has some videos that have a cast-members section. Instead of getting all of them, you can ask the interpreter to return only the one at a specific position. This is done by adding a second pair of square brackets and passing the desired index. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[cast-members][2]");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
This would produce:
Accessing the Grand-Children by Name
After getting a child node by name, if that child node has at least one child node and you know the name of that grand-child, pass that name to the square brackets applied after /. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[categories]/keywords[keyword]");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
This would produce:
XPath Functions
Introduction
The XPath language provides many functions. Some functions are made to perform some of the operations we have applied already. Some other functions are meant to produce some results we have not gotten so far.
The Position of a Node
We have seen that we can use the square brackets to get to the position of a node. The XPath language has a function named position that can be used to access a node based on its position. To use it, in the square brackets of the node that is considered the parent, assign the desired position to position().
As you may know already, the first child of a node has an index of 1. Therefore, to get the first child node, assign 1 to the position() function. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[position() = 1]");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
This would produce:
In the same way, to get to any node, assign its index to position().
The Last Child Node
To help you get to the last child node of a node, XPath proposes a function named last. Therefore, to get the last child node, pass last() as the index of the node that acts as the parent. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
// This expression first gets the 2nd video.
// Then it gets the 3rd actor of that video
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[last()]");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
This would produce:
As an alternative, you can assign last() to position(). Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[position() = last()]");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
Consider the following example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video/cast-members[last()]");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
This would produce:
Notice that the result includes all nodes from a parent that has a cast-members section. If you want to get only the last node that has that section, include the whole path in parentheses excluding the square brackets and their index. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("(/videos/video/cast-members)[last()]");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.InnerXml</p>
}
}
}
This would produce:
Boolean Node Searching
AND Both Child Nodes
In our XML document, some parent nodes include some child nodes that are not found in other parent nodes. For example, the first, the second, and the third videos of our XML document have a <format> child node but the fourth video does not. On the other hand, the second, the third, and the fourth videos have a <categories> child node while the first does not. You can ask the XmlNodeList.SelectNodes() method (in fact the XML interpreter) to produce in its results only the parent nodes that have a combination of certain two nodes. To get such a result, add a first combination that includes one of the names of child nodes. Include a second pair of square brackets with the other name. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[format][categories]");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
This would produce:
The "and" operator is used to check that two child nodes are found under a parent node. To use it, create the square brackets and in them, provide the names of two child nodes separated by the and operator. Here is an example:
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("//videos/video[format and rating]");
In this case, only parent nodes that have both child nodes would be produced.
OR Either Child Node
To produce the parent nodes that include one or the other child node, use the or operator. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[cast-members or categories]");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
In this case, only parent nodes that have both child nodes would be produced:
Once you have accessed the nodes, you can apply any concept we have reviewed so far to locate a specific node. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video/cast-members[actor or narrator][. = 'Danny DeVito']");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.InnerText</p>
}
}
}
NOT This Child Node
To let you get only the nodes that do not have a certain child node, the XPath language provides a Boolean function named not. To use it, pass the name of the node to it. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[not(cast-members)]");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
When the not() function returns, it produces only the nodes that don't have the child-node whose name was passed:
Sets Combinations
All the expressions we have seen so far produced only one result each. To let you combine two results into one, the XPath language provides the | operator. It must be applied between two expressions. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("//cast-members[actor='Danny DeVito'] | //cast-members[actor='Eddie Murphy']");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
Boolean Operations
Numeric Comparisons on a Node Value
The XPath language provides some operators that can be used to perform Booleans operations. The numeric comparisons are used to find the Boolean relationship between two values. Only some operators are allowed. If you use an operator that is not valid, the compiler would throw an exception. The valid Boolean operators and the rules are:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("//videos/video/cast-members[actor = 'Sheryl Lee Ralph']");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.InnerXml</p>
}
}
}
Here is an example:
u@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("//videos/video[length >= 100]");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.InnerXml</p>
}
}
}
Logical Conjunction
Logical conjunction consists of combining two Boolean operations that must both produce true results. To create a logical conjunction, create some square brackets applied to the parent name of a node and create the conjunction operation in the square brackets. There are two main ways you can perform the operation:
@{ string strVideosFile = Server.MapPath("~/App_Data/Videos.xml"); System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument(); if (System.IO.File.Exists(strVideosFile)) { xdVideos.Load(strVideosFile); System.Xml.XmlElement xeVideo = xdVideos.DocumentElement; System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[director='Danny DeVito' and actor='Danny DeVito']"); foreach (System.Xml.XmlNode xnVideo in xnlVideos) { <p>@xnVideo.OuterXml</p> } } }By the way, if one the nodes in the square brackets is from a different level, precede its name with //. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[director='Danny DeVito' and //actor='Danny DeVito']");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[director='Danny DeVito' and title='The War of the Roses']");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
In the same way, you can create as many conjunctions as you want, by separating the operations with the and operator.
Logical Disjunctions
Logical disjunction consists of applying the same criterion to two nodes or applying different criteria to the same node so that at least one of the operations needs to be true. To create a logical disjunction, apply the square brackets to the parent node. As seen for the conjunction, there are two main ways to use a logical disjunction:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("//cast-members[actor='Danny DeVito' or actor='Eddie Murphy']");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[director='Danny DeVito' or director='Roland Emmerich']");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
By using the Boolean search operators (and, or, and the not() functions) and the logical conjunction/disjunction operations, you can create tremendous combinations to include and exclude some nodes in the XPath expression and get the desired results.
XPath Axes
XPath Keywords
An axis is a technique of locating a node or a series of nodes using a path. To address this issue, the XPath language provides some keywords that can be used in the expressions. Some keywords are optional and some other keywords are necessary or useful. If you decide to include one of those keywords in your XPath expression, you must use only a valid keyword. If you use a keyword that is not recognized, the compiler will throw an XPathException exception.
When used, an axis keyword is followed by ::, followed by the name of a node or an operator.
Child Nodes
The child keyword is used to indicate that a child node must be accessed. The child keyword is followed by ::. If you want to see all child nodes, follow :: with *. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video/child::*");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.InnerXml</p>
}
}
}
That code would produce all nodes that are direct children of video. If a child node includes its own child nodes, the node and all its children would be considered as one. To get only specific child nodes, follow :: by the name of the child node to access. Here is an example that accesses the director child nodes:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video/child::director");
<ul>
@foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<li>@xnVideo.InnerXml</li>
}
</ul>
}
}
In the same way, you can access any child node by preceding it with the child keyword. In most cases, you can omit the child keyword. For example this:
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes(".//video/*/actor");
Is the same as:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes(".//video/*/child::actor");
<ul>
@foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<li>@xnVideo.InnerText</li>
}
</ul>
}
}
You can also apply any of the rules we have used so far. For example, the following code will produce the child nodes of the second video if the child node is named cast-members:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[2]/child::cast-members");
<ul>
@foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<li>@xnVideo.OuterXml</li>
}
</ul>
}
}
This would produce:
Parent Nodes
The parent keyword is used to get the parent of an element. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("//parent::director");
<ul>
@foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<li>@xnVideo.InnerText</li>
}
</ul>
}
}
The Ancestors of a Node
The ancestors of a node are its parent and grand-parent, up to the root of the XML file. To get the ancestors of a node, precede its name with the ancestor keyword. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("//ancestor::video");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.InnerXml</p>
}
}
}
The Descendants of a Node
The descendants of a node are its child(ren) and grand-child(ren), down to the last grand-child of that node. To get the descendants of a node, precede its name with the descendant keyword.
The Previous Sibling of a Node
The previous sibling of a node is a node that comes before it in the tree while both nodes are on the same level. To get the collection of nodes that come before a certain node, precede its name with the preceding keyword. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("//preceding::categories");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
If your expression indludes the preceding keyword, the result would include the node itself and the next node of the same name in the same tree level. If you want to exclude the node itself from the result, in other words if you want to get only the next sibliing of the same level, use the preceding-sibling keyword.
The Next Sibling a Node
The following keyword is used to get the next node(s) that come(s) after the referenced one but of the same tree level. Consider the following expression:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video/following::director");
<ul>
@foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<li>@xnVideo.InnerXml</li>
}
</ul>
}
}
This code would access the director nodes, excluding the first one.
XPath and XML Attributes
Introduction
As you should know already, an attribute is a type of element included in the start tag of a node. Here are examples of attributes in an XML document of a file named Videos.xml:
<?xml version="1.0" encoding="utf-8"?> <videos common-name="Video Collection" purpose="To keep track of personal/home videos"> <video shelf-number="CMD97904"> <title>Her Alibi</title> <director>Bruce Beresford</director> <length>94</length> <format>DVD</format> <rating>PG-13</rating> </video> <video shelf-number="PLT24857"> <title France="Monsieur le Député" Italy="Il Distinto Gentiluomo" Spain="Su Distinguida Señoría">The Distinguished Gentleman</title> <director>Jonathan Lynn</director> <cast-members> <actor role="Thomas Jefferson Johnson">Eddie Murphy</actor> <actor role="Dick Dodge">Lane Smith</actor> <actor role="Miss Loretta">Sheryl Lee Ralph</actor> <actor role="Olaf Andersen">Joe Don Baker</actor> <actor role="Celia Kirby">Victoria Rowell</actor> </cast-members> <cast-members> <actor role="Elijah Hawkins">Charles S. Dutton</actor> <actor role="Arthur Reinhardt">Grant Shaud</actor> <actor role="Terry Corrigan">Kevin McCarthy</actor> <actor role="Armando">Victor Rivers</actor> <actor role="Homer">Chi McBride</actor> <actor role="Zeke Bridges">Noble Willingham</actor> </cast-members> <length>112</length> <format screen="Wide Screen">DVD</format> <rating>R</rating> <year-released date-released="4 December 1992">1992</year-released> <categories> <genre>Comedy</genre> <genre>Politics</genre> <keywords> <keyword>satire</keyword> <keyword>government</keyword> <keyword>con artist</keyword> <keyword>lobbyist</keyword> <keyword>election</keyword> </keywords> </categories> </video> <video> <title>Duplex</title> <director>Danny DeVito</director> <cast-members> <narrator>Danny DeVito</narrator> </cast-members> </video> <video shelf-number="SCF13948"> <title Spain="El Día de Mañana" Croatia="Dan Poslije Sutra" Portugal="O Dia Depois de Amanhã">The Day After Tomorrow</title> <director>Roland Emmerich</director> <length>124</length> <categories> <genre>Drama</genre> <genre>Environment</genre> <genre>Science Fiction</genre> </categories> <format>BD</format> <rating>PG-13</rating> <keywords> <keyword>climate</keyword> <keyword>global warming</keyword> <keyword>disaster</keyword> <keyword>new york</keyword> </keywords> </video> <video shelf-number="CMD93805"> <title>Other People's Money</title> <director>Alan Brunstein</director> <year-released date-released="18 October 1991">1991</year-released> <cast-members> <actor role="Lawrence Garfield">Danny DeVito</actor> <actor role="Andrew Jorgenson">Gregory Peck</actor> <actor role="Kate Sullivan">Penelope Ann Miller</actor> </cast-members> <cast-members> <actor role="Bill Coles">Dean Jones</actor> <actor role="Bea Sullivan">Piper Laurie</actor> </cast-members> <categories> <genre>Comedy</genre> <keywords> <keyword>satire</keyword> <keyword>female stocking</keyword> <keyword>seduction</keyword> </keywords> <genre>Business</genre> <keywords> <keyword>capitalism</keyword> <keyword>corporate take-over</keyword> <keyword>factory</keyword> <keyword>speech</keyword> <keyword>public speaking</keyword> </keywords> <genre>Drama</genre> <keywords> <keyword>play</keyword> <keyword>hostile take-over</keyword> <keyword>corporate raider</keyword> </keywords> </categories> </video> </videos>
To access an attribute, follow the name of its parent tag with a forward slash, an @ sign, and the name of the attribute. For example, if the root node has an attribute, use the formula:
/RootName/@AttributeName");
Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/@common-name");
foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<p>@xnVideo.OuterXml</p>
}
}
}
In the same way, use any of the formulas we have seen so far to access any node. Then, if the node has an attribute, use the @ sign to get that attribute. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video/@shelf-number");
<ul>
@foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<li>@xnVideo.OuterXml</li>
}
</ul>
}
}
If you use an attribute that either doesn't exist or the element doesn't have that particular attribute, the result would be empty; no exception would be thrown.
Getting all Attributes of an Element
If you want to get all the attributes of an element, replace the name of the attribute with * as in @*. Here is an example:
@{ string strVideosFile = Server.MapPath("~/App_Data/Videos.xml"); System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument(); if (System.IO.File.Exists(strVideosFile)) { xdVideos.Load(strVideosFile); System.Xml.XmlElement xeVideo = xdVideos.DocumentElement; System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video/title/@*"); <ul> @foreach (System.Xml.XmlNode xnVideo in xnlVideos) { <li>@xnVideo.OuterXml</li> } </ul> } }
Getting the Value of an Attribute
As we have seen so far, both the XPath language and the .NET Framework provide various means to access an attribute. For example, the XPath language uses the @ sign to access an XML attribute while the XmlNode class is equipped with the OuterXml, the InnerXml, and the InnerText properties. Here is an example that uses the XmlNode.InnerXml property to access an attribute:
@{ string strVideosFile = Server.MapPath("~/App_Data/Videos.xml"); System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument(); if (System.IO.File.Exists(strVideosFile)) { xdVideos.Load(strVideosFile); System.Xml.XmlElement xeVideo = xdVideos.DocumentElement; System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video/cast-members/actor/@role"); <ul> @foreach (System.Xml.XmlNode xnVideo in xnlVideos) { <li>@xnVideo.InnerXml</li> } </ul> } }
Remember that, to get all attributes, you can use @* as in:
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement; System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video/cast-members/actor/@*"); foreach(System.Xml.XmlNode xnVideo in xnlVideos){} lbxActorsRoles.Items.Add(xnVideo.InnerXml);
Notice that the XmlNode.InnerXml property applied to an attribute produces only the text of the attribute. In the same way, you can use the XmlNode.InnerText property to get the same result. On the other hand, the XmlNode.OuterXml property produces the name and value of an attribute.
Getting an Attribute and its Parent
In some cases, you may want to get both the attribute and its parent. To do that, after the name of the element, include the @ and name of the attribute in square brackets. Here is an example:
@{
string strVideosFile = Server.MapPath("~/App_Data/Videos.xml");
System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument();
if (System.IO.File.Exists(strVideosFile))
{
xdVideos.Load(strVideosFile);
System.Xml.XmlElement xeVideo = xdVideos.DocumentElement;
System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video/cast-members/actor[@role]");
<ul>
@foreach (System.Xml.XmlNode xnVideo in xnlVideos)
{
<li>@xnVideo.OuterXml</li>
}
</ul>
}
}
If you want to get all attributes and their parent, remember that you can use * in place of the name of the attribute. Here is an example:
@{ string strVideosFile = Server.MapPath("~/App_Data/Videos.xml"); System.Xml.XmlDocument xdVideos = new System.Xml.XmlDocument(); if (System.IO.File.Exists(strVideosFile)) { xdVideos.Load(strVideosFile); System.Xml.XmlElement xeVideo = xdVideos.DocumentElement; System.Xml.XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video/title[@*]"); foreach (System.Xml.XmlNode xnVideo in xnlVideos) { <p>@xnVideo.OuterXml</p> } } }
|
||
Previous | Copyright © 2014-2019, FunctionX, Inc. | Home |
|