-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
yaml_write_archive.cc
266 lines (250 loc) · 8.91 KB
/
yaml_write_archive.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
#include "drake/common/yaml/yaml_write_archive.h"
#include <algorithm>
#include <utility>
#include <vector>
#include "third_party/com_github_jbeder_yaml_cpp/include/yaml-cpp/emitfromevents.h" // NOLINT
#include "drake/common/drake_assert.h"
namespace drake {
namespace yaml {
namespace {
constexpr const char* const kKeyOrder = "__key_order";
// This function uses the same approach as YAML::NodeEvents::Emit.
// https://github.com/jbeder/yaml-cpp/blob/release-0.5.2/src/nodeevents.cpp#L55
void RecursiveEmit(const YAML::Node& node, YAML::EmitFromEvents* sink) {
const YAML::Mark no_mark;
const YAML::anchor_t no_anchor = YAML::NullAnchor;
switch (node.Type()) {
case YAML::NodeType::Undefined: { return; }
case YAML::NodeType::Null: { return; }
case YAML::NodeType::Scalar: {
sink->OnScalar(no_mark, node.Tag(), no_anchor, node.Scalar());
return;
}
case YAML::NodeType::Sequence: {
// If all children are scalars, then format this sequence onto a
// single line; otherwise, format as a bulleted list.
auto style = YAML::EmitterStyle::Flow;
for (const auto& child : node) {
if (child.IsSequence() || child.IsMap()) {
style = YAML::EmitterStyle::Block;
}
}
sink->OnSequenceStart(no_mark, node.Tag(), no_anchor, style);
for (const auto& child : node) {
RecursiveEmit(child, sink);
}
sink->OnSequenceEnd();
return;
}
case YAML::NodeType::Map: {
// If there are no children, then format this map onto a single line;
// otherwise, format over multiple "key: value\n" lines.
auto style = YAML::EmitterStyle::Block;
if (node.size() == 0) {
style = YAML::EmitterStyle::Flow;
}
sink->OnMapStart(no_mark, node.Tag(), no_anchor, style);
// In yaml-cpp < 0.6.0, the iteration order of yaml-cpp NodeType::Map is
// not deterministic. If there is a __key_order node inserted (as part
// of the Accept() method in our header file), use it to specify output
// order; otherwise, use alphabetical order.
std::vector<std::string> key_order;
const YAML::Node key_order_node = node[kKeyOrder];
if (key_order_node) {
// Use Accept()'s ordering. (If SubstractDefaults has been called,
// some of the keys may have disappeared.)
for (const auto& item : key_order_node) {
const std::string& key = item.Scalar();
if (node[key].IsDefined()) {
key_order.push_back(key);
}
}
} else {
// Use alphabetical ordering.
for (const auto& yaml_key_value : node) {
key_order.push_back(yaml_key_value.first.Scalar());
}
std::sort(key_order.begin(), key_order.end());
}
for (const auto& string_key : key_order) {
const YAML::Node key(string_key);
RecursiveEmit(key, sink);
RecursiveEmit(node[string_key], sink);
}
sink->OnMapEnd();
return;
}
}
DRAKE_UNREACHABLE();
}
} // namespace
const char* const YamlWriteArchive::kKeyOrderName = kKeyOrder;
// Convert the given document to a string ala YAML::Dump, but emit Map nodes
// using a deterministic key ordering. (By default, the ordering build in to
// YAML::Dump is non-deterministic in < 0.6 and addition-order in >= 0.6. Once
// we are using >= 0.6, perhaps this function can evaporate.)
std::string YamlWriteArchive::YamlDumpWithSortedMaps(
const YAML::Node& document) {
YAML::Emitter emitter;
YAML::EmitFromEvents sink(emitter);
RecursiveEmit(document, &sink);
return emitter.c_str();
}
std::string YamlWriteArchive::EmitString(const std::string& root_name) const {
std::string result;
if (root_.IsNull()) {
if (root_name.empty()) {
result = "{}\n";
} else {
result = root_name + ":\n";
}
} else {
if (root_name.empty()) {
result = YamlDumpWithSortedMaps(root_) + "\n";
} else {
YAML::Node document;
document[root_name] = root_;
result = YamlDumpWithSortedMaps(document) + "\n";
}
}
return result;
}
namespace {
// Returns true iff x and y have a identical node types and values throughout
// their entire tree structure, but without trying to match representationally
// distinct but semantically equal values (e.g., 0x0a compared to 10 may return
// false even though they refer to the same integer).
//
// See https://github.com/jbeder/yaml-cpp/issues/274 for a feature request to
// provide this kind of function directly as part of yaml-cpp.
bool AreLexicallyEqual(const YAML::Node& x, const YAML::Node& y) {
DRAKE_DEMAND(x.IsDefined() && y.IsDefined());
const YAML::NodeType::value type = x.Type();
if (y.Type() != type) {
return false;
}
switch (type) {
case YAML::NodeType::Undefined: { DRAKE_UNREACHABLE(); }
case YAML::NodeType::Null: {
// Two nulls are always equal to each other.
return true;
}
case YAML::NodeType::Scalar: {
return x.Scalar() == y.Scalar();
}
case YAML::NodeType::Sequence: {
size_t size = x.size();
if (y.size() != size) {
return false;
}
for (size_t i = 0; i < size; ++i) {
if (!AreLexicallyEqual(x[i], y[i])) {
return false;
}
}
return true;
}
case YAML::NodeType::Map: {
if (x.size() != y.size()) {
return false;
}
if (x.Tag() != y.Tag()) {
return false;
}
for (const auto& x_key_value : x) {
const std::string& key = x_key_value.first.Scalar();
const YAML::Node& x_val = x_key_value.second;
const YAML::Node& y_val = y[key];
if (!y_val.IsDefined()) {
return false;
}
if (!AreLexicallyEqual(x_val, y_val)) {
return false;
}
}
return true;
}
}
DRAKE_UNREACHABLE();
}
// Implements YamlWriteArchive::EraseMatchingMaps recursively.
void DoEraseMatchingMaps(YAML::Node* x, const YAML::Node* y) {
// If x and y are different types, then no subtraction that can be done.
DRAKE_DEMAND((x != nullptr) && (y != nullptr));
DRAKE_DEMAND(x->IsDefined() && y->IsDefined());
const YAML::NodeType::value type = x->Type();
if (y->Type() != type) {
// If the types differ, we should not subtract.
return;
}
// If their type is non-map, then we do not subtract them. The reader's
// retain_map_defaults mode only merges default maps; it does not, e.g.,
// concatenate sequences.
switch (type) {
case YAML::NodeType::Undefined: { DRAKE_UNREACHABLE(); }
case YAML::NodeType::Null: { return; }
case YAML::NodeType::Scalar: { return; }
case YAML::NodeType::Sequence: { return; }
case YAML::NodeType::Map: {
break;
}
}
// Both x are y are maps. Remove from x any key-value pair that is identical
// within both x and y.
std::vector<std::string> keys_to_prune;
for (const auto& x_key_value : *x) {
const std::string& key = x_key_value.first.Scalar();
if (key == kKeyOrder) {
// Subtraction should always leave the special kKeyOrder node alone when
// considering which keys to prune. If any keys are unpruned, we still
// want to know in what order they should appear. The only way to remove
// a kKeyOrder node is when its enclosing Map is removed wholesale.
continue;
}
const YAML::Node& x_val = x_key_value.second;
const YAML::Node& y_val = (*y)[key];
if (!y_val.IsDefined()) {
// The key only appears in x, so we should not prune.
continue;
}
if (!AreLexicallyEqual(x_val, y_val)) {
// The values do not match, so we should not prune.
continue;
}
keys_to_prune.push_back(key);
}
for (const auto& key : keys_to_prune) {
x->remove(key);
}
// Recurse into any children of x and y with the same key name.
for (auto&& x_key_value : *x) {
const std::string& key = x_key_value.first.Scalar();
if (key == kKeyOrder) {
// Subtraction should always leave the special kKeyOrder node alone.
// (See the other kKeyOrder guard a few lines above for details.)
continue;
}
YAML::Node& x_val = x_key_value.second;
const YAML::Node& y_val = (*y)[key];
if (!y_val.IsDefined()) {
// The key only appears in x, so we cannot recurse.
continue;
}
if (x_val.Tag() != y_val.Tag()) {
// The maps are tagged differently, so we should not subtract their
// children, since they may have different semantics.
continue;
}
DoEraseMatchingMaps(&x_val, &y_val);
}
}
} // namespace
void YamlWriteArchive::EraseMatchingMaps(const YamlWriteArchive& other) {
// Note that the implementations of DoEraseMatchingMaps and AreLexicallyEqual
// are both recursive. It's possible that we could improve performance by
// merging them into a single recursive traveral. However, for readability
// they are kept separate until we see a proven need for that optimization.
DoEraseMatchingMaps(&(this->root_), &(other.root_));
}
} // namespace yaml
} // namespace drake