There are at least a couple of rapidxml characteristics you should be aware before start working with it.
Rapidxml parsing is destructive. The xml_document::parse() method gets in input a non-constant C-string of characters, that it uses as an its own internal buffer. If you want to keep your XML as it is, you'd better pass in a copy of it.
Preconditions are usually checked with assertions. Exceptions are thrown from the xml_document::parse() method only. Be careful in testing what you are passing to an asserting function (for instance, xml_node::last_node() requires the node to have at least a child (it asserts its first_node is not NULL), and try/catching the parse call.
I have written a test case (using the Google Test framework) that shows how to parse a simple XML and to read the information in it. Notice that I just read a document, without performing any editing on it, this keeps the example simple enough.
#include "rapidxml/rapidxml.hpp" #include <gtest/gtest.h> TEST(RapidXml, simple) { char buffer[] = "<root><first>one</first><second>two</second><third>whatever</third></root>"; // 1 rapidxml::xml_document<char> doc; // 2 ASSERT_NO_THROW(doc.parse<0>(buffer)); // 3 rapidxml::xml_node<char>* root = doc.first_node(); // 4 ASSERT_TRUE(root); ASSERT_STREQ("root", root->name()); // 5 bool fields[4] {}; // 6 for(rapidxml::xml_node<char>* node = root->first_node(); node != NULL; node = node->next_sibling()) // 7 { if(strcmp(node->name(), "first") == 0) // 8 { ASSERT_STREQ("one", node->value()); fields[0] = true; } else if(strcmp(node->name(), "second") == 0) { ASSERT_STREQ("two", node->value()); fields[1] = true; } else if(strcmp(node->name(), "third") == 0) // 9 { fields[2] = true; } else // 10 { fields[3] = true; // unexpected! std::cout << "Unexpected node: " << node->name() << std::endl; } } EXPECT_TRUE(fields[0]); // 11 EXPECT_TRUE(fields[1]); EXPECT_TRUE(fields[2]); EXPECT_FALSE(fields[3]); }1. Remember that rapidxml is going to change this C-string (NULL-terminated array of characters) for its own purposes.
2. The xml_document template class has a template parameter that defaults to char. If you want to save some typing you can rewrite this line without specifying the parameter, and using the char default:
rapidxml::xml_document<> doc3. xml_document::parse() expects an int as template parameter, pass zero to get the default behavior. In your code you should try/catch this call for rapidxml::parse_error exception (it extends the std::exception). Here I assert that it should not throw.
4. xml_document IS-A xml_node, so I call on doc the xml_node::first_node() method to get the first document child. If doc has no child, first_node() returns a NULL pointer, otherwise we have a pointer to that node.
5. I expect the root to be there, so I assert that it is not zero (AKA false), then I get its name and I assert it is as expected. xml_node IS-A xml_base, where we can see that the name() method never returns NULL, if the node has no name, an empty C-string is returned instead.
6. Root has three children. I want to ensure I see all of them and nothing more. This bunch of booleans keeps track of them. They are all initialized to false (through the handy C++ empty list initializer) and then, in the following loop, when I see one of them I set the relative flag to true. There are four booleans, and not three, because I want to flag also the case of an unexpected child.
7. The for-loop is initialized getting the first root child, then we get the next sibling, until we reach the end of the family (a NULL is returned). We should pay attention using xml_node::next_sibling(), since it asserts when the current node has no parent. But here we call next_sibling() on a node that is surely a children of another node.
8. For first and second node, we want to ensure it has a specific value, hence the assertion.
9. The third node could have any value, I just set the flag when I see it.
10. In case an unexpected node is detected, I keep track of this anomaly setting the relative flag.
11. Check if the expectations are confirmed.