Introduction
After working with a couple of XML projects for the last six months, I am ready to share a class I wrote called XmlHelper
which got me through all the complexity of XML and made my life a lot simpler. If you are a XML beginner or have no idea about XML, this article should teach you the very basic concepts that you need to create any type of XML document. If you already have experience in XML, you will probably benefit by using the methods available in XmlHelper
. The article evolves from simple concepts about XML to creation of XML documents, to DataSource assignment from XML documents to ListBox
es and DataGrids, and finally to DataSource update from changes made on a DataGrid to update the source XML document.
Xml notation
I believe it’s always easier to learn with actual examples, so to give you a brief tutorial about XML, I created the following "ZooTable". By the way, I have no idea about Zoos; it was the first thing that came to my mind when I decided to give an example:
Name |
Address |
City |
State |
Zip |
Crazy Zoo |
32 Turtle Lane |
Austin |
Tx |
12345 |
Chicago Zoo |
23 Zebra Ave |
Chicago |
IL |
(null) |
Hungry Zoo |
45 Lion st |
Miami |
FL |
33122 |
There are two ways to represent this table, the first way which is the easier to read is by using "attributes". The second way to represent this table using XML is by using "elements".
XmlDocument representation using attributes
="1.0"="utf-8"
<ZooRoot>
<ZooTable Name="Crazy Zoo" Address="32 Turtle Lane"
City="Austin" State="TX" Zip="12345" />
<ZooTable Name="Chicago Zoo" Address="23 Zebra Ave"
City="Chicago" State="IL" />
<ZooTable Name="Hungry Zoo" Address="45 Lion st"
City="Miami" State="FL" Zip="33122" />
</ZooRoot>
The field "ZooRoot
" is an element and the fields named "ZooTable
" are also elements. The fields inside ZooTable
such as "Name
", "Address
", "City
", "State
", "Zip
" are the "attributes" of element ZooTable
.
Note: Each element is also called a node.
When you create an XML document you always need a root element and then inside that element you can place any number of elements. In this case the root element is "ZooRoot
" and the element inside this root is called "ZooTable
". Each column is represented by an attribute that corresponds to the column name.
I left the "Zip
" field of the second row empty intentionally so you can see how to represent null attributes in XML. As you see, it’s rather simple, basically a null value in XML is represented by omitting that attribute.
If you have to create a child table of element "ZooTable
" then the notation per line is slightly different, for example if we want to create a child table that contains a classification for the type of animals of each zoo in "ZooTable
" then you write the table as follows:
="1.0"="utf-8"
<ZooRoot>
<ZooTable Name="Crazy Zoo" Address="32 Turtle Lane"
City="Austin" State="TX" Zip="12345">
<Classification Type="Reptiles" />
<Classification Type="Birds" />
<Classification Type="Primates" />
</ZooTable>
<ZooTable Name="Chicago Zoo" Address="23 Zebra Ave"
City="Chicago" State="IL" >
<Classification Type="Fish" />
<Classification Type="Mammals" />
<Classification Type="Primates" />
</ZooTable>
<ZooTable Name="Hungry Zoo" Address="45 Lion st"
City="Miami" State="FL" Zip="33122" >
<Classification Type="Arachnids" />
<Classification Type="Rodents" />
</ZooTable>
</ZooRoot>
Note that the notation of ZooTable
element changed from:
<ElementName attribute1="…" attribute2="…" />
to:
<ElementName attribute1="…" attribute2="…" > </ElementName>
As you can see, it’s pretty simple to add child elements to the existing tables.
XmlDocument representation using elements
When I started learning about XML, I noticed that the preferred notation by Visual Studio was by using "Elements", so I thought that this was the way we should always represent data; however, as I learned more about XML notation, I learned that if you are dealing mainly with table representations then you can use either elements or attributes to represent your XML data; therefore, I usually avoid representing data using elements because its notation uses more space and it is harder to read if you ever have to debug it. Keep in mind that although XML representation using attributes is more readable, it is less powerful because an attribute cannot contain child elements whereas elements can contain any number of child elements.
The following representation is to show you the equivalent table using elements instead of attributes. For the rest of the examples I will use XML notation with attributes.
="1.0"="utf-8"
<ZooRoot>
<ZooTable>
<Name>Crazy Zoo</Name>
<Address>32 Turtle Lane</Address>
<City>Austin</City>
<State>TX</State>
<Zip>12345</Zip>
</ZooTable>
<ZooTable>
<Name>Chicago Zoo</Name>
<Address>23 Zebra Ave</Address>
<City>Chicago</City>
<State>IL</State>
</ZooTable>
<ZooTable>
<Name>Hungry Zoo</Name>
<Address>45 Lion st</Address>
<City>Miami</City>
<State>FL</State>
<Zip>33122</Zip>
</ZooTable>
</ZooRoot>
And the following XML representation shows you how to add the "Classification" child nodes using elements:
="1.0"="utf-8"
<ZooRoot>
<ZooTable>
<Name>Crazy Zoo</Name>
<Address>32 Turtle Lane</Address>
<City>Austin</City>
<State>TX</State>
<Zip>12345</Zip>
<Classification>
<Type>Reptiles</Type>
<Type>Birds</Type>
<Type>Primates</Type>
</Classification>
</ZooTable>
<ZooTable>
<Name>Chicago Zoo</Name>
<Address>23 Zebra Ave</Address>
<City>Chicago</City>
<State>IL</State>
<Classification>
<Type>Fish</Type>
<Type>Mammals</Type>
<Type>Primates</Type>
</Classification>
</ZooTable>
<ZooTable>
<Name>Hungry Zoo</Name>
<Address>45 Lion st</Address>
<City>Miami</City>
<State>FL</State>
<Zip>33122</Zip>
<Classification>
<Type>Arachnids</Type>
<Type>Rodents</Type>
</Classification>
</ZooTable>
</ZooRoot>
Using XmlHelper to create XmlDocuments from scratch
Now I will show you how to use XmlHelper
to programmatically create the "ZooTable
". From now on I will refer to elements as nodes. The first thing you have to do is create an XmlDocument
as follows:
XmlDocument doc = XmlHelper.CreateXmlDocument();
All the new nodes that you add to your XmlDocument
have to be created with the XmlDocument
to which they will belong. In this case all nodes created have to be created with "doc
" variable.
XmlNode newNode = doc.CreateElement("ZooRoot");
To add a child node use the "AppendChild
" method. Note that an XmlDocument
can only have one child element which means that to add more elements you have to add them to the child of your unique element that is located at the root of the XML document.
To add a child at the root of the XML document:
XmlNode rootNode = doc.AppendChild(newNode);
The variable "rootNode
" is a reference to the location of the node named "ZooRoot
" that we added above. This variable will become a parent to all the child nodes that will be added to it. Next, I will add the three rows that are child elements of "ZooRoot
":
newNode = doc.CreateElement("ZooTable");
XmlHelper.CreateAttribute(newNode, "Name", "Crazy Zoo");
XmlHelper.CreateAttribute(newNode, "Address", "32 Turtle Lane");
XmlHelper.CreateAttribute(newNode, "City", "Austin");
XmlHelper.CreateAttribute(newNode, "State", "TX");
XmlHelper.CreateAttribute(newNode, "Zip", "12345");
rootNode.AppendChild(newNode);
newNode = doc.CreateElement("ZooTable");
XmlHelper.CreateAttribute(newNode, "Name", "Chicago Zoo");
XmlHelper.CreateAttribute(newNode, "Address", "23 Zebra Ave");
XmlHelper.CreateAttribute(newNode, "City", "Chicago");
XmlHelper.CreateAttribute(newNode, "State", "IL");
rootNode.AppendChild(newNode);
newNode = doc.CreateElement("ZooTable");
XmlHelper.CreateAttribute(newNode, "Name", "Hungry Zoo");
XmlHelper.CreateAttribute(newNode, "Address", "45 Lion st");
XmlHelper.CreateAttribute(newNode, "City", "Miami");
XmlHelper.CreateAttribute(newNode, "State", "FL");
XmlHelper.CreateAttribute(newNode, "Zip", "33122");
rootNode.AppendChild(newNode);
As you can see, the procedure is monotonous and simple, you just need to understand that when you add a new element you always use the "AppendChild
" method which means that first you need to get a reference to the the parent element (or node) in order to add a child node. In this case we used "XmlHelper.CreateAttribute
" method to create all the necessary attributes of each element (or node). XmlHelper
also has methods to get or set the attribute value of a node using "XmlHelper.GetAttributeValue
" and "XmlHelper.SetAttributeValue
", but before we get into the details of how to use these methods, let’s add the child nodes named "Classification
".
To add child nodes all we need is access to whoever the parent node is, so it is as simple as changing the line of code from:
rootNode.AppendChild(newNode);
to:
XmlNode zooTableNode = rootNode.AppendChild(newNode);
Now that you have access to the parent node you can create any number of child nodes, and the following lines show you how to add the child nodes needed for "Crazy Zoo":
zooTableNode = rootNode.AppendChild(newNode);
XmlNode newChildNode = doc.CreateElement("Classification");
XmlHelper.CreateAttribute(newChildNode, "Type", "Reptiles");
zooTableNode.AppendChild(newChildNode);
newChildNode = doc.CreateElement("Classification");
XmlHelper.CreateAttribute(newChildNode, "Type", "Birds");
zooTableNode.AppendChild(newChildNode);
newChildNode = doc.CreateElement("Classification");
XmlHelper.CreateAttribute(newChildNode, "Type", "Primates");
zooTableNode.AppendChild(newChildNode);
As you can see the process is repetitive and simple. But what if the XmlDocument
is already created and you need to add child nodes to an existing document?
Before I can show you the answer, first I need to give you a brief tutorial of XPath queries.
XPath queries
XML Path Language (XPath) is a general purpose query notation that can be used to filter elements and attributes of XmlDocument
s. I normally use XPath queries to find specific nodes in a XML document based on the attribute values. You can find more examples about XPath queries here.
Single node selection
For example, if we want to access the second row of "ZooTable
" you can define a query as follows:
string xpathQuery = "/ZooRoot/ZooTable[@Name='Chicago Zoo']";
XmlNode selectedNode = doc.SelectSingleNode(xpathQuery);
There is a slight difference in the notation to access elements vs. the notation to access attributes.
Following is the equivalent notation when you are dealing with a XML document that uses elements instead of attributes:
string xpathQuery = "/ZooRoot/ZooTable/Name='Chicago Zoo'";
XmlNode selectedNode = doc.SelectSingleNode(xpathQuery);
So the only difference is that to access attributes you use brackets and the "@" sign.
You can also create filters that match multiple fields for example:
string xpathQuery =
"/ZooRoot/ZooTable/Name='Chicago Zoo' and @City=’Chicago’";
XmlNode selectedNode = doc.SelectSingleNode(xpathQuery);
This query is just for illustration purposes because it will return the same node. I just wanted to show you how to apply filters from multiple attributes.
In the examples above I used SelectSingleNode
method by accessing the XmlDocument variable "doc
". However, you can use this method from a node itself.
For example:
XmlNode anotherSelectedNode =
selectedNode.SelectSingleNode("Classification[@Type=’Reptiles’]";);
Multiple node selection
To select multiple nodes you use the SelectNodes
method.
Following is an example:
string xpathQuery =
"/ZooRoot/ZooTable/[@Name='Chicago Zoo']/Classification";
XmlNodeList nodeList = doc.SelectNodes(xpathQuery);
The example above shows how to select all the child nodes whose elements are named ‘Classification
’, but how do you access child nodes that have different element names?
The following example shows you how to access all child nodes of a node whose attribute name is ‘Chicago Zoo’:
string xpathQuery =
"/ZooRoot/ZooTable/[@Name='Chicago Zoo']/child::*";
XmlNodeList nodeList = doc.SelectNodes(xpathQuery);
Another way to access the child nodes of an element is by using the property ChildNodes
.
Insert nodes to existing nodes of an XmlDocument
Now that you are more familiar with XPath queries, I will show you how to programmatically insert the "Classification
" nodes: First you need to create a query to get all the available nodes:
string xpathQuery = "/ZooRoot/ZooTable";
XmlNodeList nodeList = doc.SelectNodes(xpathQuery);
Next, you can use a foreach
loop to append the classification nodes to each node that was returned from the query:
int index = 0;
foreach (XmlNode nodeFromList in nodeList)
{
foreach (string classificationName in classificationData[index])
{
newNode = doc.CreateElement("Classification");
XmlHelper.CreateAttribute(newNode, "Type",
classificationName);
nodeFromList.AppendChild(newNode);
}
index++;
}
How to modify attribute values of existing nodes
You can use the method XmlHelper.SetAttributeValue
to modify the value of an existing node; however, first you need to get access to the node that must be modified.
The following example shows how to modify all the classification nodes from ‘Primates’ to ‘Monkeys’:
XmlNodeList nodeList = doc.SelectNodes(
"/ZooRoot/ZooTable/Classification[@Type='Primates']");
foreach (XmlNode node in nodeList)
{
XmlHelper.SetAttributeValue(node, "Type", "Monkeys");
}
Inserting nodes with children into existing nodes
Occasionally, you will have some nodes that you want to copy from an existing location to another. In this example we want to add the "New York Zoo" which has the same "Classification" of animals as the "Chicago Zoo":
XmlNode nodeToCopy = doc.SelectSingleNode(
"/ZooRoot/ZooTable[@Name='Chicago Zoo']");
XmlNode newNode = doc.ImportNode(nodeToCopy, true);
XmlHelper.SetAttributeValue(newNode, "Name", "New York Zoo");
XmlHelper.SetAttributeValue(newNode, "Address",
"235 Congestion Ave");
XmlHelper.SetAttributeValue(newNode, "City", "New York");
XmlHelper.SetAttributeValue(newNode, "State", "NY");
XmlHelper.SetAttributeValue(newNode, "Zip", "44444");
nodeToCopy.ParentNode.AppendChild(newNode);
XmlHelper
has a method XmlHelper.CopyAttribute
that you can use to copy attributes from one node to another node as well as another way to copy all child nodes from Chicago Zoo to New York Zoo is:
XmlNode newNode = doc.CreateElement("ZooTable");
XmlHelper.CreateAttribute(newNode, "Name", "New York Zoo");
XmlHelper.CreateAttribute(newNode, "Address",
"235 Congestion Ave");
XmlHelper.CreateAttribute(newNode, "City", "New York");
XmlHelper.CreateAttribute(newNode, "State", "NY");
XmlHelper.CreateAttribute(newNode, "Zip", "44444");
XmlNode nodeToCopy = doc.SelectSingleNode(
"/ZooRoot/ZooTable[@Name='Chicago Zoo']");
foreach (XmlNode childNode in nodeToCopy.ChildNodes)
{
XmlNode newChildNode = doc.CreateElement("Classification");
XmlHelper.CreateAttribute(newChildNode, "Type", "");
XmlHelper.CopyAttribute(childNode, newChildNode, "Type");
newNode.AppendChild(newChildNode);
}
nodeToCopy.ParentNode.AppendChild(newNode);
Using XmlHelper to get DataSources
XmlHelper
has several static
methods that come in handy when you have to deal with DataTables as DataSource. Following are some of the methods and a brief description of how they can be used.
To convert a XmlNodeList
to a DataTable
use:
XmlHelper.GetDataTable( XmlNodeList nodelist )
To convert a XmlNodeList
to a DataTable
and set one of its columns as primary key column use:
XmlHelper.GetDataTable( XmlNodeList nodelist,
string primaryKeyColumn, bool autoIncrement)
To update XML nodes from a DataTable
to a XmlDocument
, use:
XmlHelper.UpdateChildNodesWithDataTable(XmlNode parentNode,
DataTable table, string keyField)
To copy the elements of a DataRow
into attributes of a Xmlnode
, use:
XmlHelper.CopyAttributes(DataRow fromRow, XmlNode toNode)
To get an array that represents a column whose name corresponds to an attribute:
XmlHelper.GetAttributeArray(XmlNodeList nodeList, string attributeName)
The following example illustrates how to fill a ListBox
with the Zoo names of our XmlDocument
:
XmlNodeList nodeList = doc.SelectNodes("/ZooRoot/ZooTable");
this.lstZoos.DataSource =
XmlHelper.GetAttributeArray(nodeList, "Name");
The following example illustrates how to fill a DataGrid with the data from ZooTable
:
XmlNodeList nodeList = doc.SelectNodes("/ZooRoot/ZooTable");
this.grdZoos.DataSource = XmlHelper.GetDataTable(nodeList);
The following example shows you how you can update an existing XmlDocument
with changes made on a DataGrid using the method XmlHelper.UpdateChildNodesWithDataTable
. Note that in order to use this method the DataTable from which we are updating the values requires a primary key column, so let’s go back and use again the method XmlHelper.GetDataTable
but this time we will set the column called "Name
" as the primary key column:
DataTable table = (DataTable)this.grdZoos.DataSource;
XmlNode parentNode = doc.SelectSingleNode("/ZooRoot");
XmlHelper.UpdateChildNodesWithDataTable(parentNode,
table, "Name");
Importing nodes from other XmlDocuments
As a quick note I want to show you how you can import nodes from another XmlDocument
.
In this example I will create an XmlDocument
called "docMaster
" which ideally could be used to store Zoo information from different countries. For this example, I just used one country but that should be enough to give you the basic idea.
XmlDocument docMaster = XmlHelper.CreateXmlDocument();
XmlNode newNode = docMaster.CreateElement("root");
XmlNode rootNode = docMaster.AppendChild(newNode);
newNode = docMaster.CreateElement("Country");
XmlHelper.CreateAttribute(newNode, "Name", "USA");
XmlNode usaRoot = rootNode.AppendChild(newNode);
XmlNodeList nodeList = doc.SelectNodes("/ZooRoot/ZooTable");
foreach (XmlNode sourceNode in nodeList)
{
newNode = docMaster.ImportNode(sourceNode, true);
usaRoot.AppendChild(newNode);
}
this.txtQueryResults.Text = XmlHelper.DocumentToString(docMaster);
Debugging your code with XmlHelper
XmlHelper
class has a couple of methods that are very helpful when troubleshooting your code. These methods are: XmlHelper.DocumentToString
and XmlHelper.NodeToString
. I normally use these methods in the Command window of Visual Studio debugger to verify the nodes created.
Example:
Trace.WriteLine(XmlHelper.NodeToString(currentNode))
Using Insert, Update, Delete and Query methods
The first release of XmlHelper
did not have methods Insert
, Update
, Delete
, and Query
. These methods were added on the second release of this article, thanks to Marc Clifton’s XmlDatabase. These methods will simplify your XML data manipulation even more.
Insert
This method has several overloads to simplify your work. First of all, note that all methods require an XmlDocument
and an xpath
string. The xpath
string must not include the root node as was shown in previous examples (ZooRoot
). Another important point to note is that since you are free to use actual XPath queries, you can have attributes as part of the xpath
string as will be shown in the examples.
Following is a list of the overloads and their functionality:
Creates a node at the bottom of the hierarchy, creating the tree as required:
XmlHelper.Insert(XmlDocument doc, string xpath)
Same as above but adds the fields (or attributes) with their corresponding values for each attribute:
XmlHelper.Insert(XmlDocument doc, string xpath,
string[] fields, string[] values)
Same as above but uses System.Collections.Specialized.NameValueCollection
instead of string arrays:
XmlHelper.Insert(XmlDocument doc, string xpath,
NameValueCollection nameValuePairs)
Creates node at the bottom of the hierarchy and creates the attributes based on the column names from the DataRow
and sets their values based on the values from the DataRow
:
XmlHelper.Insert(XmlDocument doc, string xpath, DataRow rowValues)
Same as above but inserts an entire DataTable
at the bottom of the hierarchy:
XmlHelper.Insert(XmlDocument doc, string xpath, DataTable table)
The method below is analogous to inserting a column of data for the specified field at the end of the tree hierarchy:
XmlHelper.Insert(XmlDocument doc, string xpath, string field, string[] values)
The following example shows a simplified version of how easily we can create the "ZooTable
" nodes:
doc = XmlHelper.CreateXmlDocument("ZooRoot");
string[] fields = new string[]
{"Name", "Address", "City", "State", "Zip"};
string[] values = new string[]
{"Crazy Zoo", "32 Turtle Lane", "Austin", "TX", "12345"};
XmlHelper.Insert(doc, "ZooTable", fields, values);
string[] fields2 = new string[]
{"Name", "Address", "City", "State"};
values = new string[]
{"Chicago Zoo", "23 Zebra Ave", "Chicago", "IL"};
XmlHelper.Insert(doc, "ZooTable", fields2, values);
values = new string[]
{"Hungry Zoo", "45 Lion st", "Miami", "FL", "33122"};
XmlHelper.Insert(doc, "ZooTable", fields, values);
Now let’s add the Classification nodes for each of the ZooTable
nodes. Notice that with xpath
query you can directly select the node to which you want to add new values.
values = new string[]{"Reptiles", "Birds", "Primates"};
XmlHelper.Insert(doc,
"ZooTable[@Name='Crazy Zoo']/Classification", "Type", values);
values = new string[] {"Fish", "Mammals", "Primates"};
XmlHelper.Insert(doc,
"ZooTable[@Name='Chicago Zoo']/Classification", "Type", values);
values = new string[] {"Arachnids", "Rodents"};
XmlHelper.Insert(doc,
"ZooTable[@Name='Hungry Zoo']/Classification", "Type", values);
The example above should give you a pretty good idea of how easily you can create any type of hierarchy for your application.
Update
In a previous example I showed you how to modify all the Classification nodes from ‘Primates’ to ‘Monkeys’. Now we will do the same but using the XmlHelper.Update
method:
XmlHelper.Update(doc,
"ZooTable/Classification[@Type='Primates']", "Type", "Monkeys");
Delete
Following are the overloaded methods available for Delete
:
Delete all records on the specified path:
XmlHelper.Delete(XmlDocument doc, string xpath)
Delete a field (or attribute) from all records on the specified path:
XmlHelper.Delete(XmlDocument doc, string xpath, string field)
Query
Following are the overloaded methods available for Query
:
Returns a DataTable
for all Rows on the path:
XmlHelper.Query(XmlDocument doc, string xpath)
Return a single string representing the value of the specified field for the first record encountered:
XmlHelper.QueryScalar(XmlDocument doc, string xpath, string field)
Returns a string array for the specified field for all rows on the path (analogous to getting a column of data):
XmlHelper.QueryField(XmlDocument doc, string xpath, string field)
Conclusion
The XmlHelper
class is very useful and reduces a lot of work when dealing with XML; however, you still need to understand a few basic concepts about XML so that you can do a proper debugging. Note that XmlHelper
is not able to handle multiple namespaces in your XML document. I hope this article and the XmlHelper
class can be of help to you.
References
For full reference of XML I recommend the book named "Applied XML Programming for .NET" by Dino Esposito. This has been an excellent source to learn XML programming using .NET. For reference mainly on XML, I recommend the book named "XML Pocket Consultant" by William R. Stanek. For reference on the Insert
, Update
, Delete
, and Query
original source, you can view Marc Clifton's XmlDatabase.
Revision history
- Sept-06-2005
- Sept-11-2005
- Added methods:
Insert
, Update
, Delete
, Query
.
- Sept-20-2005
- Fixed invalid query in section "XPath Queries/Multiple Node Selection".
- Fixed image URL.