-->

Boost Property Tree: Remove attribute from a node

2019-06-08 14:51发布

问题:

I have the following XML file:

<?xml version="1.0" encoding="utf-8"?>
<gexf>
  <graph>
    <nodes>
      <node id="0" label="0" start="0" end="25"/>
      <node id="1" label="1" start="1"/>
      <node id="2" label="2" start="2"/>
      ...
    </nodes>
    <edges>
      <edge id="0" source="0" target="1" start="7" end="19"/>
      <edge id="1" source="0" target="2" start="8" end="20"/>
      ...
    </edges>
  </graph>
</gexf>

I want to remove the start and end attributes from the edge with source="0" and target="1".

The way I've tried to do this is in the following code. Assuming the XML file is named ptree_test.gexf I read it in, find the correct edge in the tree, and then attempt to use erase to get rid of the attributes.

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <iostream>

using boost::property_tree::ptree;

int main(int argc, char *argv[]) {

  ptree pt;

  read_xml("ptree_test.gexf", pt);

  // Now find edge (0, 1) and delete the start and end attributes
  ptree edge;
  int id1, id2;
  id1 = 0;
  id2 = 1;

  for(auto &e : pt.get_child("gexf.graph.edges")) {
    int s, t;
    s = e.second.get<int>("<xmlattr>.source");
    t = e.second.get<int>("<xmlattr>.target");

    // Check if this is the correct edge
    // Two checks because it can be reversed
    if((id1 == s && id2 == t) || (id1 == t && id2 == s)) {
      edge = e.second;
      break;
    }
  }

  for(auto & attr : edge.get_child("<xmlattr>")) {
    if(attr.first == "end" || attr.first == "start") {
      edge.erase(attr.first);
    }
  }

  write_xml(std::cout, pt);
  return 0;
}

This does not work. It doesn't remove the attribute. In fact, if I put in a debug statement that prints the return of edge.erase(attr.first) it shows 0.

回答1:

Before answering, I would like to again discourage you from using Boost.PropertyTree as a quick-and-dirty XML processing system. Please use a real XML parser; there are many to choose from, and some are quite effective and require little dependency maintenance.

Anyway, your problem comes from your use of erase. You are trying to erase an element from a list that you're iterating over. That's not going to work. Not without special coding for your loop.

So you can't use a range-based for-loop here. You have to use a real for-loop over the iterators.

auto &children = edge.get_child();
for(auto &attrIt = children.begin(); attrIt != children.end();)
{
  auto &attr = *attrIt;
  //Do stuff here.


  if(attr.first == "end" || attr.first == "start")
    attrIt = children.erase(attrIt);
  else
    ++attrIt;
}


回答2:

The main problem is that you are making a copy of the subtree in this line:

  edge = e.second;

and then modifying that copy instead of the original. Later, as @NicolBolas said you need an interator to erase. The full code looks like this:

int main(){
        boost::property_tree::ptree pt;
        read_xml("ptree_test.gexf", pt, boost::property_tree::xml_parser::trim_whitespace);
        int id1, id2;
        id1 = 0;
        id2 = 1;
        for(auto &e : pt.get_child("gexf.graph.edges")) {
            int s, t;
            s = e.second.get<int>("<xmlattr>.source");
            t = e.second.get<int>("<xmlattr>.target");
            // Check if this is the correct edge
            // Two checks because it can be reversed
            if((id1 == s && id2 == t) || (id1 == t && id2 == s)){
                auto &children = e.second.get_child("<xmlattr>");
                for(auto attrIt = children.begin(); attrIt != children.end(); ++attrIt){
                  if(attrIt->first == "end" || attrIt->first == "start")
                    attrIt = children.erase(attrIt);
                }
                break; // maybe shouldn't be here to keep looking?
            }
        }
        write_xml("ptree_test_copy.gexf", pt, std::locale(), bpt::xml_writer_settings<std::string>{'\t', 1});
}