c++boostboost-propertytree

How to modify node element with boost::property_tree and put_value(const Type &value)


I am fairly new to boost::property_tree and I am having a little trouble with what should be a simple task.

I have a default xml file of which is to be copied and made unique with parameters passed into via the ptree & modelModifier(...) function below. All I want to do is parse the xml into a ptree, and then modify the name field (amongst others, but lets just start with name) from "default" to whatever name is passed in from the object_name variable, then write that back into the original ptree.

The function pTreeIterator just iterates through each child and displays its contents.

xml

<?xml version='1.0'?>
<sdf version='1.7'>
  <model name='default'>
    <link name='link'>
      <inertial>
        <mass>3.14999</mass>
        <inertia>
          <ixx>2.86712</ixx>
          <ixy>0</ixy>
          <ixz>0</ixz>
          <iyy>2.86712</iyy>
          <iyz>0</iyz>
          <izz>0.524998</izz>
        </inertia>
        <pose>0 0 0 0 -0 0</pose>
      </inertial>
    </link>
  </model>
</sdf>

Code

void ptreeIterator(ptree & pt)
{
    using boost::property_tree::ptree;
    for (auto&it : pt)
    {
        std::cout << __FUNCTION__ << std::endl;
        std::cout << "------------------------------------------------------" << std::endl;
        std::cout << "Iteration output: " << std::endl;
        std::cout << "1: pt1: " << it.first << std::endl;

        if(pt.get_child_optional(it.first))
        {
            cout << "pt.get_child_optional(it.first) ---> " << it.first << endl;
            ptree pt2 = pt.get_child(it.first);
            for (auto&it2 : pt2)
            {
                std::cout << "\t2: " << "pt2: " << it2.first << " :: " << (std::string)it2.second.data() << std::endl;
                if(pt2.get_child_optional(it2.first))
                {
                    ptree pt3 = pt2.get_child(it2.first);
                    for (auto&it3 : pt3)
                    {
                        std::cout << "\t\t3: " << "pt3: " << it3.first << " :: " << (std::string)it3.second.data() << std::endl;
                    }
                }
            }
        }
    }
}

ptree & modelModifier(ptree &model, double px, double py, std::string object_name, uint16_t height)
{
    for(auto &it:model){
        cout << "it.first = " << it.first << endl;
        if(it.first=="model"){
            cout << "MODEL TAG" << endl;
            model.put_value(object_name);
        }
        ptreeIterator(model);
    }

}

int main(){
    ptree ptModel;

    const string filenameToInsert = "model.sdf";
    
    std::ifstream ifsModel(filenameToInsert,std::ifstream::binary);

    read_xml(ifsModel, ptModel, boost::property_tree::xml_parser::trim_whitespace);         

    modelModifier(ptModel, 0, 0, "TEST1234567", 30);
    
    return 0;
}

Output

it.first = model
it.second.data() 
ptreeIterator
------------------------------------------------------
Iteration output: 
1: pt1: model
pt.get_child_optional(it.first) ---> model
        2: pt2: <xmlattr> :: 
                3: pt3: name :: default

Expected Output

it.first = model
it.second.data() 
ptreeIterator
------------------------------------------------------
Iteration output: 
1: pt1: model
pt.get_child_optional(it.first) ---> model
        2: pt2: <xmlattr> :: 
                3: pt3: name :: TEST1234567

Solution

  • Firstly, your code has UB, since modelModifier doesn't return a value.

    The C-style cast in (std::string)it2.second.data() is extremely dangerous as it risks reinterpret_cast-ing unrelated types. There is no reason whatsoever for this kind of blunt casting. Just remove the cast!

    Also, ptreeIterator should probably take a ptree const&, not ptree&.

    With these fixed, the sample does NOT show the output you claim, instead it prints (Live On Coliru)

    it.first = sdf
    ptreeIterator
    ------------------------------------------------------
    Iteration output:
    1: pt1: sdf
    pt.get_child_optional(it.first) ---> sdf
            2: pt2: <xmlattr> ::
                    3: pt3: version :: 1.7
            2: pt2: model ::
                    3: pt3: <xmlattr> ::
                    3: pt3: link ::
    

    Now even in your question output, you clearly see the difference between the model node and its name attribute, which apparently you want to modify. Just write the code to access that:

    it.second.get_child("<xmlattr>.name").put_value(object_name);
    

    This would be correct, assuming that the attribute always exists and instead of ptModel you pass ptModel.get_child("sdf") to modifyModel).

    Other Notes: SIMPLIFY!

    That said, please simplify the whole thing!

      ptree pt2 = pt.get_child(it.first);
    

    Should have been something like

      ptree const& pt2 = it.second;
    

    And

    Listing

    Live On Coliru

    namespace Model {
        void display(ptree const& pt)
        {
            write_json(std::cout << __FUNCTION__ << "\n---------------\n", pt);
        }
    
        void modify(ptree& model, double, double, std::string object_name, uint16_t)
        {
            for (auto& it : model) {
                std::cout << "root = " << it.first << std::endl;
                it.second.get_child("model.<xmlattr>.name").put_value(object_name);
            }
        }
    }
    

    Prints

    root = sdf
    display
    ---------------
    {
        "sdf": {
            "<xmlattr>": {
                "version": "1.7"
            },
            "model": {
                "<xmlattr>": {
                    "name": "TEST1234567"
                },
                "link": {
                    "<xmlattr>": {
                        "name": "link"
                    },
                    "inertial": {
                        "mass": "3.14999",
                        "inertia": {
                            "ixx": "2.86712",
                            "ixy": "0",
                            "ixz": "0",
                            "iyy": "2.86712",
                            "iyz": "0",
                            "izz": "0.524998"
                        },
                        "pose": "0 0 0 0 -0 0"
                    }
                }
            }
        }
    }
    

    Bonus

    In the case that the name attribute might not already exist, the following code would create it on the fly:

    void modify(ptree& model, double, double, std::string object_name, uint16_t)
    {
        ptree value;
        value.put_value(object_name);
        for (auto& it : model) {
            std::cout << "root = " << it.first << std::endl;
            it.second.put_child("model.<xmlattr>.name", value);
        }
    }