What we have in this page?
The following exercise shows you how to use XmlDocument. You’ll write a program that reads the myxml XML file into memory and then inserts a new element into the structure.
1. Start a new Visual C++ CLR Console Application project named CppDom.
|
2. Add the two following lines to the top of CppDom.cpp. These lines reference the XML DLL and help you access the namespace members.
#using <System.xml.dll>
using namespace System::Xml;
3. You’re going to supply the name of the XML document to read when you run the program from the command line, so change the declaration of the main() function to include the command-line argument parameters, as shown here:
// Get the command line arguments
args = Environment::GetCommandLineArgs();
4. Add this code to the start of the main() function to check the number of arguments and save the path.
// Check for required arguments
if (args->Length < 2)
{
Console::WriteLine(L"Usage: {0} path", args[0]);
return -1;
}
String^ path = gcnew String(args[1]);
5. Create a new managed class named XmlBuilder, and give it an XmlDocument^ as a data member.
ref class XmlBuilder
{
XmlDocument^ doc;
};
You need a managed class because it will be necessary to pass the XmlDocument pointer around between functions. You could pass the pointer explicitly in the argument list of each function, but it’s better to make it a member of a class so that it can be accessed by all the member functions.
6. Add a constructor that creates an XmlDocument object, and tell it to load the file that was specified on the command line.
public:
XmlBuilder(String^ path)
{
// Create the XmlDocument
doc = gcnew XmlDocument();
// Load the data
doc->Load(path);
Console::WriteLine(L"Document loaded");
}
Unlike XmlTextReader, the XmlDocument class reads and parses the file when it’s constructed. Note that you’re not catching exceptions here. Something might go wrong when opening or parsing the file, but exceptions are left for the caller to handle.
7. Add some code to the main() function to create an XmlBuilder object. Make sure you are prepared to handle any exceptions that occur.
// Create a Builder and get it to read the file
try
{
XmlBuilder^ pf = gcnew XmlBuilder(path);
}
catch(Exception^ pe)
{
Console::WriteLine(pe->Message);
}
8. You can try building and running the code at this point. First copy the myxml.xml and mydtd.dtd files you created earlier into the project’s debug folder.
If you see the “Document loaded” message displayed when you run the program, you know that the document has been loaded and parsed.
The next step is to access the nodes in the tree. The current XML document contains three volcano elements; what you’ll do is find the second element and insert a new element after it. There are a number of ways in which you could do this, and we’ll just illustrate one method. It isn’t the most efficient way to do the job, but it does show how to use several XmlDocument and XmlNode methods and properties.
9. Continue working on the CppDom project. Start working with the tree by getting a pointer to its root. Because you’ll use this root several times, add an XmlNode^ member to the XmlBuilder class to hold the root, like this:
private:
XmlNode^ root;
10. Add the following code to the constructor to get the root node.
// Get the root of the tree
root = doc->DocumentElement;
DocumentElement returns you the top of the DOM tree. Note that this is not the root element of the XML document, which is one level down.
11. You also need to get the list of child nodes for the root. Because you’ll be using this list again, add an XmlNodeList^ member to the class to hold the list.
private:
XmlNodeList^ xnl;
12. The following code shows how you can get a list of child nodes and iterate over it. Add this code to the constructor:
// get the child node list
xnl = doc->ChildNodes;
IEnumerator^ ie = xnl->GetEnumerator();
while (ie->MoveNext() == true)
Console::WriteLine(L"Child: {0}", (dynamic_cast<XmlNode^>(ie->Current))->Name);
The ChildNodes property returns a list of child nodes as an XmlNodeList. The XmlNodeList is a typical .NET collection class, which means that you can get an enumerator to iterate over the nodes. The code iterates over the child nodes, printing the name of each. Note that because Current returns an Object^, it has to be cast to an XmlNode^ before you can use the Name property.
13. The IEnumerator interface is part of the System::Collections namespace, so you need to add the following code near the top of the CppDom.cpp file, after the other using directives:
using namespace System::Collections;
14. If you run this code on the myxml.xml file, you should see output similar to the following.
![]() |
The root of the tree has four child nodes: the XML declaration, the DOCTYPE declaration, a comment, and the root node.
Once you’ve verified the existence of the child nodes, you can remove the lines that declare and use the enumerator because you won’t need them again. Make sure you don’t remove the line that assigns the value to xnl! Here, we just comment it out.
15. Now that you’ve got the root of the tree, you need to find the root element of the XML by using a public class member function named ProcessChildNodes, as shown here:
void ProcessChildNodes()
{
// Declare an enumerator
IEnumerator^ ie = xnl->GetEnumerator();
while (ie->MoveNext() == true)
{
// Get a pointer to the node
XmlNode^ pNode = dynamic_cast<XmlNode^>(ie->Current);
// See if it is the root
if (pNode->NodeType == XmlNodeType::Element &&
pNode->Name->Equals(L"geology"))
{
Console::WriteLine(L" Found the root");
ProcessRoot(pNode);
}
}
}
The function creates an enumerator and iterates over the children of the root node. The root XML element will be of type XmlNodeType::Element and will have the name geology.
16. Once we’ve identified that element, the function ProcessRoot is then used to process the children of the root XML element. Add the public ProcessRoot member function just after the previous member function.
void ProcessRoot(XmlNode^ rootNode)
{
XmlNode^ pVolc = dynamic_cast<XmlNode^>(rootNode->ChildNodes->Item(1));
// Create a new volcano element
XmlElement^ newVolcano = CreateNewVolcano();
// Link it in
root->InsertBefore(newVolcano, pVolc);
}
The function is passed in the root node. Take note that the file we are working with has more than two volcano elements, and we know that we want to insert a new one before the second element. So, we can get a direct reference to the second element by using the Items property on ChildNodes to access a child node by index. In real code, you’d obviously need to put in a lot more checking to make sure you were retrieving the desired node.
17. Once the node has been retrieved, you call CreateNewVolcano to create a new volcano element. Then you use InsertBefore to insert the new one immediately before the node you just retrieved by index. Now add the public CreateNewVolcano function, which creates a new volcano element. To save space, we haven’t given the code for creating the whole element, but just enough that you can see it working. Add the following member function to the class.
XmlElement^ CreateNewVolcano()
{
// Create a new element
XmlElement^ newElement = doc->CreateElement(L"volcano");
// Set the name attribute
XmlAttribute^ pAtt = doc->CreateAttribute(L"name");
pAtt->Value = L"Mount St.Helens";
newElement->Attributes->Append(pAtt);
// Create the location element
XmlElement^ locElement = doc->CreateElement(L"location");
XmlText^ xt = doc->CreateTextNode(L"Washington State, USA");
locElement->AppendChild(xt);
newElement->AppendChild(locElement);
return newElement;
}
The function creates a new XmlElement for the volcano. Note that the node classes - XmlElement, XmlComment, and so on - don’t have public constructors, so you need to create them by calling the appropriate factory method. The name attribute gets appended to the element’s collection of attributes, and then the location element is created with its content. Building DOM trees like this is a process of creating new nodes and appending them to one another.
18. It would be useful to be able to print out the modified tree, so add a public function named PrintTree to the class, as shown here. Add the code just after the previous member function.
void PrintTree()
{
XmlTextWriter^ xtw = gcnew XmlTextWriter(Console::Out);
xtw->Formatting = Formatting::Indented;
doc->WriteTo(xtw);
xtw->Flush();
Console::WriteLine();
}
You’ve already seen the use of XmlTextWriter to create XML manually. You can also use it to output XML from a DOM tree, by linking it up to an XmlDocument, as shown in the preceding code.
19. Add calls to ProcessChildNodes and PrintTree to the main() function, and you can build and test the program.
try
{
XmlBuilder^ pf = gcnew XmlBuilder(path);
pf->ProcessChildNodes();
pf->PrintTree();
}
catch(Exception^ pe)
{
Console::WriteLine(pe->Message);
}
20. When you run the program, you’ll be able to see that the new node has been added to the tree. Remember that this operation has modified only the DOM tree in memory; the original XML file has not been changed. You need to ‘write’ it to save the changes.
A complete code for this part is given below.
// CppDom.cpp : main project file.
#include "stdafx.h"
#using <System.xml.dll>
using namespace System;
using namespace System::Xml;
using namespace System::Collections;
ref class XmlBuilder
{
XmlDocument^ doc;
public:
XmlBuilder(String^ path)
{
// Create the XmlDocument
doc = gcnew XmlDocument();
// Load the data
doc->Load(path);
Console::WriteLine(L"Document loaded");
// Get the root of the tree
root = doc->DocumentElement;
// get the child node list
xnl = doc->ChildNodes;
// IEnumerator^ ie = xnl->GetEnumerator();
//
// while (ie->MoveNext() == true)
// Console::WriteLine(L"Child: {0}",
// (dynamic_cast<XmlNode^>(ie->Current))->Name);
}
void ProcessChildNodes()
{
// Declare an enumerator
IEnumerator^ ie = xnl->GetEnumerator();
while (ie->MoveNext() == true)
{
// Get a pointer to the node
XmlNode^ pNode = dynamic_cast<XmlNode^>(ie->Current);
// See if it is the root
if (pNode->NodeType == XmlNodeType::Element &&
pNode->Name->Equals(L"geology"))
{
Console::WriteLine(L" Found the root");
ProcessRoot(pNode);
}
}
}
void ProcessRoot(XmlNode^ rootNode)
{
XmlNode^ pVolc = dynamic_cast<XmlNode^>(rootNode->ChildNodes->Item(1));
// Create a new volcano element
XmlElement^ newVolcano = CreateNewVolcano();
// Link it in
root->InsertBefore(newVolcano, pVolc);
}
XmlElement^ CreateNewVolcano()
{
// Create a new element
XmlElement^ newElement = doc->CreateElement(L"volcano");
// Set the name attribute
XmlAttribute^ pAtt = doc->CreateAttribute(L"name");
pAtt->Value = L"Mount St.Helens";
newElement->Attributes->Append(pAtt);
// Create the location element
XmlElement^ locElement = doc->CreateElement(L"location");
XmlText^ xt = doc->CreateTextNode(L"Washington State, USA");
locElement->AppendChild(xt);
newElement->AppendChild(locElement);
return newElement;
}
void PrintTree()
{
XmlTextWriter^ xtw = gcnew XmlTextWriter(Console::Out);
xtw->Formatting = Formatting::Indented;
doc->WriteTo(xtw);
xtw->Flush();
Console::WriteLine();
}
private:
XmlNode^ root;
XmlNodeList^ xnl;
};
int main(array<System::String ^> ^args)
{
// Get the command line arguments
args = Environment::GetCommandLineArgs();
// Check for required arguments
if (args->Length < 2)
{
Console::WriteLine(L"Usage: {0:s} path", args[0]);
return -1;
}
String^ path = gcnew String(args[1]);
// Create a Builder and get it to read the file
try
{
XmlBuilder^ pf = gcnew XmlBuilder(path);
pf->ProcessChildNodes();
pf->PrintTree();
}
catch(Exception^ pe)
{
Console::WriteLine(pe->Message);
}
return 0;
}
To | Do this |
Parse XML without validation. | Create an XmlTextReader, and pass it the name of a file. Then use the Read method to read nodes from the file. |
Parse XML with validation. | Create an XmlTextReader, and then use it to initialize an XmlValidatingReader. Create a handler function for validation events, and attach it to the ValidationEventHandler event of the XmlValidatingReader. |
Work with XML in memory. | Create an XmlDocument, and use its Load or LoadXml function to parse XML into a DOM tree in memory. |
Table 25. |