diff --git a/include/yaml-cpp/node/node.h b/include/yaml-cpp/node/node.h index c9e9a0a4b..beb860c27 100644 --- a/include/yaml-cpp/node/node.h +++ b/include/yaml-cpp/node/node.h @@ -139,7 +139,7 @@ class YAML_CPP_API Node { YAML_CPP_API bool operator==(const Node& lhs, const Node& rhs); -YAML_CPP_API Node Clone(const Node& node); +YAML_CPP_API Node Clone(const Node& node, bool preserveMarks = false); template struct convert; diff --git a/src/node.cpp b/src/node.cpp index badc3110e..432791481 100644 --- a/src/node.cpp +++ b/src/node.cpp @@ -3,10 +3,10 @@ #include "nodeevents.h" namespace YAML { -Node Clone(const Node& node) { +Node Clone(const Node& node, bool preserveMarks) { NodeEvents events(node); NodeBuilder builder; - events.Emit(builder); + events.Emit(builder, preserveMarks); return builder.Root(); } } // namespace YAML diff --git a/src/nodeevents.cpp b/src/nodeevents.cpp index b1774fef3..b6b48e337 100644 --- a/src/nodeevents.cpp +++ b/src/nodeevents.cpp @@ -42,17 +42,17 @@ void NodeEvents::Setup(const detail::node& node) { } } -void NodeEvents::Emit(EventHandler& handler) { +void NodeEvents::Emit(EventHandler& handler, bool preserveMarks) { AliasManager am; handler.OnDocumentStart(Mark()); if (m_root) - Emit(*m_root, handler, am); + Emit(*m_root, handler, am, preserveMarks); handler.OnDocumentEnd(); } void NodeEvents::Emit(const detail::node& node, EventHandler& handler, - AliasManager& am) const { + AliasManager& am, bool preserveMarks) const { anchor_t anchor = NullAnchor; if (IsAliased(node)) { anchor = am.LookupAnchor(node); @@ -65,26 +65,30 @@ void NodeEvents::Emit(const detail::node& node, EventHandler& handler, anchor = am.LookupAnchor(node); } + auto getMark = [preserveMarks, &node]() { + return preserveMarks ? node.mark() : Mark(); + }; + switch (node.type()) { case NodeType::Undefined: break; case NodeType::Null: - handler.OnNull(Mark(), anchor); + handler.OnNull(getMark(), anchor); break; case NodeType::Scalar: - handler.OnScalar(Mark(), node.tag(), anchor, node.scalar()); + handler.OnScalar(getMark(), node.tag(), anchor, node.scalar()); break; case NodeType::Sequence: - handler.OnSequenceStart(Mark(), node.tag(), anchor, node.style()); + handler.OnSequenceStart(getMark(), node.tag(), anchor, node.style()); for (auto element : node) - Emit(*element, handler, am); + Emit(*element, handler, am, preserveMarks); handler.OnSequenceEnd(); break; case NodeType::Map: - handler.OnMapStart(Mark(), node.tag(), anchor, node.style()); + handler.OnMapStart(getMark(), node.tag(), anchor, node.style()); for (auto element : node) { - Emit(*element.first, handler, am); - Emit(*element.second, handler, am); + Emit(*element.first, handler, am, preserveMarks); + Emit(*element.second, handler, am, preserveMarks); } handler.OnMapEnd(); break; diff --git a/src/nodeevents.h b/src/nodeevents.h index efca9149e..662a64f75 100644 --- a/src/nodeevents.h +++ b/src/nodeevents.h @@ -31,7 +31,7 @@ class NodeEvents { NodeEvents& operator=(const NodeEvents&) = delete; NodeEvents& operator=(NodeEvents&&) = delete; - void Emit(EventHandler& handler); + void Emit(EventHandler& handler, bool preserveMarks = false); private: class AliasManager { @@ -52,8 +52,8 @@ class NodeEvents { }; void Setup(const detail::node& node); - void Emit(const detail::node& node, EventHandler& handler, - AliasManager& am) const; + void Emit(const detail::node& node, EventHandler& handler, AliasManager& am, + bool preserveMarks) const; bool IsAliased(const detail::node& node) const; private: diff --git a/test/integration/clone_node_test.cpp b/test/integration/clone_node_test.cpp new file mode 100644 index 000000000..c1fe2eb7a --- /dev/null +++ b/test/integration/clone_node_test.cpp @@ -0,0 +1,84 @@ +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include + +struct ComparableMark { + int pos; + int line, column; + + ComparableMark(int pos, int line, int column) + : pos(pos), line(line), column(column) {} +}; + +bool operator==(const YAML::Mark& a, const ComparableMark& b) { + return a.pos == b.pos && a.line == b.line && a.column == b.column; +} + +template +void PrintMark(const Mark& mark, std::ostream* os) { + *os << mark.line << ':' << mark.column << " (pos " << mark.pos << ')'; +} + +void PrintTo(const ComparableMark& mark, std::ostream* os) { + PrintMark(mark, os); +} + +namespace YAML { +void PrintTo(const Mark& mark, std::ostream* os) { PrintMark(mark, os); } +} // namespace YAML + +TEST(CloneNodeTest, PreserveMark) { + std::string yaml_str = R"( +scalar: value +sequence: [1, 2, 3] +"null": null +[1, 2, 3]: value # check non-scalar keys +)"; + + auto checkMarks = [](const YAML::Node& root_node) { + EXPECT_EQ(root_node.Mark(), ComparableMark(1, 1, 0)); + + const YAML::Node& scalar = root_node["scalar"]; + EXPECT_EQ(scalar.Mark(), ComparableMark(9, 1, 8)); + + const YAML::Node& sequence = root_node["sequence"]; + EXPECT_EQ(sequence.Mark(), ComparableMark(25, 2, 10)); + EXPECT_EQ(sequence[0].Mark(), ComparableMark(26, 2, 11)); + EXPECT_EQ(sequence[1].Mark(), ComparableMark(29, 2, 14)); + EXPECT_EQ(sequence[2].Mark(), ComparableMark(32, 2, 17)); + + const YAML::Node& null = root_node["null"]; + EXPECT_EQ(null.Mark(), ComparableMark(43, 3, 8)); + + YAML::Node sequence_key; + std::vector key_marks; + for (auto it = root_node.begin(); it != root_node.end(); ++it) { + // Not assuming any key order + key_marks.emplace_back(it->first.Mark()); + if (it->first.IsSequence()) { + sequence_key.reset(it->first); + } + } + + EXPECT_THAT(key_marks, + testing::UnorderedElementsAre( + ComparableMark(1, 1, 0), ComparableMark(15, 2, 0), + ComparableMark(35, 3, 0), ComparableMark(48, 4, 0))); + + ASSERT_TRUE(sequence_key); + EXPECT_EQ(sequence_key[0].Mark(), ComparableMark(49, 4, 1)); + EXPECT_EQ(sequence_key[1].Mark(), ComparableMark(52, 4, 4)); + EXPECT_EQ(sequence_key[2].Mark(), ComparableMark(55, 4, 7)); + }; + + YAML::Node root_node = YAML::Load(yaml_str); + { + SCOPED_TRACE("original node"); + checkMarks(root_node); + } + YAML::Node cloned_node = YAML::Clone(root_node, true); + { + SCOPED_TRACE("cloned node"); + checkMarks(cloned_node); + } +}