Frage

I have an XML structure along the lines of:

<root>
 <SomeElement>
  <AnotherElement>
   <ElementIWant x="1" y="1"/>
  </AnotherElement>
 </SomeElement>
 <SomeElement>
  <AnotherElement>
   <ElementIWant x="1" y="1"/>
   <ElementIWant x="2" y="1"/>
   <ElementIWant x="3" y="1"/>
  </AnotherElement>
 </SomeElement>
</root>

Which is being read into a boost::property_tree, There are 1..Many <SomeElement>s, and then at an arbitrary depth within that element there could be 1..Many <ElementIWant>s

Is there a way to iterate over the <ElementIWant> directly (in a single loop) in the order that they appear in the doc?

I have looked at equal_range

void iterateOverPoints()
{
     const char* test = 
     "<?xml version=\"1.0\" encoding=\"utf-8\"?><root>"
      "<SomeElement>"
       "<AnotherElement>"
        "<ElementIWant x=\"1\" y=\"1\"/>"
       "</AnotherElement>"
      "</SomeElement>"
      "<SomeElement>"
       "<AnotherElement>"
        "<ElementIWant x=\"1\" y=\"1\"/>"
        "<ElementIWant x=\"2\" y=\"1\"/>"
        "<ElementIWant x=\"3\" y=\"1\"/>"
       "</AnotherElement>"
      "</SomeElement>"
    "</root>";

    boost::property_tree::ptree message;
    std::istringstream toParse(test); 
    boost::property_tree::read_xml(toParse,result_tree);

    //Now we need to locate the point elements and set the x/y accordingly.
    std::pair< boost::property_tree::ptree::const_assoc_iterator,
               boost::property_tree::ptree::const_assoc_iterator > result =
         message.equal_range("ElementIWant");

    for( boost::property_tree::ptree::const_assoc_iterator it = result.first; 
           it != result.second; ++it )
    {
        std::cout  << it->first << " : ";
        const boost::property_tree::ptree& x = it->second.get_child( "<xmlattr>.x" );
        const boost::property_tree::ptree& y = it->second.get_child( "<xmlattr>.y" );
        std::cout << x.get_value<int>() << "," << y.get_value<int>() << "\n";
    }

    return;
}

However it seems to fail to return nodes (Which I suspect is because equal_range works at the level of the tree node supplied) Which brings me to the question above...

War es hilfreich?

Lösung

It is not possible to iterate over all elements directly; the documentation says

There is no way to iterate over the entire tree.

Now, you could use recursion, and apply STL algorithms at each level to mimic that; it does not fit your requirement of doing this in a single loop in my sample below, but it does works:

template <typename InputIt, typename OutputIt, typename Compare>
void collect(InputIt first, InputIt last, OutputIt dest, Compare comp)
{
    typedef typename std::iterator_traits<InputIt>::reference reference;

    std::copy_if (
        first, last, dest,
        [comp] (reference what) { return comp(what.first); });

    std::for_each (
        first, last,
        [dest, comp] (reference what) { collect(what.second.begin(), what.second.end(), dest, comp); });
}


std::vector<std::pair<std::string, ptree>> match;

collect(
    xml.begin (), xml.end (), std::back_inserter(match),
    [] (const std::string& key) { return key == "ElementIWant"; });

for (auto pair: match)
{
     std::cout << pair.first << std::endl;
}

Here is a version that is "fully" recursive and preserve the order of appearance:

template <typename InputIt, typename OutputIt, typename Compare>
void collect_recursive(InputIt first, InputIt last, OutputIt dest, Compare comp)
{
    typedef typename std::iterator_traits<InputIt>::reference reference;

    if (first == last)
    {
        return;
    }

    auto begin = first->second.begin ();
    auto end = first->second.end ();

    if (begin != end)
    {
        collect_recursive (begin, end, dest, comp);
    }

    if (comp (first->first))
    {
        dest = *first;
    }

    collect_recursive (++first, last, dest, comp);
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top