Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to preserve node marks when cloning a node #1337

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion include/yaml-cpp/node/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <typename T>
struct convert;
Expand Down
4 changes: 2 additions & 2 deletions src/node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
24 changes: 14 additions & 10 deletions src/nodeevents.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions src/nodeevents.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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:
Expand Down
84 changes: 84 additions & 0 deletions test/integration/clone_node_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <yaml-cpp/yaml.h>

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 <typename Mark>
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<YAML::Mark> 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);
}
}