Reading and Writing XML

The FCL’s System.Xml namespace offers a variety of classes for reading and writing XML documents. For DOM lovers, there’s the XmlDocument class, which looks and feels like MSXML but is simpler to use. If you prefer a stream-based approach to reading XML documents, you can use XmlTextReader or the schema-aware XmlValidatingReader instead. A complementary class named XmlTextWriter simplifies the process of creating XML documents. These classes are the first line of defense when battle plans call for manipulating XML.

The XmlDocument Class

XmlDocument provides a programmatic interface to XML documents that complies with the DOM Level 2 Core specification. It represents a document as an upside-down tree of nodes, with the root element, or document element, at the top. Each node is an instance of XmlNode, which exposes methods and properties for navigating DOM trees, reading and writing node content, adding and removing nodes, and more. XmlDocument derives from XmlNode and adds methods and properties of its own supporting the loading and saving of documents, the creation of new nodes, and other operations.

The following statements create an XmlDocument object and initialize it with the contents of Guitars.xml:

XmlDocument?doc?=?new?XmlDocument?();
doc.Load?("Guitars.xml");

Load parses the specified XML document and builds an in-memory representation of it. It throws an XmlException if the document isn’t well-formed.

A successful call to Load is often followed by reading the XmlDocument’s DocumentElement property. DocumentElement returns an XmlNode reference to the document element, which is the starting point for a top-to-bottom navigation of the DOM tree. You can find out whether a given node (including the document node) has children by reading the node’s HasChildNodes property. You can enumerate a node’s children by reading its ChildNodes property, which returns an XmlNodeList representing a collection of nodes. The combination of HasChildNodes and ChildNodes makes possible a recursive approach to iterating over all the nodes in the tree. The following code loads an XML document and writes a list of its nodes to a console window:

XmlDocument?doc?=?new?XmlDocument?();
doc.Load?("Guitars.xml");
OutputNode?(doc.DocumentElement);
??.
??.
??.
void?OutputNode?(XmlNode?node)
{
????Console.WriteLine?("Type={0}\tName={1}\tValue={2}",
????????node.NodeType,?node.Name,?node.Value);

????if?(node.HasChildNodes)?{
????????XmlNodeList?children?=?node.ChildNodes;
????????foreach?(XmlNode?child?in?children)
????????????OutputNode?(child);
????}
}

Run against Guitars.xml in Figure 13-3, it produces the following output:

Type=Element????Name=Guitars????Value=
Type=Element????Name=Guitar?????Value=
Type=Element????Name=Make???????Value=
Type=Text???????Name=#text??????Value=Gibson
Type=Element????Name=Model??????Value=
Type=Text???????Name=#text??????Value=SG
Type=Element????Name=Year???????Value=
Type=Text???????Name=#text??????Value=1977
Type=Element????Name=Color??????Value=
Type=Text???????Name=#text??????Value=Tobacco?Sunburst
Type=Element????Name=Neck???????Value=
Type=Text???????Name=#text??????Value=Rosewood
Type=Element????Name=Guitar?????Value=
Type=Element????Name=Make???????Value=
Type=Text???????Name=#text??????Value=Fender
Type=Element????Name=Model??????Value=
Type=Text???????Name=#text??????Value=Stratocaster
Type=Element????Name=Year???????Value=
Type=Text???????Name=#text??????Value=1990
Type=Element????Name=Color??????Value=
Type=Text???????Name=#text??????Value=Black
Type=Element????Name=Neck???????Value=
Type=Text???????Name=#text??????Value=Maple

Notice the varying node types in the listing’s first column. Element nodes represent elements in an XML document, and text nodes represent the text associated with those elements. The following table lists the full range of possible node types, which are represented by members of the XmlNodeType enumeration. Whitespace nodes represent “insignificant” white space—that is, white space that appears between markup elements and therefore contributes nothing to a document’s content—and aren’t counted among a document’s nodes unless you set XmlDocument’s PreserveWhitespace property, which defaults to false, equal to true before calling Load.

XmlNodeType

Example

Attribute

<Guitar Image="MySG.jpeg">

CDATA

<![CDATA["This is character data"]]>

Comment

<!-- This is a comment -->

Document

<Guitars>

DocumentType

<!DOCTYPE Guitars SYSTEM "Guitars.dtd">

Element

<Guitar>

Entity

<!ENTITY filename "Strats.xml">

EntityReference

&lt;

Notation

<!NOTATION GIF89a SYSTEM "gif">

ProcessingInstruction

<?xml-stylesheet type="text/xsl" href="Guitars.xsl"?>

Text

<Model>Stratocaster</Model>

Whitespace

<Make/>\r\n<Model/>

XmlDeclaration

<?xml version="1.0"?>

Observe that the preceding output contains no attribute nodes even though the input document contained two elements having attributes. That’s because attributes get special treatment. A node’s ChildNodes property doesn’t include attributes, but its Attributes property does. Here’s how you’d modify the OutputNode method to list attributes as well as other node types:

void?OutputNode?(XmlNode?node)
{
????Console.WriteLine?("Type={0}\tName={1}\tValue={2}",
????????node.NodeType,?node.Name,?node.Value);

????if?(node.Attributes?!=?null)?{
????????foreach?(XmlAttribute?attr?in?node.Attributes)
????????????Console.WriteLine?("Type={0}\tName={1}\tValue={2}",
????????????????attr.NodeType,?attr.Name,?attr.Value);
????}

????if?(node.HasChildNodes)?{
????????foreach?(XmlNode?child?in?node.ChildNodes)
????????????OutputNode?(child);
????}
}

An XmlNode object’s NodeType, Name, and Value properties expose the type, name, and value of the corresponding node. For some node types (for example, elements), Name is meaningful and Value is not. For others (text nodes, for instance), Value is meaningful but Name is not. And for still others—attributes being a great example—both Name and Value are meaningful. Name returns a node’s qualified name, which includes a namespace prefix if a prefix is present (for example, win:Guitar). Use the LocalName property to retrieve names without prefixes.

You don’t have to iterate through every node in a document to find a specific node or set of nodes. You can use XmlDocument’s GetElementsByTagName, SelectNodes, and SelectSingleNode methods to target particular nodes. The sample application in Figure 13-5 uses GetElementsByTagName to quickly create an XmlNodeList targeting all of the document’s Guitar nodes. SelectNodes and SelectSingleNode execute XPath expressions. XPath is introduced later in this chapter.

XmlDocument can be used to write XML documents as well as read them. The following code sample opens Guitars.xml, deletes the first Guitar element, adds a new Guitar element, and saves the results back to Guitars.xml:

XmlDocument?doc?=?new?XmlDocument?();
doc.Load?("Guitars.xml");

//?Delete?the?first?Guitar?element
XmlNode?root?=?doc.DocumentElement;
root.RemoveChild?(root.FirstChild);

//?Create?element?nodes
XmlNode?guitar?=?doc.CreateElement?("Guitar");????????
XmlNode?elem1?=?doc.CreateElement?("Make");
XmlNode?elem2?=?doc.CreateElement?("Model");
XmlNode?elem3?=?doc.CreateElement?("Year");
XmlNode?elem4?=?doc.CreateElement?("Color");
XmlNode?elem5?=?doc.CreateElement?("Neck");

//?Create?text?nodes
XmlNode?text1?=?doc.CreateTextNode?("Gibson");
XmlNode?text2?=?doc.CreateTextNode?("Les?Paul");
XmlNode?text3?=?doc.CreateTextNode?("1959");
XmlNode?text4?=?doc.CreateTextNode?("Gold");
XmlNode?text5?=?doc.CreateTextNode?("Rosewood");

//?Attach?the?text?nodes?to?the?element?nodes
elem1.AppendChild?(text1);
elem2.AppendChild?(text2);
elem3.AppendChild?(text3);
elem4.AppendChild?(text4);
elem5.AppendChild?(text5);

//?Attach?the?element?nodes?to?the?Guitar?node
guitar.AppendChild?(elem1);
guitar.AppendChild?(elem2);
guitar.AppendChild?(elem3);
guitar.AppendChild?(elem4);
guitar.AppendChild?(elem5);

//?Attach?the?Guitar?node?to?the?document?node
root.AppendChild?(guitar);

//?Save?the?modified?document
doc.Save?("Guitars.xml");

Other XmlDocument methods that are useful for modifying document content include PrependChild, InsertBefore, InsertAfter, RemoveAll, and ReplaceChild. As an alternative to manually creating text nodes and making them children of element nodes, you can assign text by writing to elements’ InnerText properties. By the same token, reading an element node’s InnerText property is a quick way to retrieve the text associated with an XML element.

XmlDocument is typically used by applications that read XML documents and care about the relationships between nodes. Figure 13-6 shows one such application. Called XmlView, it’s a Windows Forms application that reads an XML document and displays it in a tree view control. Each item in the control represents one node in the document. Items are color-coded to reflect node types. Items without colored blocks represent attributes.

Figure 13-6
Windows Forms XML viewer.

XmlView’s source code appears in Figure 13-7. Clicking the Load button activates XmlViewForm.OnLoadDocument, which loads an XmlDocument from the specified data source and calls a local method named AddNodeAndChildren to recursively navigate the document tree and populate the tree view control. The end result is a graphic depiction of the document’s structure and a handy tool for digging around in XML files to see what they’re made of. XmlView is compiled slightly differently than the Windows Forms applications in Chapter 4. Here’s the command to compile it:

csc?/t:winexe?/res:buttons.bmp,Buttons?xmlview.cs

The /res switch embeds the contents of Buttons.bmp in XmlView.exe and assigns the resulting resource the name “Buttons”. Buttons.bmp contains an image depicting the colored blocks used in the tree view control. The statement

NodeImages.Images.AddStrip?(new?Bitmap?(GetType?(), "Buttons"));

loads the image and uses it to initialize the ImageList named NodeImages. Packaging the image as an embedded resource makes the resulting executable self-contained.

XmlView.cs
using?System;
using?System.Drawing;
using?System.Windows.Forms;
using?System.Xml;

class?XmlViewForm?:?Form
{
????GroupBox?DocumentGB;
????TextBox?Source;
????Button?LoadButton;
????ImageList?NodeImages;
????TreeView?XmlView;

????public?XmlViewForm?()
????{
????????//?Initialize?the?form's?properties
????????Text?= "XML?Viewer";
????????ClientSize?=?new?System.Drawing.Size?(488,?422);

????????//?Instantiate?the?form's?controls
????????DocumentGB?=?new?GroupBox?();
????????Source?=?new?TextBox?();
????????LoadButton?=?new?Button?();
????????XmlView?=?new?TreeView?();

????????//?Initialize?the?controls
????????Source.Anchor?=
????????????AnchorStyles.Top?│?AnchorStyles.Left?│?AnchorStyles.Right;
????????Source.Location?=?new?System.Drawing.Point?(16,?24);
????????Source.Size?=?new?System.Drawing.Size?(336,?24);
????????Source.TabIndex?=?0;
????????Source.Name?= "Source";

????????LoadButton.Anchor?=?AnchorStyles.Top?│?AnchorStyles.Right;
????????LoadButton.Location?=?new?System.Drawing.Point?(368,?24);
????????LoadButton.Size?=?new?System.Drawing.Size?(72,?24);
????????LoadButton.TabIndex?=?1;
????????LoadButton.Text?= "Load";
????????LoadButton.Click?+=?new?System.EventHandler?(OnLoadDocument);

????????DocumentGB.Anchor?=
????????????AnchorStyles.Top?│?AnchorStyles.Left?│?AnchorStyles.Right;
????????DocumentGB.Location?=?new?Point?(16,?16);
????????DocumentGB.Size?=?new?Size?(456,?64);
????????DocumentGB.Text?= "Document";
????????DocumentGB.Controls.Add?(Source);
????????DocumentGB.Controls.Add?(LoadButton);

????????NodeImages?=?new?ImageList?();
????????NodeImages.ImageSize?=?new?Size?(12,?12);
????????NodeImages.Images.AddStrip?(new?Bitmap?(GetType(),?"Buttons"));
????????NodeImages.TransparentColor?=?Color.White;

????????XmlView.Anchor?=?AnchorStyles.Top?│?AnchorStyles.Bottom?│
????????????AnchorStyles.Left?│?AnchorStyles.Right;
????????XmlView.Location?=?new?System.Drawing.Point?(16,?96);
????????XmlView.Size?=?new?System.Drawing.Size?(456,?308);
????????XmlView.ImageList?=?NodeImages;
????????XmlView.TabIndex?=?2;
????????XmlView.Name?= "XmlView";

????????//?Add?the?controls?to?the?form
????????Controls.Add?(DocumentGB);
????????Controls.Add?(XmlView);
????}

????void?OnLoadDocument?(object?sender,?EventArgs?e)
????{
????????try?{
????????????XmlDocument?doc?=?new?XmlDocument?();
????????????doc.Load?(Source.Text);
????????????XmlView.Nodes.Clear?();
????????????AddNodeAndChildren?(doc.DocumentElement,?null);
????????}
????????catch?(Exception?ex)??{
????????????MessageBox.Show?(ex.Message);
????????}
????}

????void?AddNodeAndChildren?(XmlNode?xnode,?TreeNode?tnode)
????{
????????TreeNode?child?=?AddNode?(xnode,?tnode);

????????if?(xnode.Attributes?!=?null)?{
????????????foreach?(XmlAttribute?attribute?in?xnode.Attributes)
????????????????AddAttribute?(attribute,?child);
????????}

????????if?(xnode.HasChildNodes)?{
????????????foreach?(XmlNode?node?in?xnode.ChildNodes)
????????????????AddNodeAndChildren?(node,?child);
????????}
????}

????TreeNode?AddNode?(XmlNode?xnode,?TreeNode?tnode)
????{
????????string?text?=?null;
????????TreeNode?child?=?null;

????????TreeNodeCollection?tnodes?=?(tnode?==?null)??
????????????XmlView.Nodes?:?tnode.Nodes;

????????switch?(xnode.NodeType)?{

????????case?XmlNodeType.Element:
????????case?XmlNodeType.Document:
????????????tnodes.Add?(child?=?new?TreeNode?(xnode.Name,?0,?0));
????????????break;

????????case?XmlNodeType.Text:
????????????text?=?xnode.Value;
????????????if?(text.Length?>?128)
????????????????text?=?text.Substring?(0,?128)?+ "...";
????????????tnodes.Add?(child?=?new?TreeNode?(text,?2,?2));
????????????break;

????????case?XmlNodeType.CDATA:
????????????text?=?xnode.Value;
????????????if?(text.Length?>?128)
????????????????text?=?text.Substring?(0,?128)?+ "...";
????????????text?=?String.Format?("<![CDATA]{0}]]>",?text);
????????????tnodes.Add?(child?=?new?TreeNode?(text,?3,?3));
????????????break;

????????case?XmlNodeType.Comment:
????????????text?=?String.Format?("<!--{0}-->",?xnode.Value);
????????????tnodes.Add?(child?=?new?TreeNode?(text,?4,?4));
????????????break;

????????case?XmlNodeType.XmlDeclaration:
????????case?XmlNodeType.ProcessingInstruction:
????????????text?=?String.Format?("<?{0}?{1}?>",?xnode.Name,
????????????????xnode.Value);
????????????tnodes.Add?(child?=?new?TreeNode?(text,?5,?5));
????????????break;

????????case?XmlNodeType.Entity:
????????????text?=?String.Format?("<!ENTITY?{0}>",?xnode.Value);
????????????tnodes.Add?(child?=?new?TreeNode?(text,?6,?6));
????????????break;

????????case?XmlNodeType.EntityReference:
????????????text?=?String.Format?("&{0};",?xnode.Value);
????????????tnodes.Add?(child?=?new?TreeNode?(text,?7,?7));
????????????break;

????????case?XmlNodeType.DocumentType:
????????????text?=?String.Format?("<!DOCTYPE?{0}>",?xnode.Value);
????????????tnodes.Add?(child?=?new?TreeNode?(text,?8,?8));
????????????break;

????????case?XmlNodeType.Notation:
????????????text?=?String.Format?("<!NOTATION?{0}>",?xnode.Value);
????????????tnodes.Add?(child?=?new?TreeNode?(text,?9,?9));
????????????break;

????????default:
????????????tnodes.Add?(child?=
????????????????new?TreeNode?(xnode.NodeType.ToString?(),?1,?1));
????????????break;
????????}
????????return?child;
????}

????void?AddAttribute?(XmlAttribute?attribute,?TreeNode?tnode)
????{
????????string?text?=?String.Format?("{0}={1}",?attribute.Name,
????????????attribute.Value);
????????tnode.Nodes.Add?(new?TreeNode?(text,?1,?1));
????}

????static?void?Main?()?
????{
????????Application.Run?(new?XmlViewForm?());
????}
}
Figure 13-7
Source code for XmlView.

Incidentally, the FCL includes a class named XmlDataDocument that’s closely related to and, in fact, derives from XmlDocument. XmlDataDocument is a mechanism for treating relational data as XML data. You can wrap an XmlDataDocument around a DataSet, as shown here:

DataSet?ds?=?new?DataSet?();
//?TODO:?Initialize?the?DataSet?with?a?database?query
XmlDataDocument?doc?=?new?XmlDataDocument?(ds);

This action layers an XML DOM over a DataSet and allows the DataSet’s contents to be read and written using XmlDocument semantics.

The XmlTextReader Class

XmlDocument is an efficient and easy-to-use mechanism for reading XML documents. It allows you to move backward, forward, and sideways within a document and even make changes to the document as you go. But if your intent is simply to read XML and you’re less interested in the structure of the document than its contents, there’s another way to go about it: the FCL’s XmlTextReader class. XmlTextReader, which, like XmlDocument, belongs to the System.Xml namespace, provides a fast, forward-only, read-only interface to XML documents. It’s stream-based like SAX. It’s more memory-efficient than XmlDocument, especially for large documents, because it doesn’t read an entire document into memory at once. And it makes it even easier than XmlDocument to read through a document searching for particular elements, attributes, or other content items.

Using XmlTextReader is simplicity itself. The basic idea is to create an XmlTextReader object from a file, URL, or other data source, and to call XmlTextReader.Read repeatedly until you find the content you’re looking for or reach the end of the document. Each call to Read advances an imaginary cursor to the next node in the document. XmlTextReader properties such as NodeType, Name, Value, and AttributeCount expose information about the current node. Methods such as GetAttribute, MoveToFirstAttribute, and MoveToNextAttribute let you access the attributes, if any, attached to the current node.

The following code fragment wraps an XmlTextReader around Guitars.xml and reads through the entire file node by node:

XmlTextReader?reader?=?null;

try?{
????reader?=?new?XmlTextReader?("Guitars.xml");
????reader.WhitespaceHandling?=?WhitespaceHandling.None;
????while?(reader.Read?())?{
????????Console.WriteLine?("Type={0}\tName={1}\tValue={2}",
????????????reader.NodeType,?reader.Name,?reader.Value);
????}
}
finally?{
????if?(reader?!=?null)
????????reader.Close?();
}

Running it against the XML document in Figure 13-3 produces the following output:

Type=XmlDeclaration?????Name=xml????????Value=version="1.0"
Type=Element????Name=Guitars????Value=
Type=Element????Name=Guitar?????Value=
Type=Element????Name=Make???????Value=
Type=Text???????Name=???Value=Gibson
Type=EndElement?Name=Make???????Value=
Type=Element????Name=Model??????Value=
Type=Text???????Name=???Value=SG
Type=EndElement?Name=Model??????Value=
Type=Element????Name=Year???????Value=
Type=Text???????Name=???Value=1977
Type=EndElement?Name=Year???????Value=
Type=Element????Name=Color??????Value=
Type=Text???????Name=???Value=Tobacco?Sunburst
Type=EndElement?Name=Color??????Value=
Type=Element????Name=Neck???????Value=
Type=Text???????Name=???Value=Rosewood
Type=EndElement?Name=Neck???????Value=
Type=EndElement?Name=Guitar?????Value=
Type=Element????Name=Guitar?????Value=
Type=Element????Name=Make???????Value=
Type=Text???????Name=???Value=Fender
Type=EndElement?Name=Make???????Value=
Type=Element????Name=Model??????Value=
Type=Text???????Name=???Value=Stratocaster
Type=EndElement?Name=Model??????Value=
Type=Element????Name=Year???????Value=
Type=Text???????Name=???Value=1990
Type=EndElement?Name=Year???????Value=
Type=Element????Name=Color??????Value=
Type=Text???????Name=???Value=Black
Type=EndElement?Name=Color??????Value=
Type=Element????Name=Neck???????Value=
Type=Text???????Name=???Value=Maple
Type=EndElement?Name=Neck???????Value=
Type=EndElement?Name=Guitar?????Value=
Type=EndElement?Name=Guitars????Value=

Note the EndElement nodes in the output. Unlike XmlDocument, XmlText-Reader counts an element’s start and end tags as separate nodes. XmlTextReader also includes whitespace nodes in its output unless told to do otherwise. Setting its WhitespaceHandling property to WhitespaceHandling.None prevents a reader from returning whitespace nodes.

Like XmlDocument, XmlTextReader treats attributes differently than other nodes and doesn’t return them as part of the normal iterative process. If you want to enumerate attribute nodes, you have to read them separately. Here’s a revised code sample that outputs attribute nodes as well as other nodes:

XmlTextReader?reader?=?null;

try?{
????reader?=?new?XmlTextReader?("Guitars.xml");
????reader.WhitespaceHandling?=?WhitespaceHandling.None;
????while?(reader.Read?())?{
????????Console.WriteLine?("Type={0}\tName={1}\tValue={2}",
????????????reader.NodeType,?reader.Name,?reader.Value);
????????if?(reader.AttributeCount?>?0)?{
????????????while?(reader.MoveToNextAttribute?())?{
????????????????Console.WriteLine?("Type={0}\tName={1}\tValue={2}",
????????????????????reader.NodeType,?reader.Name,?reader.Value);????????????}
????????}
????}
}
finally?{
????if?(reader?!=?null)
????????reader.Close?();
}

A common use for XmlTextReader is parsing an XML document and extracting selected node values. The following code sample finds all the Guitar elements that are accompanied by Image attributes and echoes the attribute values to a console window:

XmlTextReader?reader?=?null;

try?{
????reader?=?new?XmlTextReader?("Guitars.xml");
????reader.WhitespaceHandling?=?WhitespaceHandling.None;

????while?(reader.Read?())?{
????????if?(reader.NodeType?==?XmlNodeType.Element?&&
????????????reader.Name?== "Guitar" &&
????????????reader.AttributeCount?>?0)?{
????????????while?(reader.MoveToNextAttribute?())?{
????????????????if?(reader.Name?== "Image")?{
????????????????????Console.WriteLine?(reader.Value);
????????????????????break;
????????????????}
????????????}
????????}
????}
}
finally?{
????if?(reader?!=?null)
????????reader.Close?();
}

Run against Guitars.xml (Figure 13-3), this sample produces the following output:

MySG.jpeg
MyStrat.jpeg

It’s important to close an XmlTextReader when you’re finished with it so that the reader, in turn, can close the underlying data source. That’s why all the samples in this section call Close on their XmlTextReaders and do so in finally blocks.

The XmlValidatingReader Class

XmlValidatingReader is a derivative of XmlTextReader. It adds one important feature that XmlTextReader lacks: the ability to validate XML documents as it reads them. It supports three schema types: DTD, XSD, and XML-Data Reduced (XDR). Its Schemas property holds the schema (or schemas) that a document is validated against, and its ValidationType property specifies the schema type. ValidationType defaults to ValidationType.Auto, which allows XmlValidating-Reader to determine the schema type from the schema document provided to it. Setting ValidationType to ValidationType.None creates a nonvalidating reader—the equivalent of XmlTextReader.

XmlValidatingReader doesn’t accept a file name or URL as input, but you can initialize an XmlTextReader with a file name or URL and wrap an XmlValidatingReader around it. The following statements create an XmlValidating-Reader and initialize it with an XML document and a schema document:

XmlTextReader?nvr?=?new?XmlTextReader?("Guitars.xml");
XmlValidatingReader?reader?=?new?XmlValidatingReader?(nvr);
reader.Schemas.Add?("", "Guitars.xsd");

The first parameter passed to Add identifies the target namespace, if any, specified in the schema document. An empty string means the schema defines no target namespace.

Validating a document is as simple as iterating through all its nodes with repeated calls to XmlValidatingReader.Read:

while?(reader.Read?());

If the reader encounters well-formedness errors as it reads, it throws an XmlException. If it encounter validation errors, it fires ValidationEventHandler events. An application that uses an XmlValidatingReader can trap these events by registering an event handler:

reader.ValidationEventHandler?+=
????new?ValidationEventHandler?(OnValidationError);

The event handler receives a ValidationEventArgs containing information about the validation error, including a textual description of it (in ValidationEventArgs.Message) and an XmlSchemaException (in ValidationEvent-Args.Exception). The latter contains additional information about the error such as the position in the source document where the error occurred.

Figure 13-8 lists the source code for a console app named Validate that validates XML documents against XSD schemas. To use it, type the command name followed by the name or URL of an XML document and the name or URL of a schema document, as in

validate?guitars.xml?guitars.xsd

As a convenience for users, Validate uses an XmlTextReader to parse the schema document for the target namespace that’s needed to add the schema to the Schemas collection. (See the GetTargetNamespace method for details.) It takes advantage of the fact that XSDs, unlike DTDs, are XML documents themselves and can therefore be read using XML parsers.

Validate.cs
using?System;
using?System.Xml;
using?System.Xml.Schema;

class?MyApp
{
????static?void?Main?(string[]?args)
????{
????????if?(args.Length?<?2)?{
????????????Console.WriteLine?("Syntax:?VALIDATE?xmldoc?schemadoc");
????????????return;
????????}

????????XmlValidatingReader?reader?=?null;

????????try?{
????????????XmlTextReader?nvr?=?new?XmlTextReader?(args[0]);
????????????nvr.WhitespaceHandling?=?WhitespaceHandling.None;

????????????reader?=?new?XmlValidatingReader?(nvr);
????????????reader.Schemas.Add?(GetTargetNamespace?(args[1]),?args[1]);
????????????reader.ValidationEventHandler?+=
????????????????new?ValidationEventHandler?(OnValidationError);

????????????while?(reader.Read?());
????????}
????????catch?(Exception?ex)?{
????????????Console.WriteLine?(ex.Message);
????????}
????????finally?{
????????????if?(reader?!=?null)
????????????????reader.Close?();
????????}
????}

????static?void?OnValidationError?(object?sender,?ValidationEventArgs?e)
????{
????????Console.WriteLine?(e.Message);
????}

????public?static?string?GetTargetNamespace?(string?src)
????{
????????XmlTextReader?reader?=?null;

????????try?{
????????????reader?=?new?XmlTextReader?(src);
????????????reader.WhitespaceHandling?=?WhitespaceHandling.None;

????????????while?(reader.Read?())?{
????????????????if?(reader.NodeType?==?XmlNodeType.Element?&&
????????????????????reader.LocalName?== "schema")?{
????????????????????while?(reader.MoveToNextAttribute?())?{
????????????????????????if?(reader.Name?== "targetNamespace")
????????????????????????????return?reader.Value;
????????????????????}
????????????????}
????????????}
????????????return "";
????????}
????????finally?{
????????????if?(reader?!=?null)
????????????????reader.Close?();
????????}
????}
}
Figure 13-8
Utility for validating XML documents.
The XmlTextWriter Class

The FCL’s XmlDocument class can be used to modify existing XML documents, but it can’t be used to generate XML documents from scratch. XmlTextWriter can. It features an assortment of Write methods that emit various types of XML, including elements, attributes, comments, and more. The following example uses some of these methods to create an XML file named Guitars.xml containing a document element named Guitars and a subelement named Guitar:

XmlTextWriter?writer?=?null;

try?{
????writer?=?new?XmlTextWriter?("Guitars.xml",?System.Text.Encoding.Unicode);
????writer.Formatting?=?Formatting.Indented;

????writer.WriteStartDocument?();
????writer.WriteStartElement?("Guitars");
????writer.WriteStartElement?("Guitar");
????writer.WriteAttributeString?("Image", "MySG.jpeg");
????writer.WriteElementString?("Make", "Gibson");
????writer.WriteElementString?("Model", "SG");
????writer.WriteElementString?("Year", "1977");
????writer.WriteElementString?("Color", "Tobacco?Sunburst");
????writer.WriteElementString?("Neck", "Rosewood");
????writer.WriteEndElement?();
????writer.WriteEndElement?();
}
finally?{
????if?(writer?!=?null)
????????writer.Close?();
}

Here’s what the generated document looks like:

<?xml?version="1.0" encoding="utf-16"?>
<Guitars>
??<Guitar?Image="MySG.jpeg">
????<Make>Gibson</Make>
????<Model>SG</Model>
????<Year>1977</Year>
????<Color>Tobacco?Sunburst</Color>
????<Neck>Rosewood</Neck>
??</Guitar>
</Guitars>

Setting an XmlTextWriter’s Formatting property to Formatting.Indented before writing begins produces the indentation seen in the sample. Skipping this step omits the indents and the line breaks too. The default indentation depth is 2, and the default indentation character is the space character. You can change the indentation depth and indentation character using XmlTextWriter’s Indentation and IndentChar properties.