The abstract XmlReader and XmlWriter classes of the System.XML namespace provide reading and writing functionality for XML files. XmlReaders are implemented in three main classes that provide forward-only processing of XML files. Table 12.3 lists the implementations of the XmlReader class.
Class |
Description |
---|---|
XmlTextReader |
Fastest implementation of XmlReader. It checks for well-formed XML, but doesn't support data validation. This reader cannot expand general entities and doesn't support default attributes. |
XmlValidatingReader |
Implementation of XmlReader that can validate data using DTDs or schemas. This reader can also expand general entities and supports default attributes. |
XmlNodeReader |
Implementation of XmlReader that reads XML data from an XmlNode. |
The XmlReader classes are noncached forward-only readers. If you need to parse XML in memory, use the XmlDocument class. The XmlDocument class loads an XML file in memory as a tree-like hierarchy, similar to using the DOM objects in the MSXML parser. To work with the different variations of parsing XML files, you're going to write code that uses the different navigation techniques of the XmlWriter, XmlReader, and XmlDocument classes.
To the XML application you've been working with, add a new form named XMLCode by right-clicking your project name, selecting Add from the contextual menu, and then selecting Add New Form. Table 12.4 describes the controls and the Text and Name properties you must add to the form. When you're done, your form should look like Figure 12.12.
Control |
Name |
Text |
---|---|---|
CommandButton |
GetXmlFromDataSet |
Get XML From DataSet |
CommandButton |
GetXmlDataReader |
Get XML DataReader |
CommandButton |
LoadXmlFromFile |
Load XML From File |
CommandButton |
SimpleXmlTextReader |
Simple XmlTextReader |
CommandButton |
XmlTextReaderByNode |
XmlTextReader By Node |
CommandButton |
XmlTextReaderAttributes |
XmlTextReader Attributes |
CommandButton |
UsingXmlDocument |
Using XmlDocument |
CommandButton |
XPathSelectNodes |
XPath SelectNodes |
CommandButton |
DOMXmlAttributes |
DOM XmlAttributes |
RichTextBox |
XmlOut |
Leave Blank |
You must also alias the following namespaces in this form:
System.Data.SqlClient
System.IO
System.Text
System.Xml
Now that the form is created, you'll write code to create the XML files you'll be working with.
Earlier today you created an XML file based on rules you defined in an XML schema file. I mentioned that this could have been done just as easily using methods of the DataSet class. To create an XML file and schema based on the Customers table in the Northwind database, double-click the Get XML from DataSet button and add the code in Listing 12.3.
Private Sub GetXmlFromDataSet_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles GetXmlFromDataSet.Click ' Check to see if the files exist, is they do, Delete them If File.Exists("Customers.xml") Then File.Delete("Customers.xml") End If If File.Exists("CustomersSchema.xml") Then File.Delete("CustomersSchema.xml") End If ' Create a DataSet to hold the XML Dim ds As DataSet = New DataSet("NorthwindCustomers") ' Connect to the database Dim cn As SqlConnection = New SqlConnection( _ "database=northwind;server=.\NetSDK;Integrated Security=SSPI") ' Set the DataAdapter to get the data Dim adp As New SqlDataAdapter("Select * from Customers", cn) ' Fill the DataSet with the DataAdapter command adp.Fill(ds, "Customer") ' call the WriteXml and WriteXmlSchema methods ds.WriteXml("Customers.xml") ds.WriteXmlSchema("CustomersSchema.xml") End Sub
private void GetXmlFromDataSet_Click(object sender, System.EventArgs e) { // Check to see if the files exist, is they do, Delete them if (File.Exists(@"Customers.xml")) { File.Delete(@"Customers.xml"); } if (File.Exists(@"CustomersSchema.xml")) { File.Delete(@"CustomersSchema.xml"); } // Create a DataSet to hold the XML DataSet ds = new DataSet("NorthwindCustomers"); // Connect to the database SqlConnection cn = new SqlConnection (@"database=northwind;server=.\NetSDK;Integrated Security=SSPI"); // Set the DataAdapter to get the data SqlDataAdapter adp = new SqlDataAdapter("Select * from Customers", cn); // Fill the DataSet with the DataAdapter command adp.Fill(ds, "Customer"); // call the WriteXml and WriteXmlSchema methods ds.WriteXml(@"Customers.xml"); ds.WriteXmlSchema(@"CustomersSchema.xml"); }
Run the application and execute the code you just wrote. If you drill into the Bin directory of the solution, you'll see the two new XML files that your code created. Using the WriteXml and WriteXmlSchema methods of the DataSet class, you created the XML file and XML schema for the Customers table in the Northwind database. If you double-click the XML files in the Solution Explorer, you'll see the XML that was created in the XML designer. You'll notice that using the WriteXml and WriteXmlSchema methods creates XML files using Elementsthere are no attributes in the created files.
To create attribute-based XML files, you can use a DataReader with the For XML Auto clause in T-SQL and a FileStream to generate the XML file.
To create an attribute-based XML file, you can use many techniques. The XmlTextWriter class could be used, but there's an easier way. Listing 12.4 demonstrates how to use the For XML Auto Transact-SQL clause to return XML from SQL Server. After the data is returned in a SqlDataReader, you can loop through the data and write it out to a stream. To ensure that the resulting XML is well formed, you add the prolog information and the root element's start and end tags. Double-click on the Get XML DataReader button and add the code in Listing 12.4.
Private Sub GetXmlDataReader_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles GetXmlDataReader.Click ' Create a stream to write to Dim sr As New StreamWriter("CustomerAttributes.xml") ' Add the XML prolog and Root element sr.WriteLine("<?xml version='1.0' standalone='yes' ?>") sr.WriteLine("<TransformedCustomers>") ' Connect to the database Dim cn As SqlConnection = New SqlConnection( _ "database=northwind;server=.\NetSDK;Integrated Security=SSPI") cn.Open() ' Use the For XML Auto clause Dim cmd As New SqlCommand("Select * from customers for XML Auto", cn) ' Declare the DataReader to read the data Dim dr As SqlDataReader = cmd.ExecuteReader ' Loop thru the DataReader, writing out the file While dr.Read ' Get each chunk of XML as the reader reads it sr.Write(dr(0)) End While ' Write the End Element sr.WriteLine("</TransformedCustomers>") ' Flush and Close the Stream sr.Flush() sr.Close() End Sub
private void GetXmlDataReader_Click(object sender, System.EventArgs e) { // Create a stream to write to StreamWriter sr = new StreamWriter(@"CustomerAttributes.xml"); // Add the XML prolog and Root element sr.WriteLine("<?xml version='1.0' standalone='yes' ?>"); sr.WriteLine("<TransformedCustomers>"); // Connect to the database SqlConnection cn = new SqlConnection (@"database=northwind;server=.\NetSDK;Integrated Security=SSPI"); cn.Open(); // Use the For XML Auto clause SqlCommand cmd = new SqlCommand (@"Select * from customers for XML Auto", cn); // Declare the DataReader to read the data SqlDataReader dr = cmd.ExecuteReader(); // Loop thru the DataReader, writing out the file while (dr.Read()) { // Get each chunk of XML as the reader reads it sr.Write(dr.GetString (0)); } // Write the End Element sr.WriteLine("</TransformedCustomers>"); // Flush and Close the Stream sr.Flush(); sr.Close(); }
If you run the application now and run the GetXMLDataReader code, you'll have a new XML file in your Bin directory named CustomerAttributes.xml. If you compare this file to the Customers.xml file you created in Listing 12.3, you'll see the XML format looks quite different, but the way they are handled in the Data view of the XML Designer is exactly the same. They're both examples of well-formed XML documents.
The amount of actual text, or size in bytes, of an XML file that's purely using elements is much larger than an XML file created using attributes. If the amount of data being passed over the wire is critical, you should consider using attributes for flat XML files instead of elements.
The ReadXml method of the DataSet you used earlier to load the Employees.xml file behaves the same for XML documents based on elements or attributes.
To read the complete XML file as a stream, you can use the GetXml method of the DataSet class. Listing 12.5 loads the Customers.xml file into a DataSet, and then uses the GetXml method of the DataSet class to display the data in the textbox. Add the code in Listing 12.5 to the LoadXml click event.
Private Sub LoadXMLFile_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles LoadXMLFile.Click ' Create a new DataSet Dim ds As New DataSet() ' Load the XML file into the DataSet with the ReadXml method ds.ReadXml("Customers.xml") ' Send the XML to the textbox XmlOut.Text = ds.GetXml() End Sub
private void LoadXMLFile_Click(object sender, System.EventArgs e) { // Create a new DataSet DataSet ds = new DataSet(); // Load the XML file into the DataSet with the ReadXml method ds.ReadXml(@"Customers.xml"); // Send the XML to the textbox XmlOut.Text = ds.GetXml(); }
In Listing 12.5, the ReadXml method is used to fill the DataSet with the data from the XML file. After the DataSet is loaded, the GetXml method is called to output the XML data to the text box. When you run the application and execute the code, the data from the XML file is simply displayed in the text box, and you should see something similar to Figure 12.13.
After an XML file is loaded into the DataSet, you can treat it like any other DataTable in the DataSet. You can loop through tables, rows, and columns just as you did over the last two days when you learned how to work with DataSets in ADO.NET. Remember that XML is data. After data is in a DataSet, the methods to interact with it are the same no matter what the datasource is.
The XmlTextReader class can be compared to the DataReader class of the System.Data namespace. DataReaders are read-only, forward-only unbuffered streams of data from a database. XmlReaders are read-only, forward-only unbuffered streams of data from XML files. The XmlTextReader class provides robust functionality for reading XML files that are element based or attribute based.
The Read method of the XmlTextReader class reads the XML stream until a False value is returned to indicate the end of the file has been reached. While reading the file, you can check the NodeType property to determine what node you're reading and what kind of node it is. Table 12.5 lists the NodeType enumeration and descriptions of the different types of nodes.
To implement a simple XmlTextReader that outputs only the text or data value for a node, you can use the NodeType as the XmlReader reads the XML stream. The code in Listing 12.6 uses an XmlTextReader to read the Customers.xml file to output only Text node types. Add the code in Listing 12.6 to the Simple XmlTextReader click event.
Private Sub SimpleXmlTextReader_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles SimpleXmlTextReader.Click ' Load the XML file into a new XmlTextReader Dim xr As New XmlTextReader("Customers.xml") Dim sb As New StringBuilder() ' Read the file, getting the Text nodes, which is the actual data While xr.Read ' Check the XmlNodeType enumeration for Text If xr.NodeType = XmlNodeType.Text Then ' Get the Value of the Text nodetype sb.Append(xr.Value) ' Append the carriage return sb.Append(ControlChars.CrLf) End If End While ' Output to the textbox XmlOut.Text = sb.ToString ' Close the XmlTextReader xr.Close() End Sub
private void SimpleXmlTextReader_Click (object sender, System.EventArgs e) { // Load the XML file into a new XmlTextReader XmlTextReader xr = new XmlTextReader(@"Customers.xml"); StringBuilder sb = new StringBuilder(); // Read the file, getting the Text nodes, which is the actual data while (xr.Read()) { // Check the XmlNodeType enumeration for Text if (xr.NodeType == XmlNodeType.Text) { // Get the Value of the Text nodetype sb.Append(xr.Value + "\n"); } } // Output to the textbox XmlOut.Text = sb.ToString(); // Close the XmlTextReader xr.Close(); }
After you call the Read method in Listing 12.6, you check the NodeType property. If NodeType is equal to NodeType.Text, the value is appended to the string builder. If you run the application and execute the code, you'll see something similar to Figure 12.14.
By outputting only the Text node type, you're getting rid of the XML tags that describe the data.
If you need to output specific data, such as the CompanyName, you can check the Name property of the current element. The code in Listing 12.7 reads each element, and adds only the CompanyName values to the string builder for output to the text box. Add the code in Listing 12.7 to the XmlTextReaderByNode click event.
Private Sub XmlTextReaderByNode_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles XmlTextReaderByNode.Click ' Load the XML File Dim xr As New XmlTextReader("Customers.xml") Dim sb As New StringBuilder() ' Retrieve data based on a specific Node Name While xr.Read ' Ignore any non-character data If xr.ReadString.Length > 0 Then ' Check the name of the Element If xr.Name = "CompanyName" Then ' Read the String with the ReadString() method sb.Append(xr.ReadString()) ' Append the carriage return sb.Append(ControlChars.CrLf) End If End If End While ' Display in the textbox XmlOut.Text = sb.ToString End Sub
private void XmlTextReaderByNode_Click (object sender, System.EventArgs e) { // Load the XML file into a new XmlTextReader XmlTextReader xr = new XmlTextReader(@"Customers.xml"); StringBuilder sb = new StringBuilder(); // Read the file, getting the Text nodes, which is the actual data while (xr.Read()) { // Ignore any non-character data if (xr.ReadString().Length > 0) { // Check the name of the Element if (xr.Name == "CompanyName") { // Read the String with the ReadString() method sb.Append(xr.ReadString() + "\n"); } } } // Output to the textbox XmlOut.Text = sb.ToString(); // Close the XmlTextReader xr.Close(); }
When you run this code, you get only the CompanyName values from the XML file, as Figure 12.15 demonstrates.
Using the Name property along the with NodeType is extremely useful for getting data values for specific elements.
Until now, you've used only the element-based Customers.xml file. But you'll often need to deal with attributes in an XML file. Using the GetAttribute method of the XmlTextReader class returns a specific attribute in a node.
When the reader reads a node using the Read method, it reads all the information in the node. So, if there are attributes in the node, it looks at them as a collection. By checking the HasAttributes and AttributesCount properties, you can determine whether a node has attributes, and use the GetAttributes method to grab the data values for the attributes you're looking for.
Listing 12.8 uses the GetAttributes method to accomplish the same thing that Listing 12.7 did: retrieve CompanyName. The difference is that the XML file you're reading is the attribute-based file, not the element-based file. The code iterates through all the attributes for each node and outputs them to the text box. Add the code in Listing 12.8 to the XmlTextReaderAttributes click event.
Private Sub XmlTextReaderAttributes_Click (ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles XmlTextReaderAttributes.Click ' Load the XML file into a new XmlTextReader Dim xr As New XmlTextReader("CustomerAttributes.xml") Dim sb As New StringBuilder() ' Read the file While xr.Read ' Only look at Elements If xr.NodeType = XmlNodeType.Element Then ' That have attributes If xr.HasAttributes Then ' With an AttributeCount > 0 If xr.AttributeCount > 0 Then sb.Append(ControlChars.CrLf) ' Write out the CompanyName sb.Append(xr.GetAttribute("CompanyName")) sb.Append(ControlChars.CrLf) ' Loop thru the Attributes and write them out Dim intX As Integer For intX = 0 To xr.AttributeCount - 1 sb.Append("***") ' Get the Attribute sb.Append(xr.GetAttribute(intX)) sb.Append(ControlChars.CrLf) Next End If End If End If End While ' Output to the textbox XmlOut.Text = sb.ToString 'Console.WriteLine(sb.ToString) ' Close the XmlTextReader xr.Close() End Sub
private void XmlTextReaderAttributes_Click (object sender, System.EventArgs e) { // Load the XML file into a new XmlTextReader XmlTextReader xr = new XmlTextReader(@"CustomerAttributes.xml"); StringBuilder sb = new StringBuilder(); // Read the file, getting the Text nodes, which is the actual data while (xr.Read()) { // Only look at Elements if (xr.NodeType == XmlNodeType.Element) { // That have attributes if (xr.HasAttributes) { // With an AttributeCount > 0 if (xr.AttributeCount > 0) { // Write out the CompanyName sb.Append(xr.GetAttribute("CompanyName")); for (int i = 0; i < xr.AttributeCount; i++) { sb.Append("***"); sb.Append(xr.GetAttribute(i) + "\n"); } } } } } // Output to the textbox XmlOut.Text = sb.ToString(); // Close the XmlTextReader xr.Close(); }
The code in Listing 12.8 first checks to see whether the current node has attributes. If it does, the code makes sure that the count is greater then zero, and then loops through the attributes using the index of the attribute within the node. If you run this now and execute the code, you'll see something like Figure 12.16.
Reading attributes is just as easy as reading elements in an XML file. More complex XML files use a combination of elements and attributes to describe the data, so understanding how to work with each type of node is important.