Skip to content

Commit be4d7ad

Browse files
committed
Adding support for handling YAML Merge Key (#41)
Support for YAML Merge keys ( <<: [*dict1, *dict2] ) is added. The merge key is a specific scalar with value << (and tag !!merge) that implies that during node construction, the map (or sequence of maps) are merged into the current map. The priority rules are that each key from maps within the value associated with << are added iff the key is not yet present in the current map (and first map gets higher priority). Test cases have been added accordingly.
1 parent 51adc5f commit be4d7ad

File tree

4 files changed

+82
-3
lines changed

4 files changed

+82
-3
lines changed

include/yaml-cpp/exceptions.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ const char* const INVALID_ANCHOR = "invalid anchor";
8787
const char* const INVALID_ALIAS = "invalid alias";
8888
const char* const INVALID_TAG = "invalid tag";
8989
const char* const BAD_FILE = "bad file";
90+
const char* const MERGE_KEY_NEEDS_SINGLE_OR_SEQUENCE_OF_MAPS =
91+
"merge key needs either single map or sequence of maps";
9092

9193
template <typename T>
9294
inline const std::string KEY_NOT_FOUND_WITH_KEY(

src/nodebuilder.cpp

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ NodeBuilder::NodeBuilder()
1515
m_stack{},
1616
m_anchors{},
1717
m_keys{},
18+
m_mergeDicts{},
1819
m_mapDepth(0) {
1920
m_anchors.push_back(nullptr); // since the anchors start at 1
2021
}
@@ -71,8 +72,24 @@ void NodeBuilder::OnMapStart(const Mark& mark, const std::string& tag,
7172
m_mapDepth++;
7273
}
7374

75+
void MergeMapCollection(detail::node& map_to, detail::node& map_from,
76+
detail::shared_memory_holder& pMemory) {
77+
const detail::node& const_map_to = map_to;
78+
for (auto j = map_from.begin(); j != map_from.end(); j++) {
79+
detail::node* s = const_map_to.get(*j->first, pMemory);
80+
if (s == nullptr) {
81+
map_to.insert(*j->first, *j->second, pMemory);
82+
}
83+
}
84+
}
85+
7486
void NodeBuilder::OnMapEnd() {
7587
assert(m_mapDepth > 0);
88+
detail::node& collection = *m_stack.back();
89+
for (detail::node* n : m_mergeDicts) {
90+
MergeMapCollection(collection, *n, m_pMemory);
91+
}
92+
m_mergeDicts.clear();
7693
m_mapDepth--;
7794
Pop();
7895
}
@@ -107,15 +124,40 @@ void NodeBuilder::Pop() {
107124
m_stack.pop_back();
108125

109126
detail::node& collection = *m_stack.back();
110-
111127
if (collection.type() == NodeType::Sequence) {
112128
collection.push_back(node, m_pMemory);
113129
} else if (collection.type() == NodeType::Map) {
114130
assert(!m_keys.empty());
115131
PushedKey& key = m_keys.back();
116132
if (key.second) {
117-
collection.insert(*key.first, node, m_pMemory);
118-
m_keys.pop_back();
133+
detail::node& nk = *key.first;
134+
if (nk.type() == NodeType::Scalar &&
135+
((nk.tag() == "!!merge" && nk.scalar() == "<<") ||
136+
(nk.tag() == "?" && nk.scalar() == "<<"))) {
137+
if (node.type() == NodeType::Map) {
138+
m_mergeDicts.emplace_back(&node);
139+
m_keys.pop_back();
140+
} else if (node.type() == NodeType::Sequence) {
141+
for (auto i = node.begin(); i != node.end(); i++) {
142+
auto v = *i;
143+
if ((*v).type() == NodeType::Map) {
144+
m_mergeDicts.emplace_back(&(*v));
145+
} else {
146+
throw ParserException(
147+
node.mark(),
148+
ErrorMsg::MERGE_KEY_NEEDS_SINGLE_OR_SEQUENCE_OF_MAPS);
149+
}
150+
}
151+
m_keys.pop_back();
152+
} else {
153+
throw ParserException(
154+
node.mark(),
155+
ErrorMsg::MERGE_KEY_NEEDS_SINGLE_OR_SEQUENCE_OF_MAPS);
156+
}
157+
} else {
158+
collection.insert(*key.first, node, m_pMemory);
159+
m_keys.pop_back();
160+
}
119161
} else {
120162
key.second = true;
121163
}

src/nodebuilder.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ class NodeBuilder : public EventHandler {
6767

6868
using PushedKey = std::pair<detail::node*, bool>;
6969
std::vector<PushedKey> m_keys;
70+
Nodes m_mergeDicts;
7071
std::size_t m_mapDepth;
7172
};
7273
} // namespace YAML

test/integration/load_node_test.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,40 @@ TEST(LoadNodeTest, CloneAlias) {
173173
EXPECT_EQ(clone[0], clone);
174174
}
175175

176+
TEST(LoadNodeTest, MergeKeyA) {
177+
Node node = Load(
178+
"{x: &foo {a : 1,b : 1,c : 1}, y: &bar {d: 2, e : 2, f : 2, a : 2}, z: "
179+
"&stuff { << : *foo, b : 3} }");
180+
EXPECT_EQ(NodeType::Map, node["z"].Type());
181+
EXPECT_FALSE(node["z"]["<<"]);
182+
EXPECT_EQ(1, node["z"]["a"].as<int>());
183+
EXPECT_EQ(3, node["z"]["b"].as<int>());
184+
EXPECT_EQ(1, node["z"]["c"].as<int>());
185+
}
186+
187+
TEST(LoadNodeTest, MergeKeyB) {
188+
Node node = Load(
189+
"{x: &foo {a : 1,b : 1,c : 1}, y: &bar {d: 2, e : 2, f : 2, a : 2}, z: "
190+
"&stuff { << : *foo, b : 3}, w: { << : [*stuff, *bar], c: 4 }, v: { '<<' "
191+
": *foo } , u : {!!merge << : *bar} }");
192+
EXPECT_EQ(NodeType::Map, node["z"].Type());
193+
EXPECT_EQ(NodeType::Map, node["w"].Type());
194+
EXPECT_FALSE(node["z"]["<<"]);
195+
EXPECT_EQ(1, node["z"]["a"].as<int>());
196+
EXPECT_EQ(3, node["z"]["b"].as<int>());
197+
EXPECT_EQ(1, node["z"]["c"].as<int>());
198+
199+
EXPECT_EQ(1, node["w"]["a"].as<int>());
200+
EXPECT_EQ(3, node["w"]["b"].as<int>());
201+
EXPECT_EQ(4, node["w"]["c"].as<int>());
202+
EXPECT_EQ(2, node["w"]["d"].as<int>());
203+
EXPECT_EQ(2, node["w"]["e"].as<int>());
204+
EXPECT_EQ(2, node["w"]["f"].as<int>());
205+
206+
EXPECT_EQ(1, node["v"]["<<"]["a"].as<int>());
207+
EXPECT_EQ(2, node["u"]["<<"]["d"].as<int>());
208+
}
209+
176210
TEST(LoadNodeTest, ForceInsertIntoMap) {
177211
Node node;
178212
node["a"] = "b";

0 commit comments

Comments
 (0)