diff --git a/include/yaml-cpp/exceptions.h b/include/yaml-cpp/exceptions.h index f6b2602ae..9c8da4ce9 100644 --- a/include/yaml-cpp/exceptions.h +++ b/include/yaml-cpp/exceptions.h @@ -87,6 +87,8 @@ const char* const INVALID_ANCHOR = "invalid anchor"; const char* const INVALID_ALIAS = "invalid alias"; const char* const INVALID_TAG = "invalid tag"; const char* const BAD_FILE = "bad file"; +const char* const MERGE_KEY_NEEDS_SINGLE_OR_SEQUENCE_OF_MAPS = + "merge key needs either single map or sequence of maps"; template inline const std::string KEY_NOT_FOUND_WITH_KEY( diff --git a/src/nodebuilder.cpp b/src/nodebuilder.cpp index bbaefac8a..a1daf8ec7 100644 --- a/src/nodebuilder.cpp +++ b/src/nodebuilder.cpp @@ -15,6 +15,7 @@ NodeBuilder::NodeBuilder() m_stack{}, m_anchors{}, m_keys{}, + m_mergeDicts{}, m_mapDepth(0) { m_anchors.push_back(nullptr); // since the anchors start at 1 } @@ -69,11 +70,29 @@ void NodeBuilder::OnMapStart(const Mark& mark, const std::string& tag, node.set_tag(tag); node.set_style(style); m_mapDepth++; + m_mergeDicts.emplace_back(); +} + +void MergeMapCollection(detail::node& map_to, detail::node& map_from, + detail::shared_memory_holder& pMemory) { + const detail::node& const_map_to = map_to; + for (auto j = map_from.begin(); j != map_from.end(); j++) { + detail::node* s = const_map_to.get(*j->first, pMemory); + if (s == nullptr) { + map_to.insert(*j->first, *j->second, pMemory); + } + } } void NodeBuilder::OnMapEnd() { assert(m_mapDepth > 0); + detail::node& collection = *m_stack.back(); + auto& toMerge = *m_mergeDicts.rbegin(); + for (detail::node* n : toMerge) { + MergeMapCollection(collection, *n, m_pMemory); + } m_mapDepth--; + m_mergeDicts.pop_back(); Pop(); } @@ -107,15 +126,40 @@ void NodeBuilder::Pop() { m_stack.pop_back(); detail::node& collection = *m_stack.back(); - if (collection.type() == NodeType::Sequence) { collection.push_back(node, m_pMemory); } else if (collection.type() == NodeType::Map) { assert(!m_keys.empty()); PushedKey& key = m_keys.back(); if (key.second) { - collection.insert(*key.first, node, m_pMemory); - m_keys.pop_back(); + detail::node& nk = *key.first; + if (nk.type() == NodeType::Scalar && + ((nk.tag() == "tag:yaml.org,2002:merge" && nk.scalar() == "<<") || + (nk.tag() == "?" && nk.scalar() == "<<"))) { + if (node.type() == NodeType::Map) { + m_mergeDicts.rbegin()->emplace_back(&node); + m_keys.pop_back(); + } else if (node.type() == NodeType::Sequence) { + for (auto i = node.begin(); i != node.end(); i++) { + auto v = *i; + if ((*v).type() == NodeType::Map) { + m_mergeDicts.rbegin()->emplace_back(&(*v)); + } else { + throw ParserException( + node.mark(), + ErrorMsg::MERGE_KEY_NEEDS_SINGLE_OR_SEQUENCE_OF_MAPS); + } + } + m_keys.pop_back(); + } else { + throw ParserException( + node.mark(), + ErrorMsg::MERGE_KEY_NEEDS_SINGLE_OR_SEQUENCE_OF_MAPS); + } + } else { + collection.insert(*key.first, node, m_pMemory); + m_keys.pop_back(); + } } else { key.second = true; } diff --git a/src/nodebuilder.h b/src/nodebuilder.h index c580d40e2..b053d58e4 100644 --- a/src/nodebuilder.h +++ b/src/nodebuilder.h @@ -67,6 +67,7 @@ class NodeBuilder : public EventHandler { using PushedKey = std::pair; std::vector m_keys; + std::vector m_mergeDicts; std::size_t m_mapDepth; }; } // namespace YAML diff --git a/test/integration/load_node_test.cpp b/test/integration/load_node_test.cpp index 9d0c790fd..aa81faa9d 100644 --- a/test/integration/load_node_test.cpp +++ b/test/integration/load_node_test.cpp @@ -173,6 +173,47 @@ TEST(LoadNodeTest, CloneAlias) { EXPECT_EQ(clone[0], clone); } +TEST(LoadNodeTest, MergeKeyA) { + Node node = Load( + "{x: &foo {a : 1,b : 1,c : 1}, y: &bar {d: 2, e : 2, f : 2, a : 2}, z: " + "&stuff { << : *foo, b : 3} }"); + EXPECT_EQ(NodeType::Map, node["z"].Type()); + EXPECT_FALSE(node["z"]["<<"]); + EXPECT_EQ(1, node["z"]["a"].as()); + EXPECT_EQ(3, node["z"]["b"].as()); + EXPECT_EQ(1, node["z"]["c"].as()); +} + +TEST(LoadNodeTest, MergeKeyB) { + Node node = Load( + "{x: &foo {a : 1,b : 1,c : 1}, y: &bar {d: 2, e : 2, f : 2, a : 2}, z: " + "&stuff { << : *foo, b : 3}, w: { << : [*stuff, *bar], c: 4 }, v: { '<<' " + ": *foo } , u : {!!merge << : *bar}, t: {!!merge << : *bar, h: 3} }"); + EXPECT_EQ(NodeType::Map, node["z"].Type()); + EXPECT_EQ(NodeType::Map, node["w"].Type()); + EXPECT_FALSE(node["z"]["<<"]); + EXPECT_EQ(1, node["z"]["a"].as()); + EXPECT_EQ(3, node["z"]["b"].as()); + EXPECT_EQ(1, node["z"]["c"].as()); + + EXPECT_EQ(1, node["w"]["a"].as()); + EXPECT_EQ(3, node["w"]["b"].as()); + EXPECT_EQ(4, node["w"]["c"].as()); + EXPECT_EQ(2, node["w"]["d"].as()); + EXPECT_EQ(2, node["w"]["e"].as()); + EXPECT_EQ(2, node["w"]["f"].as()); + + EXPECT_TRUE(node["v"]["<<"]); + EXPECT_EQ(1, node["v"]["<<"]["a"].as()); + + EXPECT_FALSE(node["u"]["<<"]); + EXPECT_EQ(2, node["u"]["d"].as()); + + EXPECT_FALSE(node["t"]["<<"]); + EXPECT_EQ(2, node["t"]["d"].as()); + EXPECT_EQ(3, node["t"]["h"].as()); +} + TEST(LoadNodeTest, ForceInsertIntoMap) { Node node; node["a"] = "b";