Search code examples
c++xpathboostpathboost-propertytree

XML Path to second child


I want to put a new value to a child inside a ptree via path (no iteration)

As example:

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

int main() {
  using boost::property_tree::ptree;

  ptree main;
  ptree children;
  children.add("Foo", "bar");
  main.add_child("child", children);
  main.add_child("child", children);

  ptree newChildren("Foo");
  main.put("child{2}.Foo", newChildren.data()); // <-- Access second element?

  // Output
  boost::property_tree::xml_writer_settings<std::string> settings('\t', 1);
  boost::property_tree::write_xml(std::cout, main, settings);

  std::cin.ignore();
  return 0;
}

The problem is, I can´t access the second child via the path. Is there any formatting which work?

I mean this {2} part:

"child{2}.Foo"

I tried <2>, [2], (2)... no luck... :(

Any hope for me? Thank you!


Solution

  • I will repeat: there is no xml parser/library in Boost.

    What you are (ab)using is Boost Property Tree. As you found, it's a "property tree" library, meaning it can do some things. Including writing and reading property trees.

    If you want general purpose XML stuff, consider using an XML library (What XML parser should I use in C++?).

    Without further ado:

    Live On Coliru

    #include <iostream>
    #include <boost/property_tree/ptree.hpp>
    #include <boost/property_tree/xml_parser.hpp>
    
    int main() {
        using boost::property_tree::ptree;
    
        ptree pt;
    
        {
            ptree children; children.add("Foo", "bar");
            pt.add_child("child", children);
            pt.add_child("child", children).put("MARK","so we know it's the second");
        }
    
        // Output
        boost::property_tree::xml_writer_settings<std::string> settings('\t', 1);
        //boost::property_tree::write_xml(std::cout, pt, settings);
    
        auto child1 = std::find_if(pt.begin(),        pt.end(), [](auto& node) { return node.first == "child"; });
        auto child2 = std::find_if(std::next(child1), pt.end(), [](auto& node) { return node.first == "child"; });
    
        if (child2 != pt.end())
        {
            boost::property_tree::write_xml(std::cout, child2->second, settings);
    
            ptree newChildren("Foo");
            child2->second.put("Sub.Foo", newChildren.data()).put("BYE", "ALL DONE"); // <-- Access second element?
            boost::property_tree::write_xml(std::cout << "\n\nAFTER EDITING:\n", child2->second, settings);
        }
    }
    

    Prints:

    <?xml version="1.0" encoding="utf-8"?>
    <Foo>bar</Foo>
    <MARK>so we know it&apos;s the second</MARK>
    
    
    AFTER EDITING:
    <?xml version="1.0" encoding="utf-8"?>
    <Foo>bar</Foo>
    <MARK>so we know it&apos;s the second</MARK>
    <Sub>
        <Foo>
            Foo
            <BYE>ALL DONE</BYE>
        </Foo>
    </Sub>
    

    UPDATE

    Improving the style with a helper function:

    Live On Coliru

    #include <iostream>
    #include <boost/property_tree/ptree.hpp>
    #include <boost/property_tree/xml_parser.hpp>
    
    template <typename Tree, typename Out>
    Out find_all(Tree& pt, typename Tree::path_type path, Out out) {
        if (path.empty()) {
            *out++ = pt;
            return out;
        }
    
        auto head = path.reduce();
        for (auto& child : pt)
            if (child.first == head)
                out = find_all(child.second, path, out);
    
        return out;
    }
    
    int main() {
        using boost::property_tree::ptree;
    
        ptree pt;
    
        {
            ptree children; children.add("Foo", "bar");
            pt.add_child("child", children);
            pt.add_child("child", children).put("MARK","so we know it's the second");
        }
    
        // Output
        boost::property_tree::xml_writer_settings<std::string> settings('\t', 1);
        //boost::property_tree::write_xml(std::cout, pt, settings);
    
        std::vector<std::reference_wrapper<ptree> > matches;
        find_all(pt, "child", back_inserter(matches));
    
        ptree& child2 = matches.at(1);
        child2.put("BYE", "ALL DONE");
    
        boost::property_tree::write_xml(std::cout, child2, settings);
    }
    

    Prints

    <?xml version="1.0" encoding="utf-8"?>
    <Foo>bar</Foo>
    <MARK>so we know it&apos;s the second</MARK>
    <BYE>ALL DONE</BYE>