< Reading & Writing XML 6 | Main | Transforming XML 1 >


 

 

Reading and Writing XML 7

 

 

What we have in this page?

 

 

  1. The XmlNode Class...continue

  2. A Very Quick Reference

 

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.

 

Starting 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;

 

Adding header and class namespace codes to the top of CppDom.cpp. These lines reference the XML DLL and help you access the namespace members

 

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();

 

Changing the declaration of the C++ .NET main() function to include the command-line argument parameters

 

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]);

 

Adding code to the start of the main() function to check the number of arguments and save the path

 

5.        Create a new managed class named XmlBuilder, and give it an XmlDocument^ as a data member.

ref class XmlBuilder

{

    XmlDocument^ doc;

};

 

Creating a new managed class named XmlBuilder, and give it an XmlDocument^ as a data member

 

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");

    }

 

Adding a constructor that creates an XmlDocument object, and tell it to load the file that was specified on the command line

 

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);

}

 

 

 

 

Adding some code to the main() function to create an XmlBuilder object

 

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.

 

Copying the DTD and XML file to the project debug directory

 

C++ .NET XMLDocument program output example

 

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;

 

Adding private member variable to the XmlBuilder class

 

10.     Add the following code to the constructor to get the root node.

// Get the root of the tree

root = doc->DocumentElement;

 

Adding code to the constructor to get the root node

 

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;

 

Adding code to get the list of child nodes for the root

 

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);

 

Adding code code to get a list of child nodes and iterate over it

 

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;

 

Adding using namespace System::Collections; code near the top of the CppDom.cpp file

 

14.     If you run this code on the myxml.xml file, you should see output similar to the following.

 

XMLDocument, DOM program output example that shows root and child node of the XML file content

 

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);

            }

      }

}

 

Adding code to find the root element of the XML by using a public class member function named ProcessChildNodes

 

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);

}

 

Adding calls to ProcessChildNodes and PrintTree to the main() function

 

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.

 

 

 

 

C++ .NET loading, modifying XML code using DOM program output example

 

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;

}

A Very Quick Reference

 

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.

 

 

Part 1 | Part 2 | Part 3 | Part 4 | Part 5 | Part 6 | Part 7

 

 


< Reading & Writing XML 6 | Main | Transforming XML 1 >