XML has rapidly gained popularity. Enterprise applications are using XML as the main data format for data exchanges.
ADO.NET breaks away from the COM-based recordset and employs XML as its transport data format. Because XML is platform independent, ADO.NET extends the reach to include anyone who is able to encode/decode XML. This is a big advantage over ADO because a COM-based recordset is not platform independent.
Even though XML is text based and readable by humans, you still should have some way of programmatically reading, inspecting, and changing XML. This is the job of XML parsers. There are two kinds of XML parsers: tree-based and event-based. Depending on your needs, these two types of parsers should complement each other and serve you well.
Tree-based XML parsers read the XML file (or stream) in its entirety to construct a tree of XML nodes. Think of these XML nodes as your XML tag:
<car> <vin>VI00000383148374</vin> <make>Acura</make> <model>Integra</model> <year>1995</year> </car>
When parsed into a tree, this information would have one root
node—car
; and under car, there are four
nodes: vin
, make
,
model
, and year
. As you might
have suspected, if the XML stream is very large in nature, then a
tree-based XML parser might not be a good idea. The tree would be too
large and consume a lot of memory.
An event-based XML parser reads the XML stream as it goes. SAX (Simple API for XML) is a specification for this kind of parsing. The parser raises events as it reads the data, notifying the application of the tag or text the parser just read. It does not attempt to create the complete tree of all XML nodes as does the tree-based parser. Therefore, memory consumption is minimal. This kind of XML parser is ideal for going through large XML files to look for small pieces of data.
Microsoft implements both types of parsers in its XML parser, MSXML. Because XML is so powerful, Microsoft, among other industry leaders, incorporates XML usage in almost all the things they do. That includes, but is not limited to, the following areas:
XML+HTTP in SOAP
XML+SQL in SQL2000
XML+DataSet in ADO.NET
XML in Web Services and Web Services Discovery (DISCO) (see Chapter 6)
In this chapter, we discuss XML+Dataset in ADO.NET. XML in Web Services will be examined in the next chapter. Because XML is used everywhere in the .NET architecture, we also provide a high-level survey of the XML classes.
To understand the tree-based Microsoft XML parser, which supports the Document Object Model (DOM Level 2 Core standard), there are only a handful of objects you should know:
XmlNode and its derivatives
XmlNodeList, as collection XmlNode
XmlNamedNodeMap, as a collection of XmlAttribute
We will walk through a simple XML example to see how XML nodes are mapped into these objects in the XML DOM.
XmlNode is a base class that represents a single node in the XML document. In the object model, almost everything derives from XmlNode. This includes: XmlAttribute, XmlDocument, XmlElement, and XmlText, among other XML node types.
The following XML excerpt demonstrates mapping of XML tags to the node types in the DOM tree:
<books> <book category="How To"> <title>How to drive in DC metropolitan</title> <author>Jack Daniel</author> <price>19.95</price> </book> <book category="Fiction"> <title>Bring down the fence</title> <author>Jack Smith</author> <price>9.95</price> </book> </books>
After parsing this XML stream, you end up with the tree depicted in
Figure 5-6. It contains one root node, which is
just a derivative of XmlNode. This root node is of type XmlDocument.
Under this books
root node, you have two children,
also derivatives of XmlNode. This time, they are of type XmlElement.
Under each book
element node, there are four
children. The first child is category
. This
category
node is of type XmlAttribute, a
derivative of XmlNode. The next three children are of type
XmlElement: title
, author
, and
price
. Each of these elements has one child of
type XmlText.
As a base class, XmlNode supports a number of methods that aid in the constructing of the XML document tree. These methods include: AppendChild( ), PrependChild( ), InsertBefore( ), InsertAfter( ), and Clone( ).
XmlNode also supports a group of properties that aid in navigation within the XML document tree. These properties include: FirstChild, NextSibling, PreviousSibling, LastChild, ChildNodes, and ParentNode. You can use the ChildNodes property to navigate down from the root of the tree. For traversing backward, use the ParentNode property from any node on the tree.
Just as an XmlNode represents a single XML element,
XmlNodeList represents a collection of
zero or more XmlNodes. The ChildNodes property of the XmlNode is of
type XmlNodeList. Looking at the root node books
,
we see that its ChildNodes property would be a collection of two
XmlNodes. XmlNodeList supports enumeration, so we can iterate over
the collection to get to each of the XmlNode objects. We can also
index into the collection through a zero-based index.
Each of the book XmlElement objects would have a ChildNodes
collection that iterates over title
,
author
, and price
XmlElements.
Similar to XmlNodeList, XmlNamedNodeMap is also a
collection object. XmlNamedNodeMap is a collection of XmlAttribute
objects that enable both enumeration and indexing of attributes by
name. Each XmlNode has a property named Attributes. In the case of
the book elements, these collections contain only one attribute,
which is category
.
In addition to all methods and properties supported by XmlNode, this derivative of XmlNode adds or restricts methods and properties. Here, we inspect only XmlDocument as an example of a derivative of XmlNode.
XmlDocument extends XmlNode and adds a number of helper functions. These helper functions are used to create other types of XmlNodes such as XmlAttribute, XmlComment, XmlElement, and XmlText.. In addition to allowing for the creation of other XML node types, XmlDocument also provides the mechanism to load and save XML contents.
The following code demonstrates how an XmlDocument is programmatically generated with DOM:
using System; using System.Xml; public class XmlDemo { public static void Main( ) { // Code to demonstrate creating of XmlDocument programmatically XmlDocument xmlDom = new XmlDocument( ); xmlDom.AppendChild(xmlDom.CreateElement("", "books", "")); XmlElement xmlRoot = xmlDom.DocumentElement; XmlElement xmlBook; XmlElement xmlTitle, xmlAuthor, xmlPrice; XmlText xmlText; xmlBook= xmlDom.CreateElement("", "book", ""); xmlBook.SetAttribute("category", "", "How To"); xmlTitle = xmlDom.CreateElement("", "title", ""); xmlText = xmlDom.CreateTextNode("How to drive in DC metropolitan"); xmlTitle.AppendChild(xmlText); xmlBook.AppendChild(xmlTitle); xmlAuthor = xmlDom.CreateElement("", "author", ""); xmlText = xmlDom.CreateTextNode("Jack Daniel"); xmlAuthor.AppendChild(xmlText); xmlBook.AppendChild(xmlAuthor); xmlPrice = xmlDom.CreateElement("", "price", ""); xmlText = xmlDom.CreateTextNode("19.95"); xmlPrice.AppendChild(xmlText); xmlBook.AppendChild(xmlPrice); xmlRoot.AppendChild(xmlBook); xmlBook= xmlDom.CreateElement("", "book", ""); xmlBook.SetAttribute("category", "", "Fiction"); xmlTitle = xmlDom.CreateElement("", "title", ""); xmlText = xmlDom.CreateTextNode("Bring down the fence"); xmlTitle.AppendChild(xmlText); xmlBook.AppendChild(xmlTitle); xmlAuthor = xmlDom.CreateElement("", "author", ""); xmlText = xmlDom.CreateTextNode("Jack Smith"); xmlAuthor.AppendChild(xmlText); xmlBook.AppendChild(xmlAuthor); xmlPrice = xmlDom.CreateElement("", "price", ""); xmlText = xmlDom.CreateTextNode("9.95"); xmlPrice.AppendChild(xmlText); xmlBook.AppendChild(xmlPrice); xmlRoot.AppendChild(xmlBook); Console.WriteLine(xmlDom.InnerXml); } }
The XmlDocument also supports
LoadXml and Load methods, which build the
whole XML tree from the input parameter. LoadXml takes a string in
XML format, whereas Load can take a filename, a TextReader, or an
XmlReader. The following example continues where the previous one
left off. The XML tree is saved to a file named
books.xml
. Then this file is loaded back into a
different XML tree. This new tree outputs the same XML stream as the
previous one:
... xmlDom.Save("books.xml"); XmlDocument xmlDom2 = new XmlDocument( ); xmlDom2.Load("books.xml"); Console.WriteLine(xmlDom2.InnerXml);
The XmlReader object, which currently supports XML 1.0, is a fast, noncached, forward-only way of accessing streamed XML data. There are two derivatives of XmlReader: XmlTextReader and XmlNodeReader. Both of these readers read XML one tag at a time. The only difference between the two is the input to each reader. As the name implies, XmlTextReader reads a stream of pure XML text. XmlNodeReader reads a stream of nodes from an XmlDocument. The stream can start at the beginning of the XML file for the whole XmlDocument or only at a specific node of the XmlDocument for partial reading.
Consider the following XML excerpt for order processing. There are two orders, each with two or more items:
<Orders> <Order id="ABC001"> <Item code="101" qty="3" price="299.00">17in Monitor</Item> <Item code="102" qty="1" price="15.99">Keyboard</Item> <Item code="103" qty="2" price="395.95">CPU</Item> </Order> <Order id="ABC002"> <Item code="101b" qty="1" price="499.00">21in Monitor</Item> <Item code="102" qty="1" price="15.99">Keyboard</Item> </Order> </Orders>
The following block of code traverses and processes each order:
StringReader myStream = new StringReader("your XML here"); XmlTextReader xmlTxtRdr = new XmlTextReader(myStream); while(xmlTxtRdr.Read( )) { if(xmlTxtRdr.NodeType == XmlNodeType.Element && xmlTxtRdr.Name == "Order") { ProcessOrder(xmlTxtRdr); } } public void ProcessOrder(XmlTextReader reader) { while(!(reader.NodeType == XmlNodeType.EndElement && reader.Name == "Order") && reader.Read( )) { // Process Content of Order } }
Let’s take a closer look at what is going on. Once we have
established the XmlTextReader object with the stream of data from the
string, all we have to do is loop through and perform a
Read( )
operation until there is nothing else to read. While we are reading,
only when we come across a node of type XmlElement and a node named
Order
do we start to process the order. Inside the
ProcessOrder function, we read and process all items inside an order
until we encounter the end tag of Order
. In this
case, we return from the function and go back to looking for the next
Order
tag to process the next order.
The following block of code is similar to the last one, but it uses an XmlNodeReader object instead of an XmlTextReader object:
XmlDocument doc = new XmlDocument( ); doc.LoadXml("your XML here"); XmlNodeReader myXMLReader = new XmlNodeReader(doc); while (myXMLReader.Read( )) { if(myXMLReader.NodeType == XmlNodeType.Element && myXMLReader.Name == "Order") { ProcessOrder2(myXMLReader); } } public void ProcessOrder2(XmlNodeReader reader) { while(!(reader.NodeType == XmlNodeType.EndElement && reader.Name == "Order") && reader.Read( )) { // Process Content of Order } }
The XmlWriter object, which currently supports XML 1.0, is a fast, noncached way of writing streamed XML data. It also supports namespaces. The only derivative of XmlWriter is XmlTextWriter.
XmlWriter supports namespaces by providing a number of overloaded functions that take a namespace to associate with the element. If this namespace is already defined and there is an existing prefix, XmlWriter automatically writes the element name with the defined prefix. Almost all element-writing methods are overloaded to support namespaces.
The following code shows how to use an XmlTextWriter object to write a valid XML file:
XmlTextWriter writer = new XmlTextWriter("test.xml", new System.Text.ASCIIEncoding( )); writer.Formatting = Formatting.Indented; writer.Indentation = 4; writer.WriteStartDocument( ); writer.WriteComment("Comment"); writer.WriteStartElement("ElementName", "myns"); writer.WriteStartAttribute("prefix", "attrName", "myns"); writer.WriteEndAttribute( ); writer.WriteElementString("ElementName", "myns", "value"); writer.WriteEndElement( ); writer.WriteEndDocument( ); writer.Flush( ); writer.Close( );
This produces the following XML document in
test.xml
:
<?xml version="1.0" encoding="us-ascii"?> <!--Comment--> <ElementName prefix:attrName="" xmlns:prefix="myns" xmlns="myns"> <prefix:ElementName>value</prefix:ElementName> </ElementName>
XslTransform converts XML from one format to another. It is typically used in data-conversion programs or to convert XML to HTML for the purpose of presenting XML data in a browser. The following code demonstrates how such a conversion takes place:
using System; using System.Xml; // XmlTextWriter using System.Xml.Xsl; // XslTransform using System.Xml.XPath; // XPathDocument using System.IO; // StreamReader public class XSLDemo { public static void Main( ) { XslTransform xslt = new XslTransform( ); xslt.Load("XSLTemplate.xsl"); XPathDocument xDoc = new XPathDocument("Books.xml"); XmlTextWriter writer = new XmlTextWriter("Books.html", null); xslt.Transform(xDoc, null, writer); writer.Close( ); StreamReader stream = new StreamReader("Books.html"); Console.Write(stream.ReadToEnd( )); } }
The code basically transforms the XML in the
Books.xml
file, which we’ve seen earlier,
into HTML to be displayed in a browser. Figure 5-7
and Figure 5-8 show the source XML and the output
HTML when viewed in a browser.
The template XSL file that was used to transform the XML is as follows:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:template match = "/" > <html> <head><title>A list of books</title></head> <style> .hdr { background-color=#ffeedd; font-weight=bold; } </style> <body> <B>List of books</B> <table style="border-collapse:collapse" border="1"> <tr> <td class="hdr">Title</td> <td class="hdr">Author</td> <td class="hdr">Price</td> </tr> <xsl:for-each select="//books/book"> <tr> <td><xsl:value-of select="title"/></td> <td><xsl:value-of select="author"/></td> <td><xsl:value-of select="price"/></td> </tr> </xsl:for-each> </table> </body> </html> </xsl:template> </xsl:stylesheet>
One of the most important points in ADO.NET is the tight integration of DataSet with XML. DataSet can easily be streamed into XML and vice versa, making it easy to exchange data with any other components in the enterprise system. The schema of the DataSet can be loaded and saved as XML Schema Definition (XSD), as described earlier.
XmlDataDocument can be associated with DataSet. The following code excerpt illustrates how such an association takes place:
// construct the XmlDataDocument with the DataSet XmlDataDocument doc = new XmlDataDocument(m_ds); XmlNodeReader myXMLReader = new XmlNodeReader(doc); while (myXMLReader.Read( )) { // process the node based on the values of the properties // exposed by the XmlNodeReader class such as NodeType, Name, Value }
The previous section describing DataSet has already shown you that once we have a DataSet, we can persist the data inside the DataSet into an XML string or file. The previous code demonstrated how to convert the DataSet into an XmlDataDocument that we can manipulate in memory.
3.145.96.163