Wednesday, February 29, 2012

How to recursively traverse boost property tree

Currently, I find the documentation for boost::property_tree to be terrible. Hence, I hope this example will help others to work out how to use BOOST_FOREACH and recursion to traverse down the boost property tree. Here's how to create a class that loads and XML file to a property_tree, then display the hierarchy and then saves to another XML file.

 #include <iostream>  
 #include <string>  
 #include <fstream>  
 #include <boost/foreach.hpp>  
 #include <boost/property_tree/ptree.hpp>  
 #include <boost/property_tree/xml_parser.hpp>  
 #include <locale>  
   
 using boost::property_tree::ptree;  
 using namespace std;  
   
 class configuration {  
  ptree pt;  
 public:  
  // constructor  
  configuration(const string& filename) {  
   load(filename); };  
  // internal property tree function  
  ptree load(const string& filename) {  
   return import_xml(filename); };  
  void save(const string& filename) {   
   export_xml(filename,pt); };  
  // import export property tree function  
  ptree property_tree(void) {   
   return pt; };  
  void display(void) {  
   display(0, pt); };  
 private:  
  ptree import_xml(const string& filename) {  
   ifstream input(filename.c_str());  
   read_xml(input, pt);  
   return pt; };  
  void export_xml(const string& filename, const ptree& pt) {  
   boost::property_tree::xml_writer_settings<char> w(' ',2);  
   write_xml(filename, pt, locale(), w); };  
  void display(const int depth, const ptree& tree) {  
   BOOST_FOREACH( ptree::value_type const&v, tree.get_child("") ) {  
    ptree subtree = v.second;  
    string nodestr = tree.get<string>(v.first);  
      
    // print current node  
    cout << string("").assign(depth*2,' ') << "* ";  
    cout << v.first;  
    if ( nodestr.length() > 0 )   
      cout << "=\"" << tree.get<string>(v.first) << "\"";  
    cout << endl;  
      
    // recursive go down the hierarchy  
    display(depth+1,subtree);  
   }  
  };  
 };  

#define DEFAULT_CALORIES 0  
   
 int main(void)  
 {  
  configuration cfg("test_input.xml");  
  cfg.display();  
  cfg.save("test_output.xml");  
    
  // example to grab data from property tree  
  int calories = cfg.property_tree().get<int>("collection.recipe.nutrition.<xmlattr>.calories",DEFAULT_CALORIES);  
  cout << "Calories = " << calories << endl;  
   
  return 0;  
 }  

Here is a sample test_input.xml file:

 <?xml version="1.0" encoding="UTF-8"?>  
 <collection>  
  <description>  
    Some recipes used for the XML tutorial.  
  </description>  
  <recipe>  
   <title>Beef Parmesan with Garlic Angel Hair Pasta</title>  
   <ingredient name="beef cube steak" amount="1.5" unit="pound"/>  
   ...  
   <preparation>  
    <step>  
     Preheat oven to 350 degrees F (175 degrees C).  
    </step>  
    ...  
   </preparation>  
   <comment>  
    Make the meat ahead of time, and refrigerate over night, the acid in the  
    tomato sauce will tenderize the meat even more. If you do this, save the  
    mozzarella till the last minute.  
   </comment>  
   <nutrition calories="1167" fat="23" carbohydrates="45" protein="32"/>  
  </recipe>  
  ...  
  <!--another comment-->  
 </collection>  

The standard output when applied to this file gives:

 * collection="  
  ...  
  "  
  * description="  
    Some recipes used for the XML tutorial.  
  "  
  * recipe="  
   ...  
   "  
   * title="Beef Parmesan with Garlic Angel Hair Pasta"  
   * ingredient  
    * <xmlattr>  
     * name="beef cube steak"  
     * amount="1.5"  
     * unit="pound"  
   * preparation="  
    ...  
   "  
    * step="  
     Preheat oven to 350 degrees F (175 degrees C).  
    "  
   * comment="  
    Make the meat ahead of time, and refrigerate over night, the acid in the  
    tomato sauce will tenderize the meat even more. If you do this, save the  
    mozzarella till the last minute.  
   "  
   * nutrition  
    * <xmlattr>  
     * calories="1167"  
     * fat="23"  
     * carbohydrates="45"  
     * protein="32"  
  * <xmlcomment>="another comment"  
   

Notice that boost::property_tree create a separate child for XML
attributes and XML comments.

2 comments:

  1. Great! Just what I was searching for...

    ReplyDelete
  2. Pretty good! Would you mind if I included this material in my own blog? I agree some of the existing Boost documentation is lacking...

    ReplyDelete