jabroni
jabroni

Reputation: 316

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

Upvotes: 1

Views: 1069

Answers (1)

sehe
sehe

Reputation: 393174

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

  • the use of get_child_optional only to repeat with get_child is even more wasteful
  • Good practice is to separate output/query and mutation. So don't call ptreeIterator from inside modelModifier
  • Also, give functions a good descriptive name (so that you don't have sheepishly explain "The function pTreeIterator just iterates through each child and displays its contents" - just call it displayModel?)
  • Instead of painstakingly (and flawed) iterating the particular model and printing it in pretty confusing bespoke manner, just use write_xml/write_info/write_json to dump it in a reliable manner.

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

Upvotes: 1

Related Questions