Reading and Writing XML

Reading and Writing XML

Reading and writing XML in Visual C# .NET programs is usually accomplished by using a class derived from the XmlReader and XmlWriter classes. These classes are abstract and therefore must be extended. This abstraction not only allows customized readers and writers to be created, but it also leaves the capability for these derived classes to be swapped in and out if needed. A few customized readers and writers are already available. Within the .NET Framework classes, you have access to the following classes, which are extensions of the XmlReader class:

  • XmlTextReader

  • XmlValidatingReader

  • XmlNodeReader

In addition, you have access to the XmlTextWriter class, which is an extension of the XmlWriter base class.

The XmlTextReader and XmlTextWriter classes provide you with a quick and efficient means of reading and writing XML. These classes were created to be fast; however, that speed comes at a cost. You’re provided with sequential, forward-only navigation to the raw data in the files. In addition, the classes provide the ability to read XML from a non-cached stream. The XmlTextReader and XmlTextWriter classes will be covered in the following sections.

Using XmlTextWriter to Create XML

You can write XML to a stream, the console, or a file using an XmlTextWriter object. The process of writing XML to a file is similar to the way other data is written to a file. You first open or create a file, then write the data to the file, and then flush the data stream to make sure everything is committed. When you’ve finished, you close the file. The methods of the XmlTextWriter object produce well-formed XML.

You can open a file at the same time you construct an XmlTextWriter object by passing the file name and an encoding parameter to the constructor. The encoding parameter, of type System.Text.Encoding, specifies the character encoding to generate. The following example shows one way to create an XmlTextWriter object:

XmlTextWriter myWriterObj = new XmlTextWriter(filename, encoding);

In this example, myWriterObj is the name of the XmlTextWriter object being created. The variable filename is the name of the XML file to be created. If the file already exists, it will be overwritten. The encoding parameter can be an instance of one of the following classes:

  • System.Text.ASCIIEncoding

  • System.Text.UnicodeEncoding

  • System.Text.UTF7Encoding

  • System.Text.UTF8Encoding

You can also pass a null reference for the encoding parameter, which causes XmlTextWriter to use UTF-8 encoding.

To create an XmlTextWriter object named myWriter and assign it to a file named myWriter.xml in the C:\Xmldata directory, you would use the following constructor:

XmlTextWriter myWriter = new XmlTextWriter(@"C:\Xmldata\myWriter.xml", null);

Once the XmlTextWriter object has been created and assigned, you can begin writing XML. You can write either a full XML document or fragments of XML—in either case, the XmlTextWriter object will work to ensure that the resulting XML is well-formed.

Your XML file should include the following items:

  • XML declaration

  • Comments

  • Elements

  • Subelements

  • Attributes

We’ll look at how to create each of these items in the sections that follow.

Creating an XML Declaration

If you’re writing a complete XML document, you should start by writing an XML declaration at the beginning of your file. The WriteStartDocument method can be used to create this declaration. This method can be called with no parameters, as shown here:

myWriter.WriteStartDocument();

Calling this method starts a new XML document and writes the following text to your XML file or stream:

<?xml version="1.0" encoding="utf-8"?>
Adding Comments

Although comments aren’t mandatory in XML, it’s good practice to add them. To add a comment to your XML file using your XmlTextWriter object, you use the WriteComment method. This method takes the comment string as a parameter, as follows:

myWriter.WriteComment("comment text");

The result of calling this method is shown here:

<!--comment text-->

As you can see, the WriteComment method takes care of formatting the comment in the appropriate XML markup.

Writing Elements and Subelements

The most important section of an XML file is the area containing the elements. The primary data is stored in the elements. Elements can contain attribute names, values, and subelements.

In general, there are two techniques for creating elements: you can write the element and its value all at once, or you can start an element, write the element content, and then later close the element.

The WriteElementString method allows you to write an element and its value all at the same time. This method takes two parameters: the first is the name of the element you want to create, and the second is the value of the element. For example, to write Gone with the Wind as the value for a video element, you could use the following line of code:

myWriter.WriteElementString("Video", "Gone with the Wind");

Here’s the resulting XML that would be written:

<Video>Gone with the Wind</Video>

As you can see, this method writes the video element’s start tag, its content, and the end tag. If you want to create an element with subelements or if you want to add attributes to an element, you must open the element, write the tags and any other data, and then close the original element. The opening and closing of individual elements can be accomplished using the WriteStart­Element and WriteEndElement methods.

The WriteStartElement method takes the name of the element as its parameter, resulting in just the start tag of the element being written. You can then create additional subelements and any other data. Last you’ll call the Write­EndElement method with no parameters to create an end tag to close the element. The following code shows how the Video element can be created using this technique:

myWriter.WriteStartElement("Video");
myWriter.WriteString("Gone with the Wind");
myWriter.WriteEndElement();

Between the start and end tags of an element, you can write additional subelements, write the value of the element, or add attributes. To add subelements, you can use either of the techniques you just learned. To write the element value or attribute value, you can call the WriteString method, as shown in the previous example. This method writes a text string, passed as a parameter, directly to the XML file. You’ll learn how to write attributes in the next section.

Adding Attributes

The XmlTextWriter class also supports the creation of attributes using the Write­AttributeString method. WriteAttributeString can be used to add an attribute to an XML element. The simple form of this method takes the attribute name and the attribute values as parameters. For example, an element for measuring time, named Length, could have an attribute to indicate what unit of measurement is being used. A video could be 120 minutes, or it could be 2 hours. The following code creates a Length element with an attribute:

myWriter.WriteStartElement("Length");
myWriter.WriteAttributeString("Measurement", "Minutes");
myWriter.WriteString("120");
myWriter.WriteEndElement();

The resulting XML from this code is shown here:

<Length Measurement="Minutes">120</Length>

As you can see, attribute values are enclosed in quotation marks in XML. If you need more control over your attribute definitions, you can use the Write­StartAttribute and WriteEndAttribute methods. These more advanced methods allow you to set the attribute prefix and namespace. For more information about these two methods, refer to the Microsoft Developer Network (MSDN) Library.

Adding Formatting and Spacing to an XML File

Just like C# code, XML doesn’t require any fancy spacing or indentation; however, adding formatting and spacing can make your XML files easier to view. You might want to set the following four properties of the XmlTextWriter object when creating XML files:

  • Formatting

  • Indentation

  • IndentChar

  • QuoteChar

The Formatting property is set to either the default value of Formatting.None or Formatting.Indented. If you set this property to Formatting.Indented, you can then set values for Indentation and IndentChar. If you leave the default value of Formatting.None, any values specified for Indentation and IndentChar are ignored. IndentChar indicates which character to use for indenting the XML code. By default, the XmlTextWriter object uses a space character, but you can change this setting to any character you want to use for indentation. Keep in mind that, to ensure valid XML, you must use the white-space characters 0x9 (tab), 0x10 (linefeed), 0x13 (carriage return), or 0x20 (space). In addition to specifying an indentation character, you can set the distance of the indentation by specifying a number in the Indentation property, which defaults to 2.

The other property you can set is the QuoteChar property. This property is used for specifying which character to use to quote text such as attribute values. By default, the character used to quote text is the double quotation mark ("). You can change this to a single quotation mark (').

Closing the XML File or Stream

After you’ve written the elements, subelements, attributes, and other values to your XML file, you’ll want to close things down. When closing down an Xml­TextWriter object, you should first end the document by calling the WriteEnd­Document method, as shown here:

myWriter.WriteEndDocument();

Calling this method will ensure that all open items are also closed. When all the elements are closed, you can then close the XmlTextWriter object by calling the Close method:

myWriter.Close();
Sample Application Using XmlTextWriter

The following listing illustrates many of the methods and properties for writing an XML file using XmlTextWriter. This listing creates a simple dialog box in which you can enter a video title, a star’s name, the length of the video (assumption is minutes), and the rating of the video. This information is written to an XML file stored at C:\Videos.xml.

note

To save space, only a partial code listing is included here. For the complete listing, see the XMLWrite application on the companion CD. This listing doesn’t include error handling, nor does it allow you to enter a dynamic file name. These features were removed to keep the listing short.

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Xml;
using System.Text;

namespace MyXMLApplication
{

    /// <summary>
    /// Summary description for Form1.
    /// </summary>
    public class FrmVideo : System.Windows.Forms.Form
    {
        private System.Windows.Forms.Button btnAdd;
        private System.Windows.Forms.Button btnExit;
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.Label label2;
        private System.ComponentModel.IContainer components;
        private System.Windows.Forms.TextBox txtVideo;
        private System.Windows.Forms.TextBox txtLength;
        private System.Windows.Forms.ToolTip toolTip1;
        private System.Windows.Forms.TextBox txtStar;
        private System.Windows.Forms.ComboBox cmbRating;
        private System.Windows.Forms.Label label3;
        private System.Windows.Forms.Label label4;

        // Define an XmlTextWriter object.
        private XmlTextWriter xtw;

        public FrmVideo()
        {
            //
            // Required for Windows Form Designer support
            //
            InitializeComponent();

            // Create an XmlTextWriter object and initialize a few 
            // properties.
            xtw = new XmlTextWriter(@"C:\Videos.xml", null);

            xtw.Formatting = Formatting.Indented;
            xtw.Indentation = 3;

            // Start the XML document.
            xtw.WriteStartDocument();  
    
            xtw.WriteComment("Video Library");
            xtw.WriteComment(
                "This is a file containing a number of videos.");
            // Create the root element.
            xtw.WriteStartElement("VideoLibrary");
        }

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if (components != null) 
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        #region Windows Form Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            
    

        }
        #endregion

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main() 
        {
            // Create the form to gather input.
            Application.Run(new FrmVideo());  
        }

        private void btnExit_Click(object sender, System.EventArgs e)
        {
            // Close the root element, and close the file.
            xtw.WriteEndElement();
            xtw.WriteEndDocument();
            xtw.Flush();  // Flush the stream.
            xtw.Close();  // Close the XmlTextWriter object.

            Application.Exit();
        }

        private void btnAdd_Click(object sender, System.EventArgs e)
        {
            // Add the data on the screen to the XML file.
            // Start a Video element.
            xtw.WriteStartElement("Video");  
            // Add a Title element.
            xtw.WriteElementString("Title", txtVideo.Text ); 
            // Start a Length element.
            xtw.WriteStartElement("Length");
            // Add the Measurement attribute to the Length element.
            xtw.WriteAttributeString("Measurement", "minutes");
            // Add the data to the Length element. 
            xtw.WriteString(txtLength.Text );
            xtw.WriteEndElement();  // End Length element.
            xtw.WriteElementString("star", txtStar.Text);
            xtw.WriteElementString("rating", cmbRating.Text);
            xtw.WriteEndElement(); // End Video element.
            // Put insertion point back in first field.
            txtVideo.Focus();  
            // Clear the fields on the screen.
            txtVideo.Text = ""; 
            txtLength.Text = "";
            txtStar.Text = "";
            cmbRating.Text = "";
        }
    }
}

The resulting dialog box is shown in Figure 19-1.

Figure 19-1.
XMLWrite, a sample video application using Xml­TextWriter.

As you can see, this listing uses all the features we’ve been looking at, including formatting, elements, subelements, attributes, comments, and more.

Using XmlTextReader to Read XML

Reading a basic XML file is much less involved than creating one. As mentioned, the XmlTextReader class provides a fast, efficient way to read an XML file. XmlTextReader is limited to forward-only reading.

The technique for using XmlTextReader is similar to reading any other file. First you open the file, then you read information from the file, and last you close the file. Each time you read from the file, your location in the file moves forward. When you reach the end of the file, there’s nothing more you can do.

Creating an XmlTextReader Object

You can open an XML file at the same time you create the XmlTextReader object. The following line of code creates an XmlTextReader object named myReader and points it to a file named filename. This file name can include a full path.

XmlTextReader myReader = new XmlTextReader(“filename”);

There are a number of other ways to create an XmlTextReader object. For example, you can create an XmlTextReader object that points to a stream containing XML. Refer to the MSDN Library for the different ways of creating XmlTextReader objects.

Reading Data

After you’ve opened an XmlTextReader object, you can read the data from it. A number of methods can be used to read data from an XML file. The more common methods are listed in Table 19-1.

note

The XmlTextReader object doesn’t validate the data; it assumes that you’re using standard XML (W3C’s XML 1.0). This lack of data validation helps to ensure that you obtain the fastest speed.

Table 19-1.  XmlTextReader Methods for Reading Data  

Method

Description

Read

Reads the next node within the XML file

ReadAttributeValue

Reads an attribute value

ReadBase64

Reads Base64 values

ReadBinHex

Reads BinHex values

ReadChars

Reads text characters

ReadElementString

Reads text-only elements

ReadEndElement

Reads an ending element

ReadInnerXml

Reads the entire contents of an XML node into a string

ReadOuterXml

Reads an entire XML node and its contents into a string

ReadStartElement

Reads a starting element

ReadString

Reads an element or a text node value into a string

The Read method gives you a lot of flexibility. This method reads each node in the XML file individually. You can then evaluate each item’s type to determine what it is and what you can do with it. This evaluation can be done by comparing the value read from the XmlTextReader object with an XmlNodeType value. The possible XmlNodeType enumeration members are listed in Table 19-2.

Table 19-2.  XmlNodeType Enumeration Values 

Value

Description

XmlNodeType.Attribute

An attribute

XmlNodeType.CDATA

A CDATA section

XmlNodeType.Comment

An XML comment

XmlNodeType.Document

The document node, which is generally the root node

XmlNodeType.DocumentFragment

A document fragment of XML not associated with an actual document

XmlNodeType.DocumentType

The document type tag

XmlNodeType.Element

The start tag for an element

XmlNodeType.EndElement

The end tag for an element

XmlNodeType.EndEntity

The end tag for an entity (physically, an XML document is composed of units called entities)

XmlNodeType.Entity

An entity

XmlNodeType.EntityReference

An entity reference

XmlNodeType.None

Returned when the Read method of the XmlTextReader object hasn’t been called

XmlNodeType.Notation

A notation in the document declaration

XmlNodeType.ProcessingInstruction

A processing instruction in the XML file

XmlNodeType.SignificantWhitespace

The significant white space (white space that’s intended for inclusion in the delivered version of an XML document) in a mixed content model

XmlNodeType.Text

The text content in a node

XmlNodeType.Whitespace

The white space between the different markup items

XmlNodeType.XmlDeclaration

The declaration for the XML document

As you read data from an XmlTextReader object, you can determine whether it’s important or unimportant to your application. As you read each item, you increment a pointer to the next item. As mentioned, you can’t go back to a previous item when using XmlTextReader because reading is forward only.

Closing the XmlTextReader Object

After you’ve read the contents of a file, you should close it. Closing the file requires a simple call to the XmlTextReader object’s Close method, as shown in the following code. Calling this method also releases resources held while reading.

myReader.Close();
Sample Application Using XmlTextReader

The following listing pulls together a simple Microsoft Windows–based application that reads an XML file using XmlTextReader. In this program, the file name is hard-coded to C:\Videos.xml; however, you could change this to any valid XML file name. The program cycles through the XML file using the Read method. With each read, the node’s type is displayed, along with the value and name. The application also displays the data with formatting.

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Xml;
using System.Text;

namespace ReadXML
{
    public class frmReadXML : System.Windows.Forms.Form
    {
        private System.Windows.Forms.TextBox txtNode;
        private System.Windows.Forms.Label lblNode;
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.Label label2;
        private System.Windows.Forms.TextBox txtValue;
        private System.Windows.Forms.TextBox txtName;
        private System.Windows.Forms.Button btnExit;
        private System.Windows.Forms.Button btnNext;
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.Container components = null;
        private System.Windows.Forms.Label txtDisplay;

        private XmlTextReader xtr = null;

        public frmReadXML()
        {
            // Required for Windows Form Designer support
            InitializeComponent();

            // Create XmlTextReader object.
            xtr = new XmlTextReader(@"C:\Videos.xml");
            // Don't ignore white space.
            xtr.WhitespaceHandling = WhitespaceHandling.All;
        }

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if (components != null) 
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        #region Windows Form Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            
    

        }
        #endregion

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main() 
        {
            Application.Run(new frmReadXML());
        }

        private void button1_Click(object sender, System.EventArgs e)
        {
            if ( xtr != null)
                xtr.Close();

            Application.Exit();
        }

        private void btnNext_Click(object sender, System.EventArgs e)
        {
            StringBuilder str = new StringBuilder("Formatted: ");

            if (xtr.Read() == true)
            {
                txtNode.Text    = xtr.NodeType.ToString();
                txtName.Text    = xtr.Name.ToString();
                txtValue.Text   = xtr.Value.ToString();

                switch (xtr.NodeType)
                {
                    case XmlNodeType.Element:
                        str.AppendFormat("<{0}>",xtr.Name);
                        break;
                    case XmlNodeType.Text:
                        break;
                    case XmlNodeType.CDATA:
                        str.AppendFormat("<![CDATA[{0}]]>", 
                            xtr.Value);
                        break;
                    case XmlNodeType.ProcessingInstruction:
                        str.AppendFormat("<?{0} {1}?>", 
                            xtr.Name, xtr.Value);
                        break;
                    case XmlNodeType.Comment:
                        str.AppendFormat("<!--{0}-->", xtr.Value);
                        break;
                    case XmlNodeType.XmlDeclaration:
                        //Console.Write("<?xml version='1.0'?>");
                        break;
                    case XmlNodeType.DocumentType:
                        str.AppendFormat("<!DOCTYPE {0} [{1}]", 
                            xtr.Name, xtr.Value);
                        break;
                    case XmlNodeType.EntityReference:
                        str.Append(xtr.Name);
                        break;
                    case XmlNodeType.EndElement:
                        str.AppendFormat("</{0}>", xtr.Name);
                        break;
                    case XmlNodeType.Whitespace:
                        //Console.Write("{0}", xtr.Value );
                        break;
                }

                txtDisplay.Text = str.ToString();
            }
            else
            {
                // End of file
                txtValue.Text = "EoF";
                txtName.Text  = "EoF";
                txtNode.Text  = "Eof";
            }
        }
    }
}

This listing generates the dialog box shown in Figure 19-2.

Figure 19-2.
Read XML, a sample application using XmlTextReader.

This listing reads through the Videos.xml file you created with the XMLWrite application, which is listed earlier in this chapter, in the section “Sample Application Using XmlTextWriter.” The code uses the Read method to read each XML node out of the file. Once a node has been read, the next few lines move node values to the fields on the screen.

txtNode.Text  = xtr.NodeType.ToString();
txtName.Text  = xtr.Name.ToString();
txtValue.Text = xtr.Value.ToString();

The node type is stored in the NodeType property of xtr (the XmlText­Reader object), the name of the node is stored in the object’s Name property, and the node’s value is stored in the object’s Value property. As you click the Next button, you’ll see each node displayed. You’ll also notice that not all node types have both a name and a value.

After the screen values have been set, the node type in xtr is compared to the different XmlNodeType values via a switch statement. The switch statement formats the read XML data based on its type, and the information is displayed in the dialog box. When the end of the file is reached, the values in the dialog box are set to EOF.



Part III: Programming Windows Forms