如何区分两个 boost::property_tree?

Posted

技术标签:

【中文标题】如何区分两个 boost::property_tree?【英文标题】:How to diff two boost::property_tree? 【发布时间】:2014-04-07 14:06:49 【问题描述】:

请考虑两个 'boost::property_tree'-s。

ptree1:


    "node1" : 1,
    "node_that_only_appears_in_this_one" : 2,
    "node3" :
    
        "nested1" : 3,
        "nested2"
        
            "double_nested1" : 5,
            "double_nested2" : "foo"
        
    

ptree2:


    "node1" : 1,
    "node3" :
    
        "nested1" : 3,
        "nested_that_only_appears_in_this_one" : 5,
        "nested2"
        
            "double_nested1" : 5,
            "double_nested2" : "bar"
        
    

我怎样才能编写一个可以这样调用的通用函数:

ptree_whats_changed(ptree1, ptree2);

因此,在这种特殊情况下,返回值是另一个属性树,如下所示:


    "node3" :
    
        "nested_that_only_appears_in_this_one" : 5,
        "nested2"
        
            "double_nested2" : "foo"
        
    

从 ptree1 到 ptree2 时是否删除节点无关紧要 - 只需查看已修改或已添加的节点即可。

我的理解是 property_tree 将所有内容存储为一个平面列表(使用 . 分隔路径),因此这可能比看起来更容易,而不必递归。

【问题讨论】:

【参考方案1】:

Boost.Property(1.55)的当前版本不提供对mathematical relations的直接支持,比如有区别。但是,可以编写一个函数来遍历树并使用节点的完整路径和节点本身调用用户提供的函数。由于每次迭代都会提供节点的完整路径,因此算法可以轻松地access data 并使用get()put()add() 构造一个结果。

例如,这里有一个可以遍历树的函数:

/// @brief Walk a Boost.PropertyTree tree, invoking the binary function with
///        the full path and the current node.
template <typename Tree, typename Function>
void for_each(
    const Tree& tree, 
    Function fn,
    const typename Tree::path_type& parent_path = typename Tree::path_type())
 
  using path_type = typename Tree::path_type;
  for (auto&& value_pair: tree)
  
    auto current_path = parent_path / path_type(value_pair.first);
    fn(current_path, value_pair.second);
    for_each(value_pair.second, fn, current_path);
  

并且这个算法使用迭代来构造差异:

/// @brief Return tree with elements in @ref s but not in @ref t.
template <typename Tree>
Tree tree_difference(const Tree& s, const Tree& t)

  using data_type = typename Tree::data_type;
  Tree result;
  // Iterate 's', adding to the result when either a node in
  // 't' is not present in 's' or the node's values differ.
  for_each(s, 
    [&](const typename Tree::path_type& path, const Tree& node)
    
      auto value = t.template get_optional<data_type>(path);
      if (!value || (value.get() != node.data()))
        result.add(path, node.data());
    );
  return result;


这是一个complete example,它演示了在两棵树之间执行差异。我还添加了其他操作,例如并集、交集和对称差分,以在 tree_difference() 未提供确切所需结果的情况下展示可扩展性。

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/foreach.hpp>
#include <iostream>

namespace tree_ops 

/// @brief Walk a Boost.PropertyTree tree, invoking the binary function with
///        the full path and the current node.
template <typename Tree, typename Function>
void for_each(
    const Tree& tree, 
    Function fn,
    const typename Tree::path_type& parent_path = typename Tree::path_type())
 
  using path_type = typename Tree::path_type;
  for (auto&& value_pair: tree)
  
    auto current_path = parent_path / path_type(value_pair.first);
    fn(current_path, value_pair.second);
    for_each(value_pair.second, fn, current_path);
  


/// @brief Return tree with elements in @ref s but not in @ref t.
template <typename Tree>
Tree tree_difference(const Tree& s, const Tree& t)

  using data_type = typename Tree::data_type;
  Tree result;
  // Iterate 's', adding to the result when either a node in
  // 't' is not present in 's' or the node's values differ.
  for_each(s, 
    [&](const typename Tree::path_type& path, const Tree& node)
    
      auto value = t.template get_optional<data_type>(path);
      if (!value || (value.get() != node.data()))
        result.add(path, node.data());
    );
  return result;


/// @brief Return tree with elements from both @ref s and @ref t.
template <typename Tree>
Tree tree_union(const Tree& s, const Tree& t)

  // The result will always contain all values in @ref s.
  Tree result = s;
  // Iterate 't', add values to the result only if the node is
  // either not in 's' or the values are different.
  for_each(t, 
    [&](const typename Tree::path_type& path, const Tree& node)
    
      auto child = s.get_child_optional(path);
      if (!child || (child->data() != node.data()))
        result.add(path, node.data());
    );
  return result;


/// @brief Return tree with elements common to @ref s and @ref t.
template <typename Tree>
Tree tree_intersection(const Tree& s, const Tree& t)

  using data_type = typename Tree::data_type;
  Tree result;
  // Iterate 's', adding common elements found in 't' that have the same
  // value.
  for_each(s,
    [&](const typename Tree::path_type& path, const Tree& node)
    
      auto value = t.template get_optional<data_type>(path);
      if (value && (value.get() == node.data()))
        result.add(path, node.data());
    );
  return result;


/// @brief Return tree with elements in either @ref s or @ref t, but not
///        both.
template <typename Tree>
Tree tree_symmetric_difference(const Tree& s, const Tree& t)

  return tree_difference(tree_union(s, t), tree_intersection(s, t));


 // namespace tree_ops

// Expose mathematical tree operations with operators.

/// @brief Return tree with elements in @ref lhs but not in @ref rhs.
boost::property_tree::ptree operator-(
    const boost::property_tree::ptree& lhs,
    const boost::property_tree::ptree& rhs)

  return tree_ops::tree_difference(lhs, rhs);


/// @brief Return tree with elements in both @ref lhs and @ref rhs.
boost::property_tree::ptree operator|(
    const boost::property_tree::ptree& lhs,
    const boost::property_tree::ptree& rhs)

  return tree_ops::tree_union(lhs, rhs);


/// @brief Return tree with elements common to @ref lhs and @ref rhs.
boost::property_tree::ptree operator&(
    const boost::property_tree::ptree& lhs,
    const boost::property_tree::ptree& rhs)

  return tree_ops::tree_intersection(lhs, rhs);


/// @brief Return tree with elements in either @ref lhs or @ref rhs, but not
///        both.
boost::property_tree::ptree operator^(
    const boost::property_tree::ptree& lhs,
    const boost::property_tree::ptree& rhs)

  return tree_ops::tree_symmetric_difference(lhs, rhs);


int main()

  std::istringstream json1_stream 
      ""
      "  \"node1\" : 1,"
      "  \"node_that_only_appears_in_this_one\" : 2,"
      "  \"node3\" :"
      "  "
      "    \"nested1\" : 3,"
      "    \"nested2\" :"
      "    "
      "      \"double_nested1\" : 5,"
      "      \"double_nested2\" : \"foo\""
      "    "
      "  "
      "";

  std::istringstream json2_stream 
      ""
      "  \"node1\" : 1,"
      "  \"node3\" :"
      "  "
      "    \"nested1\" : 3,"
      "    \"nested_that_only_appears_in_this_one\" : 5,"
      "    \"nested2\" :"
      "    "
      "      \"double_nested1\" : 5,"
      "      \"double_nested2\" : \"bar\""
      "    "
      "  "
      "";

  boost::property_tree::ptree tree1, tree2;
  read_json(json1_stream, tree1);
  read_json(json2_stream, tree2);

  std::cout << "difference in tree2 and tree1:\n";
  write_json(std::cout, tree2 - tree1);

  std::cout << "union of tree1 and tree2:\n";
  write_json(std::cout, tree1 | tree2);

  std::cout << "intersection of tree1 and tree2:\n";
  write_json(std::cout, tree1 & tree2);

  std::cout << "symmetric difference of tree1 and tree2:\n";
  write_json(std::cout, tree1 ^ tree2);

产生以下输出:

difference in tree2 and tree1:

    "node3":
    
        "nested_that_only_appears_in_this_one": "5",
        "nested2":
        
            "double_nested2": "bar"
        
    

union of tree1 and tree2:

    "node1": "1",
    "node_that_only_appears_in_this_one": "2",
    "node3":
    
        "nested1": "3",
        "nested2":
        
            "double_nested1": "5",
            "double_nested2": "foo",
            "double_nested2": "bar"
        ,
        "nested_that_only_appears_in_this_one": "5"
    

intersection of tree1 and tree2:

    "node1": "1",
    "node3":
    
        "nested1": "3",
        "nested2":
        
            "double_nested1": "5"
        
    

symmetric difference of tree1 and tree2:

    "node_that_only_appears_in_this_one": "2",
    "node3":
    
        "nested2":
        
            "double_nested2": "foo",
            "double_nested2": "bar"
        ,
        "nested_that_only_appears_in_this_one": "5"
    

注意:由于直接或间接使用get_child(),如果树有重复的键,那么结果可能不是确定性的。

根据路径的不同,每个级别的结果可能不是完全确定的,即如果同一个键出现多次,则未指定选择哪个子级。这可能导致路径无法解析,即使此路径存在后代。示例:

*   a -> b -> c
*     -> b
*

如果“b”的解析选择第一个这样的节点,路径“a.b.c”会成功,如果选择第二个则失败。

更完整的算法实现可能需要遍历两棵树以完成,填充支持重复键的中间数据结构。然后将对中间数据结构执行操作,并根据结果构造一棵树。

【讨论】:

这太好了,谢谢! boost 1.64 有什么不同还是它也缺乏差异? 所以这实际上对&lt;User UserID="2"&gt;&lt;Name&gt;ADMIN&lt;/Name&lt;/User&gt;&lt;User UserID="3"&gt;&lt;Name&gt;NOTADMIN&lt;/Name&gt;&lt;/User&gt; 失败,报告 NOTADMIN 作为结果差异的变化,知道如何解决这个问题吗?

以上是关于如何区分两个 boost::property_tree?的主要内容,如果未能解决你的问题,请参考以下文章

如何区分自定义单元格中的两个文本字段?

如何区分具有对象坐标的两个图像?

如何区分 GitHub 中的两个分支?

如何自动区分两个 iOS 应用构建包

如何区分具有相同自定义单元格的两个 UITableView

如何使用两个提交按钮,并区分使用哪个提交表单? [复制]