C#, XML, and LINQ: Deleting an XML Node

So far we've learned how to load & parse an XML file, add new nodes to it, and update it's nodes. Today we're gonna learn the scary part: deleting nodes.

The XML File

First, we need a data file to parse. For the sake of consistency, we'll use the same data file from the previous tutorials:

<?xml version="1.0" encoding="utf-8"?>
<record>
  <student>
    <id>2</id>
    <firstname>Buddy</firstname>
    <lastname>Lee</lastname>
    <major>SENG</major>
  </student>
  <student>
    <id>1</id>
    <firstname>Forrest</firstname>
    <lastname>Gump</lastname>
    <major>SENG</major>
  </student>
</record>

Simplistic, I know, but this file serves our purpose.

The Code

And now we will explore the wonderful code I used to delete nodes. Look this over, and then I'll explain what's happening.

   1:  /// <summary>
   2:  /// Remove a student from the student data file
   3:  /// </summary>
   4:  /// <param name="id">Student's ID</param>
   5:  /// <returns>true if node removed, false if node was not removed</returns>
   6:  public static bool RemoveStudent(string id)
   7:  {
   8:      if (id != string.Empty)
   9:      {
  10:          try
  11:          {
  12:              XDocument xd = XDocument.Load( "students.xml" );
  13:              xd.Element("record").Elements("student").Where(x => x.Element("id").Value.Trim() == id).Remove();
  14:   
  15:              xd.Save( "students.xml" );
  16:   
  17:              return true;
  18:          }
  19:          catch (Exception ex)
  20:          {
  21:              throw (
  22:                      new Exception(String.Format("An error occurred. The student could not be removed. Details of this error:\n\n{0}", ex))
  23:                  );
  24:          }
  25:      }
  26:      else
  27:      {
  28:          throw (new Exception("ID cannot be negative or null."));
  29:      }
  30:  }

This method gets an ID passed in that should correspond to an ID in the data file. We are again using the awesome LINQ lambda method syntax to search for a "student" node whose "id" element has a value matching the ID that was passed into the method.

xd.Element("record").Elements("student").Where(x => x.Element("id").Value.Trim() == id).Remove();

The Remove() method removes all nodes from the parent node (in our case, "record") that are specified by whatever preceded it. In our case, it's deleting the node (or nodes) that match the Where() clause we specified.

It's worth noting that the code I present here will delete multiple nodes if they happen to have the same value for their "id" element. I did not code against that simply because the code I use to write to the data file checks to see if an ID exists already and will not add the node if the ID exists. This basically mimics the "primary key" attribute of a RMDBMS system. If your system does allow the ID element to have duplicates then you will want to add a limit clause such as First() or FirstOrDefault() after the Where() clause.

This method could also be extended by changing the parameter from a single string to a params array. You would then add a loop around the Remove() method call to loop until each of the passed-in IDs have been processed, like so:

   1: /// <summary>
   2: /// Remove a series of students from the student data file
   3: /// </summary>
   4: /// <param name="id_list">Student IDs</param>
   5: /// <returns>true if node(s) removed, false if node(s) was/were not removed</returns>
   6: public static bool RemoveStudent( params string[] id_list )
   7: {
   8:       if ( id_list.Count() > 0 )
   9:       {
  10:           try
  11:           {
  12:               XDocument xd = XDocument.Load( "students.xml" );
  13:               foreach ( string id in id_list )
  14:               {
  15:                   xd.Element( "record" ).Elements( "student" ).Where( x => x.Element( "id" ).Value.Trim() == id ).Remove();
  16:               }
  17:   
  18:               xd.Save( "students.xml" );
  19:   
  20:               return true;
  21:           }
  22:           catch ( Exception ex )
  23:           {
  24:               throw (
  25:                       new Exception( String.Format( "An error occurred. The student could not be removed. Details of this error:\n\n{0}", ex ) )
  26:                   );
  27:           }
  28:       }
  29:       else
  30:       {
  31:           throw ( new Exception( "ID cannot be negative or null." ) );
  32:       }
  33: }

Questions or comments? You know what to do... :]

Comments (0)

There are no comments for this entry.

C#, XML, and LINQ: Updating an XML File

So far, I have shown you how to load and parse an XML document into a C# class, and also how to add new nodes to an XML document using C#. So now we have reached the fun part: updating an existing node!

The XML File

We're going to use our same trusty XML file for this tutorial:

<?xml version="1.0" encoding="utf-8"?>
<record>
  <student>
    <id>2</id>
    <firstname>Buddy</firstname>
    <lastname>Lee</lastname>
    <major>SENG</major>
  </student>
  <student>
    <id>1</id>
    <firstname>Forrest</firstname>
    <lastname>Gump</lastname>
    <major>SENG</major>
  </student>
</record>

Next we take a look at the code we need to modify this file.

The C# Class

As before, this code should look fairly familiar. If you've followed along so far (and I've made any sense), you should be able to recognize the LINQ code in this sample:

   1:  /// <summary>
   2:  /// Updates a Student node with new values
   3:  /// </summary>
   4:  /// <param name="id">The student&#39;s unique identification number</param>
   5:  /// <param name="firstname">The student&#39;s first name</param>
   6:  /// <param name="lastname">The student&#39;s last name</param>
   7:  /// <returns>true if update succeeded; false otherwise</returns>
   8:  public static bool UpdateStudent(string id, string firstname, string lastname)
   9:  {
  10:      if (firstname != string.Empty && lastname != string.Empty)
  11:      {
  12:          XDocument xd = XDocument.Load("students.xml");
  13:          XElement node = xd.Element("record").Elements("student").FirstOrDefault(x => x.Element("id").Value.Trim() == id);
  14:   
  15:          if (node != null)
  16:          {
  17:              node.SetElementValue("firstname", firstname);
  18:              node.SetElementValue("lastname", lastname);
  19:   
  20:              xd.Save(str);
  21:              return true;
  22:          }
  23:          return false;
  24:      }
  25:      return false;
  26:  }

Looks pretty simple, doesn't it? Well, I hate to break it to you masochists out there, but it really is that easy with LINQ.

We use some lambda syntax to search for a node identified by the id value we passed in. Then we check to see if the node object we created is null. If it is, we know we did not find a node in the XML document that has the specified id, since FirstOrDefault() returns NULL in this instance if a node is not found that matches the lambda expression. If the object is not null, we assume we have the correct node. We then use a method called SetElementValue() to set the new values of the firstname and lastname attribute nodes of this node. All that's left to do then is call the Save() method on the XDocument parent, and we're done. We return true to let the calling code know that the save operation was successful.

The conditional I placed around the actual save code is more or less optional. In my case, I did not want to let a user change a student's first and last name values to null strings, so I added the conditional to short-circuit the method and return false if the user attempted to pass in some null strings for either attribute. You could remove this conditional if your datasource doesn't require each attribute to have a value.

Comments (0)

There are no comments for this entry.

C#, XML, and LINQ: Adding Nodes to an XML File

My previous example only covered loading and parsing an XML document. This example will include the code I used to add new nodes to the XML tree.

The XML File

For the sake of simplicity, and to be a little more consistent, we will use the same XML file from Sample 1:

<?xml version="1.0" encoding="utf-8"?>
<record>
  <student>
    <id>2</id>
    <firstname>Buddy</firstname>
    <lastname>Lee</lastname>
    <major>SENG</major>
  </student>
  <student>
    <id>1</id>
    <firstname>Forrest</firstname>
    <lastname>Gump</lastname>
    <major>SENG</major>
  </student>
</record>

Classy fellows, these two... :)

The C# Class

If you read the previous sample, the code that follows should look pretty familiar. That would be because the majority of the code is the same. We are also using the Student class previously defined in Sample 1.

Let's start off by looking at the code to add a student to the XML tree:

/// <summary>
/// Add a new Student node to students file.
/// </summary>
/// <param name="student">A Student object</param>
/// <returns>true if node added, false if node was not added</returns>
public static bool AddStudent( Student student )
{
      if ( student != null )
      {
          try
          {
              XDocument xd = XDocument.Load( "students.xml" );
              xd.Element( "record" ).Add(
                  new XElement( "student",
                      new XElement( "id", student.ID ),
                      new XElement( "firstname", student.FirstName ),
                      new XElement( "lastname", student.LastName ),
                      new XElement( "major", student.MajorID )
                      )
                  );
 
              xd.Save( "students.xml" );
 
              return true;
          }
          catch ( Exception ex )
          {
              throw (
                      new Exception(
                          String.Format( "An error occurred. The student could not be saved. Details of this error:\n\n{0}", ex.Message )
                          )
                     );
          }
      }
      else
      {
          throw(new Exception("Cannot add a null student to the datasource"));
      }
}

First, we load the XML document into an XDocument object. This is a LINQ class that gives us a lot of helpful tools for parsing and manipulating XML. If you wanted to, you could use C#'s string parsing tools instead, but it's going to be a lot more work (unless you live and breathe regex, in which case, I tip my hat to you, sir).

Once we have an XDocument object, we can start pushing in new nodes. If you look at the code above, you will see that I am using the Element() method to find the root node, which our XML file says is "record." The next part of that statement is the Add() method. This is being called from the XElement object returned from the Element() method. We can stack these method calls (like I did here), or you could assign the result of xd.Element("record") to an XElement object and then call it's Add() method. I prefer the former because it looks cleaner to me, but feel free to use the latter if it makes more sense to you.

The next part of this code is where the fun begins. The Add() method expects an XElement object as an argument. We could create an XElement object and then pass it to the Add() method, but it's equally valid to create the XElement object AS the argument (which is what I did here). The benefit to the latter method is that the new object immediately goes out of scope once the method call finishes, so it's fairly likely the garbage collector will delete it sooner rather than later, since the chance of any "hanging" references is pretty much nil.

Since we are trying to construct a semantically correct XML file, each data value will be written to a node. It is technically valid to embed the values as attributes of a "student" node, but I really hate that method, so I will never cover how you would use attributes. If that bothers anyone, sorry, dem's the breaks.

I already mentioned that the root node of this XML tree is "record." Each data node is called "student" and has attributes nodes for "id," "firstname," "lastname," and "major." The attribute nodes are children of the "student" node. The code I've written to generate this new "student" node reflects this structure. First, we create a new Element and name it "student." When we add a comma and add a new Element, LINQ knows we are adding that element as a child of the element we just defined.

Note where the closing parentheses are. Since we want the attribute nodes to be on the same level, we close each attribute node's constructor call. Once we have defined all of the attribute nodes, we close the data node's constructor call, and then close the Add() method call. To help me keep track of my parentheses, I put each open and close parentheses block on another line, which (like the code block curly braces) helps me make sure I have equal numbers of opening and closing parentheses.

The last thing to do is call the Save() method on the XDocument element. This will write the new node out the file specified, which in our case is the same document we read from.

And you're done!

A Note

It is worth noting the try-catch blocks I have around this bit of code. These are useful for gracefully handling any exceptions this code may generate. For instance, this code will fail if the XML file could not be read. It will also cause an exception if the file is read-only.

Comments (0)

There are no comments for this entry.

C#, XML, and LINQ: Load & Parse an XML File

I promised an example of using LINQ and C# together before the month was out, and it here it is! Whoo. Go me.

Anyway...

In this example, I will provide some sample code of how to use LINQ to parse an existing XML file into a sample class I created in C#. As usual, the code used in this example can be downloaded below.

The XML File

First, we need a data file to parse. I recently had to make an academic audit application, so I just happen to have an XML file for compiling a list of student data lying around:

<?xml version="1.0" encoding="utf-8"?>
<record>
  <student>
    <id>2</id>
    <firstname>Buddy</firstname>
    <lastname>Lee</lastname>
    <major>SENG</major>
  </student>
  <student>
    <id>1</id>
    <firstname>Forrest</firstname>
    <lastname>Gump</lastname>
    <major>SENG</major>
  </student>
</record>

Obviously, these are fictitious students, so don't get any bright ideas, people.

The C# Class

This is the class I built to model students within my academic audit application:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace SENG._301.Final.Data
{
    /// <summary>
    /// An object for storing student information; implements ICloneable and IComparable
    /// </summary>
    public partial class Student : ICloneable, IComparable
    {
        #region Data Members
 
        private string id, firstName, lastName, majorID;
        private Major _major = new Major();
 
        /// <summary>
        /// Student&#39;s major (as a string ID value)
        /// </summary>
        public string MajorID
        {
            get { return majorID; }
            set
            {
                if (value.Length > 0)
                {
                    majorID = value;
                }
                else
                {
                    throw (new Exception("Major ID must contain a value."));
                }
            }
        }
 
        /// <summary>
        /// Student&#39;s unique identifier
        /// </summary>
        public string ID
        {
            get { return id; }
            set
            {
                if (value.Length > 0)
                {
                    id = value;
                }
                else
                {
                    throw (new Exception("ID cannot be empty."));
                }
            }
        }
 
        /// <summary>
        /// Student&#39;s first name
        /// </summary>
        public string FirstName
        {
            get { return firstName; }
            set
            {
                if (value.Length > 0)
                {
                    firstName = value;
                }
                else
                {
                    throw (new Exception("First name must contain a value."));
                }
            }
        }
 
        /// <summary>
        /// Student&#39;s last name
        /// </summary>
        public string LastName
        {
            get { return lastName; }
            set
            {
                if (value.Length > 0)
                {
                    lastName = value;
                }
                else
                {
                    throw (new Exception("Last name must contain a value."));
                }
            }
        }
 
        /// <summary>
        /// Major property; returns or accepts a Major object
        /// </summary>
        public Major _Major
        {
            get { return _major; }
            set
            {
                if (value != null)
                {
                    _major = value;
                }
                else
                {
                    throw (new Exception("Major value cannot be null."));
                }
            }
        }
 
        #endregion
 
        #region Constructors
 
        /// <summary>
        /// Default constructor
        /// </summary>
        public Student()
        {
            this.id = string.Empty;
            this.firstName = string.Empty;
            this.lastName = string.Empty;
            this.majorID = string.Empty;
        }
 
        /// <summary>
        /// Overloaded constructor
        /// </summary>
        /// <param name="id">A unique ID to identify this student</param>
        /// <param name="firstname">Student&#39;s first name</param>
        /// <param name="lastname">Student&#39;s last name</param>
        public Student(string id, string firstname, string lastname)
            : this()
        {
            this.ID = id;
            this.FirstName = firstname;
            this.LastName = lastname;
            this.majorID = string.Empty;
        }
 
        /// <summary>
        /// Overloaded constructor
        /// </summary>
        /// <param name="id">A unique ID to identify this student</param>
        /// <param name="firstname">Student&#39;s first name</param>
        /// <param name="lastname">Student&#39;s last name</param>
        /// <param name="major">Student&39;s Major (as a string ID)</param>
        public Student(string id, string firstname, string lastname, string major)
            : this(id, firstname, lastname)
        {
            this.majorID = major;
        }
 
        /// <summary>
        /// Overloaded constructor
        /// </summary>
        /// <param name="id">A unique ID to identify this student</param>
        /// <param name="firstname">Student&#39;s first name</param>
        /// <param name="lastname">Student&#39;s last name</param>
        /// <param name="major">Student&39;s Major (as a Major object)</param>
        public Student(string id, string firstname, string lastname, Major major)
            : this(id, firstname, lastname, major.ID)
        {
            this._Major = major;
        }
 
        #endregion
 
        #region ICloneable Members
 
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        object ICloneable.Clone()
        {
            return this;
        }
 
        #endregion
 
        #region Overrides
 
        /// <summary>
        /// Determines if this Student object is identical to another Student object
        /// </summary>
        /// <param name="obj">The right-hand object to compare to this object</param>
        /// <returns>true if objects match; false if they do not</returns>
        public override bool Equals(object obj)
        {
            Student right = (Student)obj;
            return (this.ID == right.ID && this.FirstName == right.FirstName && this.LastName == right.LastName);
        }
 
        /// <summary>
        /// Generate hashcode for comparing instances of student objects
        /// </summary>
        /// <returns>This object as a hashcode</returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }
 
        /// <summary>
        /// Displays student data as [ID] Last name, First name
        /// </summary>
        /// <returns>This Student object as a string</returns>
        public override string ToString()
        {
            return String.Format("[{3}] {0}, {1}", this.LastName, this.FirstName, this.ID);
        }
 
        #endregion
 
        #region IComparable Members
 
        int IComparable.CompareTo(object obj)
        {
            Student right = (Student)obj;
            return this.LastName.CompareTo(right.LastName);
        }
 
        #endregion
 
    }// end class
}// end namespace

As you can see, there is a class attribute for every element of a student node in the XML file. While this class does look a bit long, it really isn't. I added some validation to the properties and implemented a couple of interfaces (for sorting, mostly). You will also notice the comments that are preceded with three slashes. Those are what as known as XML comments. Visual Studio can use those to build tool tips for it's auto-complete tool when you are using your class in some part of your project.

Adding LINQ

Now, the fun part of LINQ is taking the data from our XML file and building class objects with it. How do we do that? Glad you asked!

I built two different methods for getting student data. The first method will return a single student, like so:

        /// <summary>
        /// Get data for a single student record
        /// </summary>
        /// <param name="id">A student ID</param>
        /// <returns>A Student object</returns>
        public static Student GetStudent(string id)
        {
            XDocument xd = XDocument.Load("students.xml");
 
            try
            {
                var s = xd.Element("record").Elements("student").FirstOrDefault(x => x.Element("id").Value.Trim() == id);
                Student student = new Student(
                        s.Element("id").Value,
                        s.Element("firstname").Value,
                        s.Element("lastname").Value,
                        s.Element("major").Value
                        );
 
                student._Major = GetMajor(student.MajorID);
 
                return student;
            }
            catch
            {
                return null;
            }
        }

The second method builds a list of student objects:

        /// <summary>
        /// Attempt to load the student data from the default location.
        /// </summary>
        /// <param name="path">Path to the Student XML file.</param>
        /// <returns>A List&lt;&gt; of Student objects.</returns>
        public static List<Student> Students(string path)
        {
            XDocument xd = XDocument.Load(path);
            List<Student> students = new List<Student>();
 
            //  Method syntax (lambda expression)
            var stds = xd.Element("record").Elements("student");
 
            try
            {
                foreach (var s in stds)
                {
                    students.Add(
                        new Student(
                            s.Element("id").Value,
                            s.Element("firstname").Value,
                            s.Element("lastname").Value,
                            s.Element("major").Value
                            )
                        );
                }
 
                return students;
            }
            catch
            {
                throw (new Exception("Student data file could not be parsed."));
            }
        }

As you can see, the methods aren't terribly different, and are actually fairly simple.

Now, these two methods are inside a static class I created called DataLoader. DataLoader is static so that I don't have to actually create an instance of it in order to use it's methods. We likes that, yes we do.

In order to gain access to all the fun LINQ XML tools, we have to add a reference to a special library, like so:

using System.Xml.Linq;

Once we do that, you get access to XDocument. We use XDocument to read in our XML file. We could just use the regular StreamReader in the System.IO library, but XDocument objects let us apply LINQ methods to find the nodes we're looking for. Using StreamReader, we'd have to build either a fairly complicated substring method, or come up with a good regex pattern to find what we wanted.

As you can see in the first example above, it's not too hard to find a node. We take our XDocument object (xd), and (using it's built-in query methods) descend through the node elements until we find elements that contain a value we want. In my case, I'm looking for a student node that has an id element whose value equals the id parameter I passed into the method. FirstOrDefault returns the first node that matches or NULL is no node is found that matches.

In the second example, I simply grab every node. That little bit of LINQ code returns an enumerated collection of XElements (the objects LINQ uses to represent XML nodes). I can then use a foreach loop to iterate through that collection and build a list of student objects, assigning the attribute nodes of each student node to the correct attribute in a student object.

Pretty cool, huh?

Download

If you want it, here's the file.

Comments (0)

There are no comments for this entry.

Creating A C++ Application, Part 1

I've seen a lot of ways of making a console application in C/C++, and I'll admit, I'm not very impressed. Okay, maybe I should clarify the previous statement. I've seen how the students at GRCC write console apps, and, well, they need help...

So, I suppose I should fulfill one of my stated goals, and demonstrate some "clean" ways of writing a console app, m'kay?

So, without further ado, here we go...

Step 1: Set up Visual Studio

First off, you don't really need anything as fancy as Visual Studio just to write a console application, but it is somtimes easier to use than the alternatives. So, if you have Visual Studio (pretty much any version) you can follow along here. If you want to use a plain ol' text editor, be my guest, but know that I'm not going to show you how to get your code to compile and run...

So, for those of you with Visual Studio, follow the steps below to set up your environment for this tutorial.

  1. Open Visual Studio
  2. Click on File -> New -> Project.
  3. Scroll down as needed until you see the section labeled Visual C++ and click on that heading.
  4. You should now see a bunch of possible file types. Select Empty Project.
  5. Give yout project a name. I also tend to uncheck the 'Create directory for solution" option, but that's up to you.
  6. Click OK.

You should now have a blank editor window. Look for the Solution Explorer tab. Depending on your configuration, it may be on the left or right side of the editor window. Now we are going to add the actual C++ file.

  1. With the Solution Explorer tab open, right-click on the Project name.
  2. In the context menu that opens, click on Add -> New Item.
  3. A dialog will open. You should see a list of available C/C++ file type.
  4. Click C++ file(.cpp) and then give your file a name.
  5. Click OK.

We are now ready to start writing our console application! :)

Step 2: Add the essentials

Now, the reason I had you set up this project manually, as opposed to just creating a "Win32 Console Application," is that the latter project type adds some unnecessary "baggage" to the project, like a pre-compiled header and some background crap we don't really need. Those of you who decided you knew where I was going and went ahead and created a "console app" can still follow along, but be aware that you can't modify the header for your main() function or remove the "stdafx.h" include directive without causing some major problems. But I digress...

To read or write anything to our console window, we need to add some library functions to our project. There are a lot of options available to us that work more or less the same, but I prefer to use the most "modern" version. As such, we are going to add the iostream library. IOStream gives us access to a couple of useful functions: cin, cout, & endl. CIN is used for reading character input from the console window. COUT is used to write characters out to the console window, ENDL is a function used for peforming a carriage return (adding a new line) on the console window.

To add the iostream library to our project, we add this line to the top of our blank .cpp file:

#include <iostream>

Next, we have to tell the compiler to give us access to the library function we want. There is a right and a wrong way to do this. N00bs like to add this line:

using namespace std;

which gives us access to EVERYTHING currently available in the std namespace. This works, but its kinda like using a pile-driver to hammer a carpet tack. In other words, its excessive! Instead, we only include those functions we know we will use by employing the using directive:

using std::cin;
using std::cout;
using std::endl;

The double colon ( '::' ) is what's known as the scope resolution operator. It tells the compiler where to look for the function or attribute specified to the right of the operator. In our case, we're telling it to look in the std, or Standard Template Library.

Step 3: Create the application entry point

Now that we have added the functionality we need to our project, we have to add the code that actually executes when the program runs.

Every application needs to have an entry point, or if your prefer, a starting point. Most modern compilers are set up to look for a special function called main(). Furthermore, the compiler in C++ is expecting an integer exit code. The common values are -1, 0, and 1, which can be interpreted by the compiler to indicate whether or not the program completed successfully. For our purposes, we will be returning the exit code '0'. So, add this to your project:

int main( )
{
     return 0;
}

Step 4: Add some code, silly!

Okay, we've added all the code we need to run a console app, but right now, it doesn't DO anything. We should probably rectify that by adding some executable code to our main() function. You can add whatever you want, but for the sake of following convention, I'm gonna add the old standard:

cout<< "Hello, world!" << endl;

system("pause");

The finished product should look similar to this:

#include <iostream>
 
using std::cin;
using std::cout;
using std::endl;
 
int main()
{
    cout<< "Hello, world!" << endl;
    
    system("pause");
    return 0;
}

To see your wonderful new creation, simply click the Run/Debug button (the big green arrow) in your menu bar.

Enjoy!

Comments (0)

There are no comments for this entry.

Page: 123Next

Invalid login. Please try again...

Forgot password? Sign up