The XML Document Object Model (DOM) is a specification, developed by the W3C, that details how applications that access XML should behave. This includes the classes these applications should create, how they should read and write XML, and the features the classes should have. For the actual specifications, check out these pages at W3C's Web site:
Up until now, you haven't been paying any attention to the XML DOM. The XmlTextReader and XmlTextWriter don't implement the DOM because it introduces too much overhead. These two objects are built for fast, lightweight XML access. However, there are times when you need the full functionality of the DOM to edit, navigate, and otherwise modify your XML files, just like you need the DataSet to provide more functionality than a data reader.
The XmlNode class provides the basic functionality described by the XML DOM. It represents an element in the XML document tree, and it can be used to navigate child and parent nodes, as well as to edit and delete data. The XmlDocument class extends the XmlNode class and allows you to perform operations on the XML file as a whole, such as loading and saving files. There are numerous other classes in the DOM that are derivations of the XmlNode class, such as XmlElement and XmlAttribute, but you won't learn about these here. Figure 11.7 details the interaction of the XML DOM classes.
Figure 11.7. The interaction of the Microsoft XML classes.
The XmlNode represents one branch of the XML file, which includes all attributes, child elements, and both opening and closing tags. For example, the <title>…</title> tags in books.xml represent two elements, but only one node. Each node can have multiple child nodes, and each represents another branch. Thus, when you interact with XML using the DOM, you're viewing it as you would a traditional data store.
For example, when viewing data in Microsoft Access, you don't consider the start of one field and the end of the same field as two different objects they're part of one field. The XmlTextReader and Writer treated each object as an individual entity, but the DOM considers them one field. Figure 11.8 illustrates the concept of nodes in an XML file.
Figure 11.8. Nodes in an XML file.
Loading XML Data
You can load data into an XmlDocument in a number of different ways. The two most common methods are to load the data from an XmlTextReader or directly from the original XML file, both shown in the following code snippet:
dim xmldocument as XmlDocument = new XmlDocument() 'loading from an XmlTextReader dim reader as new XmlTextReader(server.MapPath("books.xml")) xmldocument.Load(reader) 'loading directly from a file xmldocument.Load(server.MapPath("books.xml"))
Why would you load it from an XmlTextReader if you could skip this step and go directly to the file? Often it may not be necessary to use the XmlDocument, so an XmlTextReader will suffice. However, if you ever need the functionality of the XmlDocument, you don't need to retrieve the data from the XML file all over again. Rather, you can just load it from the Reader object you're already using.
Once the data is loaded, you can use the XmlNode object to view the data in the file. For example, you can iterate through the nodes to display the data, just as you used the Read method of the XmlTextReader to iterate through elements. Listings 11.9 and 11.10 show an example.
Listing 11.9 Opening an XML File with XmlDocument
1: <%@ Page Language="VB" %> 2: <%@ Import Namespace="System.Xml" %> 3: 4: <script runat=server> 5: private i as integer 6: private strOutput as string = "" 7: 8: sub Page_Load(Sender as Object, e as EventArgs) 9: dim xmldoc as new XMLDocument() 10: 11: try 12: xmldoc.Load(Server.MapPath("books.xml")) 13: ShowTree(xmldoc.DocumentElement) 14: 15: catch ex as Exception 16: strOutput = "Error accessing XML file" 17: end try 18: 19: output.Text = strOutput 20: end sub
| || |
The first part of this file is simple. You create a new XmlDocument named xmldoc on line 9, and you load the data from your XML file on line 12. The ShowTree method, shown in Listing 11.10, loops through the nodes and displays the data. The DocumentElement property of the XmlDocument returns the first, or base, element in the file (the XML version declaration, in this case). You supply the ShowTree method with this starting point so it loops through the entire file.
Listing 11.10 Iterating Through an XML File with XMLNodes
21: sub ShowTree(node as XMLNode) 22: Dim attrnode As XmlNode 23: Dim map As XmlNamedNodeMap 24: 25: If Not(node.HasChildNodes) 26: strOutput += " <b>" & node.Name & _ 27: "</b> <" & _ 28: node.Value & "><br>" & vbcrlf 29: Else 30: strOutput += "<b>" & node.Name & "</b>" 31: If node.NodeType = XmlNodeType.Element Then 32: map = node.Attributes 33: For Each attrnode In map 34: strOutput += " <b>" & attrnode.Name & _ 35: "</b> <" & _ 36: attrnode.Value & "> " & vbcrlf 37: Next 38: End If 39: strOutput += "<br>" 40: End If 41: 42: If node.HasChildNodes then 43: node = node.FirstChild 44: While not IsNothing(node) 45: ShowTree(node) 46: node = node.NextSibling 47: end while 48: end if 49: end sub 50: </script> 51: 52: <html><body> 53: <asp:Label runat="server" /> 54: </body></html>
| || |
The ShowTree method uses a programming concept called recursion. A recursive function will call itself over and over again until some condition is exhausted, like a loop. In this case, the ShowTree method will display the information for a node, and if there are any children for that node, it will call itself for each child. If there are no more children, it simply moves to the next node in the file. In this way, this method works its way down the XML hierarchy to display all the child nodes. Figure 11.9 illustrates this concept.
Figure 11.9. Using recursion to loop through your nodes.
| || |
On line 25, if the node doesn't have any children, you simply want to display its name and value. strOutput is a string that you're using to collect the output; it will be displayed in the label (output) on line 53 once you're through. If there are children and the node is an element type, you display all the attributes belonging to that node. The XmlNamedNodeMap object, declared on line 23 and used on line 32, represents a collection of attributes for each node. You can then iterate through this collection to retrieve the values (lines 33 37).
If this node has any children, you want to start the recursive function. This is accomplished by the block of code on lines 42 48. First you assign your node variable to the first child node of the current node. You then call ShowTree starting from this child node, which will display any data associated with the node. If this node has children, you get its first child and repeat the process again. The while loop on line 44 and the NextSibling property on line 46 iterate through all the children of each node.
Imagine opening presents on Christmas. You open one box, examine its contents, and move on to the next box. But one of your relatives tries to be sneaky and gives you a very large box. Once you open it, you find another, smaller, box. In this box, you find another box, and so on, until you eventually find your present. The process comes down to this: You open one present, and if it doesn't have any smaller boxes inside, you move on to the next present. If it does, you have to open all the boxes inside before moving on to the next present. Recursion is a similar process you open all the XML branches, and if there are children, you open them up before moving on to the next branch.
The ShowTree method runs through the entire XML document to produce the output shown in Figure 11.10.
Figure 11.10. The output of Listings 11.8 and 11.9 when viewed through a browser.
|Do ||Don't |
|Do use XmlDocument and XmlNode when you need to modify or create XML data. ||Don't use XmlDocument and XmlNode when you only need to examine the data these objects introduce overhead that may slow down your application unnecessarily. |
Modifying XML Data
The XmlDocument object also provides many methods to create and modify XML documents. To add new elements, you can use the series of Create methods CreateComment, CreateAttribute, CreateNode, and so on. You can create virtually any type of element you need with these methods. For example:
'load data into xmldoc here dim eleBook as XmlElement = xmldoc.CreateElement _ ("Book") dim attStyle as XmlAttribute = xmldoc.CreateAttribute _ ("style") eleBook.SetAttributeNode(attStyle) eleBook.SetAttribute("style", "hardcover") dim root as XmlElement = xmldoc.Item("bookstore") root.AppendChild(eleBook)
You create a new XmlElement and XmlAttribute on lines 2 and 4, respectively. You then add the attribute to the element with the SetAttributeNode method, and you set a value for that attribute with the SetAttribute method. Finally, you add the new element to the book element of the XML document.
Modifying data is even easier. All you have to do is set the values that you want. For example:
if node.Name = "price" then node.Value = "8.99" end if
That's all there is to it! If you need to modify all of the values in a file, you can simply use recursion to loop through each element. Table 11.2 lists some of the methods used to create or modify values in the XmlDocument.
Table 11.2. Methods to Modify an XmlDocument
|Method ||Description |
|AppendChild ||Adds the specified node to the end of the list of children for the current node |
|CreateAttribute ||Creates an XmlAttribute with the specified name |
|CreateCDataSection ||Creates a CData section with the specified data |
|CreateComment ||Creates an XmlComment with the specified data |
|CreateElement ||Creates an XmlElement with the specified name |
|CreateNode ||Creates an XmlNode with the specified type, name, and namespace URI (to ensure unique naming schemes) |
|CreateTextNode ||Creates an XmlText with the specified data |
|CreateXmlDeclaration ||Creates an XmlDeclaration section with the specified version, encoding, and string specifying whether attributes are standalone; version must be "1.0" |
|GetElementById ||Returns the XmlElement with the specified ID |
|GetElementsByTagName ||Returns an XmlNodeList collection with all the elements that match the specified name |
|ImportNode ||Imports the specified node from another document; the Boolean value specifies whether or not to import all child nodes as well |
|InsertAfter ||Inserts the first XmlNode after the second XmlNode |
|InsertBefore ||Inserts the first XmlNode before the second XmlNode |
|PrependChild ||Adds the specified node to the beginning of the list of children for the current node |
|RemoveAll ||Removes all children and attributes of the current node |
|RemoveChild ||Removes the specified child node |
|ReplaceChild ||Replaces the second XmlNode with the first |
|WriteContentTo ||Saves all children of the current node to the specified XmlWriter (such as an XmlTextWriter) |
|WriteTo ||Saves the current node to the specified XmlWriter (such as an XmlTextWriter) |
Note that some of these methods also have overloaded versions that allow different parameters to be used. Please see the .NET Framework SDK documentation for more information.
The XmlElement and XmlNode objects share many of the same methods as the XmlDocument.
Use the Save method to write any changes back to a file:
If you want to append to an existing XML file, simply load all the data into an XmlDocument, make your changes, and save it to a file with the same name. The old file will be overwritten, but since the data was loaded into the XmlDocument, you'll have lost nothing.
Listing 11.11 loads an XML file and appends a new book node.
Listing 11.11 Using the DOM to Append Data
1: <%@ Page Language="VB" %> 2: <%@ Import Namespace="System.Xml" %> 3: 4: <script runat=server> 5: sub Page_Load(Sender as Object, e as EventArgs) 6: dim xmldoc as new XMLDocument() 7: dim strOutput as string = "" 8: try 9: xmldoc.Load(Server.MapPath("books.xml")) 10: dim eleBook as XmlElement = xmldoc.CreateElement _ 11: ("book") 12: dim attStyle as XmlAttribute = xmldoc. _ 13: CreateAttribute _ 14: ("style") 15: 16: eleBook.SetAttributeNode(attStyle) 17: eleBook.SetAttribute("style", "hardcover") 18: 19: dim root as XmlElement = xmldoc.Item("bookstore") 20: root.AppendChild(eleBook) 21: 22: xmldoc.Save(Server.MapPath("books.xml")) 23: 24: catch ex as Exception 25: strOutput = "Error accessing XML file" 26: end try 27: 28: output.Text = "Append operation successful" 29: end sub 30: </script> 31: 32: <html><body> 33: <asp:Label runat="server" /> 34: </body></html>
| || |
Lines 1 9 are typical create an XmlDocument and fill it with the contents of books.xml. On line 10 and 14 you create a new XmlElement named book and a new XmlAttribute to hold information about the book. Note that you create these new items from the existing XmlDocument xmldoc. This ensures that the new items follow the same format as the existing data.
On lines 16 and 17, you declare the attribute as part of the book element and set a value for the attribute. On line 19 you retrieve the root element, bookstore, so that you can append your new book element to it, as shown on line 20. Finally, you use the Save method to write the changes back to the books.xml file. No data has been lost.