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.
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.
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"?>
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.
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 WriteStartElement 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 WriteEndElement 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.
The XmlTextWriter class also supports the creation of attributes using the WriteAttributeString 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 WriteStartAttribute 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.
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 (').
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 XmlTextWriter object, you should first end the document by calling the WriteEndDocument 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();
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.
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.
As you can see, this listing uses all the features we’ve been looking at, including formatting, elements, subelements, attributes, comments, and more.
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.
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.
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.
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. |
|
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.
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.
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();
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.
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 XmlTextReader 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.