遍历 yaml-cpp 中的所有节点,包括递归 Anchor/Alias

Iterate Through All Nodes In yaml-cpp Including Recursive Anchor/Alias

给定一个 YAML::Node 我们如何访问该节点内的所有标量节点(以修改它们)?我最好的猜测是递归函数:

void parseNode(YAML::Node node) {
    if (node.IsMap()) {
        for (YAML::iterator it = node.begin(); it != node.end(); ++it) {
            parseNode(it->second);
        }
    }
    else if (node.IsSequence()) {
        for (YAML::iterator it = node.begin(); it != node.end(); ++it) {
            parseNode(*it);
        }
    }
    else if (node.IsScalar()) {
        // Perform modifications here.
    }
}

除了在一种情况下,这可以很好地满足我的需要:如果有递归 anchor/alias。 (我认为这是 YAML 1.2 规范允许的?yaml-cpp 在解析时肯定不会抛出 Exception)例如:

first: &firstanchor
  a: 5
  b: *firstanchor

上面的代码将重复跟随别名到锚点,直到崩溃(SEGFAULT)可能是由于堆栈问题。如何克服这一问题?

识别节点最明显的方法是使用它们的 m_pNode 指针;然而,这是不可公开查询的。下一个最佳方法是使用 bool Node::is(const Node& rhs) const,遗憾的是,它不允许我们在搜索循环时进行任何排序以加快性能。

由于您可能不仅想避免循环,而且还想避免两次下降到同一个子图中,因此您实际上需要记住所有访问过的节点。这意味着最终您会将所有节点存储在您遍历的子图中,并且具有 space 复杂度 O(n),并且由于我们可以使用的节点没有顺序,时间复杂度为 O(n²),因为每个节点必须与所有先前看到的节点进行比较。那是可怕

无论如何,这是执行此操作的代码:

class Visitor {
public:
  using Callback = std::function<void(const YAML::Node&)>;
  Visitor(Callback cb): seen(), cb(cb) {}

  void operator()(const YAML::Node &cur) {
    seen.push_back(cur);
    if (cur.IsMap()) {
      for (const auto &pair : cur) {
        descend(pair.second);
      }
    } else if (node.IsSequence()) {
      for (const auto &child : cur) {
        descend(child);
      }
    } else if (node.IsScalar()) {
      cb(cur);
    }
  }
private:
  void descend(const YAML::Node &target) {
    if (std::find(seen.begin(), seen.end(), target) != seen.end())
     (*this)(target);
  }

  std::vector<YAML::Node> seen;
  Callback cb;
};

(我们可以使用 std::find,因为 operator== 在内部使用 Node::is。)

然后你可以做

Visitor([](const YAML::Node &scalar) {
  // do whatever with the scalar
})(node);

(请原谅我的错误,我的 C++ 有点生疏;我希望你能理解。)