Skip to content

Commit

Permalink
[fix] re #205: escaping of special chars in double quoted scalars
Browse files Browse the repository at this point in the history
  • Loading branch information
biojppm committed Jan 24, 2022
1 parent 9b70b06 commit bda127a
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 41 deletions.
28 changes: 19 additions & 9 deletions src/c4/yml/parse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4302,30 +4302,25 @@ csubstr Parser::_filter_dquot_scalar(substr s)
}
else if(next == 'r')
{
//m_filter_arena.str[pos++] = '\r';
m_filter_arena.str[pos++] = '\r';
++i;
}
else if(next == 't')
{
m_filter_arena.str[pos++] = '\t';
++i;
}
else if(next == 'b')
{
m_filter_arena.str[pos++] = '\b';
++i;
}
else if(next == 'x')
{
m_filter_arena.str[pos++] = '\\';
m_filter_arena.str[pos++] = 'x';
++i; // loop will increment next
++i;
}
else if(next == 'u')
{
m_filter_arena.str[pos++] = '\\';
m_filter_arena.str[pos++] = 'u';
++i; // loop will increment next
++i;
}
else if(next == '\\')
{
Expand All @@ -4337,6 +4332,21 @@ csubstr Parser::_filter_dquot_scalar(substr s)
m_filter_arena.str[pos++] = next;
++i;
}
else if(next == 'b')
{
m_filter_arena.str[pos++] = '\b';
++i;
}
else if(next == 'f')
{
m_filter_arena.str[pos++] = '\f';
++i;
}
else if(next == '0')
{
m_filter_arena.str[pos++] = '\0';
++i;
}
_c4dbgfdq("[%zu]: backslash...sofar=[%zu]~~~%.*s~~~", i, pos, _c4prsp(m_filter_arena.first(pos)));
}
else
Expand Down Expand Up @@ -4576,7 +4586,7 @@ csubstr Parser::_filter_block_scalar(substr s, BlockStyle_e style, BlockChomp_e
case CHOMP_STRIP: // strip all newlines from the end
{
_c4dbgp("filt_block: chomp=STRIP (-)");
r = r.trimr("\r\n");
r = r.trimr("\n\r");
break;
}
case CHOMP_CLIP: // clip to a single newline
Expand Down
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ ryml_add_test(json)
ryml_add_test(preprocess)
ryml_add_test(merge)
ryml_add_test(location)
ryml_add_test(yaml_events)
ryml_add_test_case_group(empty_file)
ryml_add_test_case_group(empty_map)
ryml_add_test_case_group(empty_seq)
Expand Down
8 changes: 8 additions & 0 deletions test/test_suite/test_suite_events.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ void emit_events(CharContainer *container, Tree const& C4_RESTRICT tree)
container->resize(ret);
}

template<class CharContainer>
CharContainer emit_events(Tree const& C4_RESTRICT tree)
{
CharContainer result;
emit_events(&result, tree);
return result;
}

} // namespace yml
} // namespace c4

Expand Down
58 changes: 26 additions & 32 deletions test/test_suite/test_suite_events_emitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,38 +35,39 @@ struct EventsEmitter
buf[pos] = c;
++pos;
}
C4_ALWAYS_INLINE size_t emit_to_esc(csubstr val, size_t i, size_t prev, char c)
{
pr(val.range(prev, i));
pr('\\');
pr(c);
return i+1;
}
};

void EventsEmitter::emit_scalar(csubstr val, bool quoted)
{
static constexpr const char openscalar[] = {':', '\''};
pr(openscalar[quoted]);
constexpr const char openchar[] = {':', '\''};
pr(openchar[quoted]);
size_t prev = 0;
for(size_t i = 0; i < val.len; ++i)
{
if(val[i] == '\n')
{
pr(val.range(prev, i));
pr('\\');
pr('n');
prev = i+1;
}
else if(val[i] == '\t')
switch(val[i])
{
pr(val.range(prev, i));
pr('\\');
pr('t');
prev = i+1;
case '\n':
prev = emit_to_esc(val, i, prev, 'n'); break;
case '\t':
prev = emit_to_esc(val, i, prev, 't'); break;
case '\\':
prev = emit_to_esc(val, i, prev, '\\'); break;
case '\r':
prev = emit_to_esc(val, i, prev, 'r'); break;
case '\b':
prev = emit_to_esc(val, i, prev, 'b'); break;
case '\f':
prev = emit_to_esc(val, i, prev, 'f'); break;
case '\0':
prev = emit_to_esc(val, i, prev, '0'); break;
}
else if(val[i] == '\\')
{
pr(val.range(prev, i));
pr('\\');
pr('\\');
prev = i+1;
}
else if(val[i] == '\r')
continue; // not really sure about this
}
pr(val.sub(prev)); // print remaining portion
}
Expand Down Expand Up @@ -171,11 +172,7 @@ void EventsEmitter::emit_events(size_t node)

void EventsEmitter::emit_doc(size_t node)
{
bool use_triple = !m_tree->is_root(node) && m_tree->is_doc(node);
if(use_triple)
pr("+DOC ---");
else
pr("+DOC");
pr("+DOC");
if(m_tree->is_val(node))
{
pr("\n=VAL");
Expand All @@ -190,10 +187,7 @@ void EventsEmitter::emit_doc(size_t node)
pr('\n');
emit_events(node);
}
if(use_triple)
pr("-DOC ...\n");
else
pr("-DOC\n");
pr("-DOC\n");
}

void EventsEmitter::emit_events()
Expand Down
125 changes: 125 additions & 0 deletions test/test_yaml_events.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#ifndef RYML_SINGLE_HEADER
#include <c4/yml/std/std.hpp>
#include <c4/yml/yml.hpp>
#endif
#include <gtest/gtest.h>

#include "./test_case.hpp"
#include "./test_suite/test_suite_events.hpp"
#include "./test_suite/test_suite_events_emitter.cpp" // HACK

namespace c4 {
namespace yml {

void test_evts(csubstr src, std::string expected)
{
Tree tree = parse_in_arena(src);
auto actual = emit_events<std::string>(tree);
EXPECT_EQ(actual, expected);
}

TEST(events, docval)
{
test_evts(
R"('quoted val'
)",
R"(+STR
+DOC
=VAL 'quoted val
-DOC
-STR
)"
);
}

TEST(events, docsep)
{
test_evts(
R"(--- 'quoted val'
--- another
...
--- and yet another
...
---
...
)",
R"(+STR
+DOC
=VAL 'quoted val
-DOC
+DOC
=VAL :another
-DOC
+DOC
=VAL :and yet another
-DOC
+DOC
=VAL :
-DOC
-STR
)"
);
}

TEST(events, basic_map)
{
test_evts(
"{foo: bar}",
R"(+STR
+DOC
+MAP
=VAL :foo
=VAL :bar
-MAP
-DOC
-STR
)"
);
}

TEST(events, basic_seq)
{
test_evts(
"[foo, bar]",
R"(+STR
+DOC
+SEQ
=VAL :foo
=VAL :bar
-SEQ
-DOC
-STR
)"
);
}

TEST(events, escapes)
{
test_evts(
R"("\b\r\n\0\f\/")",
"+STR\n"
"+DOC\n"
"=VAL '\\b\\r\\n\\0\\f/\n"
"-DOC\n"
"-STR\n"
);
}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------

// The other test executables are written to contain the declarative-style
// YmlTestCases. This executable does not have any but the build setup
// assumes it does, and links with the test lib, which requires an existing
// get_case() function. So this is here to act as placeholder until (if?)
// proper test cases are added here. This was detected in #47 (thanks
// @cburgard).
Case const* get_case(csubstr)
{
return nullptr;
}

} // namespace yml
} // namespace c4

0 comments on commit bda127a

Please sign in to comment.