From 1a427d559320d3246b06ec61c6b5bb1daee87b12 Mon Sep 17 00:00:00 2001 From: Charlie Tonneslan Date: Sun, 17 May 2026 08:58:55 -0400 Subject: [PATCH] emit secondary tag handles (e.g. !!str) on Node::SetTag MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a node's tag started with "!!" — the YAML secondary tag handle, as in "!!str" or "!!int" — EmitFromEvents stripped a single '!' and emitted the rest as a LocalTag. The remaining string ("!str") then failed the LocalTag character regex on the inner '!' and the emitter bailed out with INVALID_TAG, truncating output after "!". Reproduction from #1373: YAML::Node root; YAML::Node s{"hello"}; s.SetTag("!!str"); root["some_string"] = s; root["some_int"] = 2; std::cout << root; // -> "some_string: !" Recognize the "!!" prefix in EmitProps and route it through SecondaryTag with the rest of the tag content. After this: some_string: !!str hello some_int: 2 Added two regression tests under NodeTest covering both the secondary ("!!") and primary ("!") handle paths. Closes #1373. Signed-off-by: Charlie Tonneslan --- src/emitfromevents.cpp | 7 ++++++- test/integration/load_node_test.cpp | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/emitfromevents.cpp b/src/emitfromevents.cpp index 350bde648..0d410c426 100644 --- a/src/emitfromevents.cpp +++ b/src/emitfromevents.cpp @@ -117,7 +117,12 @@ void EmitFromEvents::BeginNode() { void EmitFromEvents::EmitProps(const std::string& tag, anchor_t anchor) { if (!tag.empty() && tag != "?" && tag != "!"){ - if (tag[0] == '!') { + if (tag.size() >= 2 && tag[0] == '!' && tag[1] == '!') { + // Tags like "!!str" use the secondary tag handle; emit them through + // SecondaryTag so the rendered output keeps both bangs instead of + // failing the LocalTag regex check on the inner '!'. See #1373. + m_emitter << SecondaryTag(std::string(tag.begin()+2, tag.end())); + } else if (tag[0] == '!') { m_emitter << LocalTag(std::string(tag.begin()+1, tag.end())); } else { m_emitter << VerbatimTag(tag); diff --git a/test/integration/load_node_test.cpp b/test/integration/load_node_test.cpp index d7698434c..65366175e 100644 --- a/test/integration/load_node_test.cpp +++ b/test/integration/load_node_test.cpp @@ -231,6 +231,33 @@ TEST(NodeTest, EmitEmptyNode) { EXPECT_EQ("", std::string(emitter.c_str())); } +// Regression for #1373: emitting a node whose tag begins with "!!" +// (a YAML secondary tag handle, e.g. "!!str") used to bail out with +// INVALID_TAG and truncate the output after the first '!'. +TEST(NodeTest, EmitSetTagSecondaryHandle) { + Node root; + Node string_node{"hello"}; + string_node.SetTag("!!str"); + root["some_string"] = string_node; + root["some_int"] = 2; + + Emitter emitter; + emitter << root; + EXPECT_EQ("some_string: !!str hello\nsome_int: 2", + std::string(emitter.c_str())); +} + +TEST(NodeTest, EmitSetTagPrimaryHandle) { + Node root; + Node string_node{"hello"}; + string_node.SetTag("!mytag"); + root["v"] = string_node; + + Emitter emitter; + emitter << root; + EXPECT_EQ("v: !mytag hello", std::string(emitter.c_str())); +} + TEST(NodeTest, ParseNodeStyle) { EXPECT_EQ(EmitterStyle::Flow, Load("[1, 2, 3]").Style()); EXPECT_EQ(EmitterStyle::Flow, Load("{foo: bar}").Style());