Fundamentals of Arrays in XPath

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.

Practical LearningPractical Learning: Introducing XPath Arrays

  1. Start Microsoft Visual Studio
  2. Create a Windows Forms App named XPathArrays

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:

using System.Xml;

namespace VideoCollection1
{
    public partial class Exercise : Form
    {
        public Exercise()
        {
            InitializeComponent();
        }

        private void btnVideos_Click(object sender, EventArgs e)
        {
            XmlDocument xdVideos = new XmlDocument();

            xdVideos.Load("../../../Videos.xml");

            XmlElement xeVideo = xdVideos.DocumentElement!;
            XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[1]")!;

            foreach(XmlNode xnVideo in xnlVideos)
            {
                MessageBox.Show(string.Format("First Video\n{0}", 
                                xnVideo.OuterXml),
                                "Video Collection",
                                MessageBoxButtons.OK,
                                MessageBoxIcon.Information);
            }
        }
    }
}

This would produce:

Accessing a Node by its Position

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:

using System.Xml;

namespace VideoCollection1
{
    public partial class Exercise : Form
    {
        public Exercise()
        {
            InitializeComponent();
        }

        private void Exercise_Load(object sender, EventArgs e)
        {
            XmlDocument xdVideos = new XmlDocument();

            xdVideos.Load("../../../Videos.xml");

            XmlElement xeVideo = xdVideos.DocumentElement!;
            XmlNodeList xnlVideos = xeVideo.SelectNodes("//videos/video/cast-members/actor")!;

            foreach (XmlNode xnVideo in xnlVideos)
            {
                lbxAllActors.Items.Add(xnVideo.InnerText);
            }

            xnlVideos = xeVideo.SelectNodes("//videos/video/cast-members/actor[1]")!;

            foreach (XmlNode xnVideo in xnlVideos)
            {
                lbxLeadingActors.Items.Add(xnVideo.InnerText);
            }
        }
    }
}

This would produce the first actor in each section (each child of the root):

Accessing a Node by its Position

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:

using System.Xml;

namespace VideoCollection1
{
    public partial class Exercise : Form
    {
        public Exercise()
        {
            InitializeComponent();
        }

        private void btnVideos_Click(object sender, EventArgs e)
        {
            XmlDocument xdVideos = new XmlDocument();

            xdVideos.Load("../../../Videos.xml");

            XmlElement xeVideo = xdVideos.DocumentElement!;
            XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[2]//cast-members")!;

            foreach (XmlNode xnVideo in xnlVideos)
            {
                MessageBox.Show(xnVideo.OuterXml,
                                "Videos with both Format and Category sections",
                                MessageBoxButtons.OK, MessageBoxIcon.Information);

            }
        }
    }
}

This would produce:

Accessing a Node by its Position

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:

using System.Xml;

namespace VideoCollection1
{
    public partial class Exercise : Form
    {
        public Exercise()
        {
            InitializeComponent();
        }

        private void btnVideos_Click(object sender, EventArgs e)
        {
            XmlDocument xdVideos = new XmlDocument();

            xdVideos.Load("../../../Videos.xml");
  
            XmlElement xeVideo = xdVideos.DocumentElement!;
            XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos[1]/video[3]")!;    

            foreach(XmlNode xnVideo in xnlVideos)
            {
	            MessageBox.Show("Video\n" + xnVideo.OuterXml,
                                "Video Collection",
	                            MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }
    }
}

This would produce:

Accessing the Lineage by Position

In the same way, you can apply the square brackets to any child node of an element. Here is an example:

using System.Xml;

namespace VideoCollection1
{
    public partial class Exercise : Form
    {
        public Exercise()
        {
            InitializeComponent();
        }

        private void btnVideos_Click(object sender, EventArgs e)
        {
            XmlDocument xdVideos = new XmlDocument();

            xdVideos.Load("../../../Videos.xml");

            XmlElement xeVideo = xdVideos.DocumentElement!;
            XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos[1]/video[5]/categories[1]/genre[3]")!;    

            foreach(XmlNode xnVideo in xnlVideos)
            {
	            MessageBox.Show(xnVideo.InnerXml,
                                "Video Collection",
                                MessageBoxButtons.OK, MessageBoxIcon.Information);        
            }
        }
    }
}

This would produce:

Accessing the Lineage by Position

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:

using System.Xml;

namespace VideoCollection1
{
    public partial class Exercise : Form
    {
        public Exercise()
        {
            InitializeComponent();
        }

        private void btnVideos_Click(object sender, EventArgs e)
        {
            XmlDocument xdVideos = new XmlDocument();

            xdVideos.Load("../../../Videos.xml");

            XmlElement xeVideo = xdVideos.DocumentElement!;
            // This expression first gets the 2nd video.
            // Then it gets the 3rd actor of the cast-members section
            XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[3]/cast-members/actor[3]")!;    

            foreach(XmlNode xnVideo in xnlVideos)
            {
                txtActor.Text = xnVideo.InnerText;    
            }
        }
    }
}

This would produce:

Accessing a Node by its Position

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:

using System.Xml; 

namespace VideoCollection1
{
    public partial class Exercise : Form
    {
        public Exercise()
        {
            InitializeComponent();
        }

        private void btnVideos_Click(object sender, EventArgs e)
        {
            XmlDocument xdVideos = new XmlDocument();

            xdVideos.Load("../../../Videos.xml");

            XmlElement xeVideo = xdVideos.DocumentElement!;
            XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos[1]")!;

            foreach(XmlNode xnVideo in xnlVideos)
            {
                MessageBox.Show(xnVideo.InnerXml,
                                "Video Collection",
                                MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }
    }
}

This would produce:

Accessing the Lineage by Position

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:

using System.Xml;

namespace VideoCollection1
{
    public partial class Exercise : Form
    {
        public Exercise()
        {
            InitializeComponent();
        }

        private void btnVideos_Click(object sender, EventArgs e)
        {
            XmlDocument xdVideos = new XmlDocument();

            xdVideos.Load("../../../Videos.xml");

            XmlElement xeVideo = xdVideos.DocumentElement!;
            XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[cast-members]")!;    

            foreach(XmlNode xnVideo in xnlVideos)
            {
                MessageBox.Show(string.Format("Video --------------------------------------------------------------------\n{0}",
                                xnVideo.OuterXml),
                                "Videos that have a cast-members section",
                                MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }
    }
}

This would produce:

Accessing a Node by its Name

Accessing a Node by its Name

Accessing a Node by its Name

Accessing a Node by its Name

Notice that, in our XML document, only some of the videos include the list of actors. If you pass a name that oesn'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:

using System.Xml;

namespace VideoCollection1
{
    public partial class Exercise : Form
    {
        public Exercise()
        {
            InitializeComponent();
        }

        private void btnVideos_Click(object sender, EventArgs e)
        {
            XmlDocument xdVideos = new XmlDocument();

            xdVideos.Load("../../../Videos.xml");

            XmlElement xeVideo = xdVideos.DocumentElement!;
            XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video/cast-members[actor]")!;    

            foreach(XmlNode xnVideo in xnlVideos)
            {
                MessageBox.Show(xnVideo.OuterXml,
                        	    "Videos with both Format and Category sections",
                                MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }
    }
}

This would produce:

Locating a Specific Node by its Name

Locating a Specific Node by its Name

Locating a Specific Node by its Name

Locating a Specific Node by its Name

Locating a Specific Node by its Name

This would produce all cast-members sections of all videos (only the cast-members section). If you want to get a only a specific child node, assign its value to the name in the square brackets. Here is an example:

using System.Xml;

namespace VideoCollection1
{
    public partial class Exercise : Form
    {
        public Exercise()
        {
            InitializeComponent();
        }

        private void btnVideos_Click(object sender, EventArgs e)
        {
            XmlDocument xdVideos = new XmlDocument();

            xdVideos.Load("../../../Videos.xml");

            XmlElement xeVideo = xdVideos.DocumentElement!;
            XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video/cast-members[actor = 'Penelope Ann Miller']")!;    

            foreach(XmlNode xnVideo in xnlVideos)
            {
                MessageBox.Show(xnVideo.OuterXml,
                                "Videos with both Format and Category sections",
                                MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }
    }
}

This would produce:

Locating a Specific Node by its Name

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:

using System.Xml;

namespace VideoCollection1
{
    public partial class Exercise : Form
    {
        public Exercise()
        {
            InitializeComponent();
        }

        private void btnVideos_Click(object sender, EventArgs e)
        {
            XmlDocument xdVideos = new XmlDocument();

            xdVideos.Load("../../../Videos.xml");

            XmlElement xeVideo = xdVideos.DocumentElement!;
            XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[categories]/keywords")!;

            foreach(XmlNode xnVideo in xnlVideos)
            {
                MessageBox.Show(xnVideo.OuterXml,
                                "Videos with both Format and Category sections",
                                MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        {
    }
}

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:

using System.Xml;

namespace VideoCollection1
{
    public partial class Exercise : Form
    {
        public Exercise()
        {
            InitializeComponent();
        }

        private void btnVideos_Click(object sender, EventArgs e)
        {
            XmlDocument xdVideos = new XmlDocument();

            xdVideos.Load("../../../Videos.xml");

            XmlElement xeVideo = xdVideos.DocumentElement!;
            XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video/cast-members/actor[. = 'Eddie Murphy']")!;    

            foreach(XmlNode xnVideo in xnlVideos)
            {
                MessageBox.Show(xnVideo.InnerText,
                                "Videos with both Format and Category sections",
                                MessageBoxButtons.OK, MessageBoxIcon.Information);

            }
        }
    }
}

This would produce:

Accessing the Lineage by Position

Accessing the Nodes With 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 an example:

using System.Xml;

namespace VideoCollection1
{
    public partial class Exercise : Form
    {
        public Exercise()
        {
            InitializeComponent();
        }

        private void btnVideos_Click(object sender, EventArgs e)
        {
            XmlDocument xdVideos = new XmlDocument();

            xdVideos.Load("../../../Videos.xml");

            XmlElement xeVideo = xdVideos.DocumentElement!;
            XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[categories/keywords]")!;    

            foreach(XmlNode xnVideo in xnlVideos)
            {
                MessageBox.Show(xnVideo.OuterXml,
                                "Videos with both Format and Category sections",
                                MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }
    }
}

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 Nodes With Specific Grand-Children

Accessing the Nodes With Specific Grand-Children

Accessing the Grand-Children by Position

Notice that 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:

using System.Xml;

namespace VideoCollection1
{
    public partial class Exercise : Form
    {
        public Exercise()
        {
            InitializeComponent();
        }

        private void btnVideos_Click(object sender, EventArgs e)
        {
            XmlDocument xdVideos = new XmlDocument();

            xdVideos.Load("../../../Videos.xml");

            XmlElement xeVideo = xdVideos.DocumentElement!;
            XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[cast-members][2]")!;    

            foreach(XmlNode xnVideo in xnlVideos)
            {
                MessageBox.Show(xnVideo.OuterXml,
                                "Second video with a cast-members section",
                                MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }
    }
}

This would produce:

Accessing the Grand-Children by Position

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:

using System.Xml;

namespace VideoCollection1
{
    public partial class Exercise : Form
    {
        public Exercise()
        {
            InitializeComponent();
        }

        private void btnVideos_Click(object sender, EventArgs e)
        {
            XmlDocument xdVideos = new XmlDocument();

            xdVideos.Load("../../../Videos.xml");

            XmlElement xeVideo = xdVideos.DocumentElement!;
            XmlNodeList xnlVideos = xeVideo.SelectNodes("/videos/video[categories]/keywords[keyword]")!;

            foreach(XmlNode xnVideo in xnlVideos)
            {
                MessageBox.Show(xnVideo.OuterXml,
                                "Videos with both Format and Category sections",
                                MessageBoxButtons.OK, MessageBoxIcon.Information);
            }
        }
    }
}

This would produce:

Accessing the Grand-Children by Position

Practical LearningPractical Learning: Ending the Lesson


Previous Copyright © 2014-2024, FunctionX Monday 24 June 2024, 13:55 Next