Skip to content

Commit

Permalink
Improve formatter, add test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
mikee47 committed Aug 31, 2024
1 parent 9db2fc6 commit 23cd5d9
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 28 deletions.
28 changes: 13 additions & 15 deletions Sming/Core/Data/Format/Formatter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,25 @@

#include "Formatter.h"

namespace
namespace Format
{
/**
* @brief Get character used for standard escapes
* @param c Code to be escaped
* @param unicode Affects escaping of NUL
* @param options
* @retval char Corresponding character, NUL if there isn't a standard escape
*/
char escapeChar(char c, bool unicode)
char escapeChar(char c, Options options)
{
switch(c) {
case '\0':
return unicode ? '\0' : '0';
return options[Option::unicode] ? '\0' : '0';
case '\"':
return '"';
return options[Option::doublequote] ? c : '\0';
case '\'':
return options[Option::singlequote] ? c : '\0';
case '\\':
return '\\';
return options[Option::backslash] ? c : '\0';
case '\a':
return 'a';
case '\b':
Expand All @@ -48,19 +50,15 @@ char escapeChar(char c, bool unicode)
}
}

} // namespace

namespace Format
{
unsigned escapeControls(String& value, bool unicode)
unsigned escapeControls(String& value, Options options)
{
// Count number of extra characters we'll need to insert
unsigned extra{0};
for(auto& c : value) {
if(escapeChar(c, unicode)) {
if(escapeChar(c, options)) {
extra += 1; // "\"
} else if(uint8_t(c) < 0x20) {
extra += unicode ? 5 : 3; // "\uNNNN" or "\xnn"
extra += options[Option::unicode] ? 5 : 3; // "\uNNNN" or "\xnn"
}
}
if(extra == 0) {
Expand All @@ -76,13 +74,13 @@ unsigned escapeControls(String& value, bool unicode)
in += extra;
while(len--) {
uint8_t c = *in++;
auto esc = escapeChar(c, unicode);
auto esc = escapeChar(c, options);
if(esc) {
*out++ = '\\';
*out++ = esc;
} else if(c < 0x20) {
*out++ = '\\';
if(unicode) {
if(options[Option::unicode]) {
*out++ = 'u';
*out++ = '0';
*out++ = '0';
Expand Down
13 changes: 11 additions & 2 deletions Sming/Core/Data/Format/Formatter.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,25 @@

#include <WString.h>
#include <Data/WebConstants.h>
#include <Data/BitSet.h>

namespace Format
{
enum class Option {
unicode, //< Use unicode escapes \uNNNN, otherwise hex \xNN
doublequote,
singlequote,
backslash,
};
using Options = BitSet<uint8_t, Option, 3>;

/**
* @brief Escape standard control codes such as `\n` (below ASCII 0x20)
* @param value String to be modified
* @param unicode If true, use unicode escapes \uNNNN, otherwise hex \xNN
* @param options
* @retval unsigned Number of control characters found and replaced
*/
unsigned escapeControls(String& value, bool unicode);
unsigned escapeControls(String& value, Options options);

/**
* @brief Virtual class to perform format-specific String adjustments
Expand Down
4 changes: 1 addition & 3 deletions Sming/Core/Data/Format/Json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ bool IsValidUtf8(const char* str, unsigned length)
*/
void Json::escape(String& value) const
{
escapeControls(value, true);
escapeControls(value, Option::unicode | Option::doublequote | Option::backslash);
if(!IsValidUtf8(value.c_str(), value.length())) {
debug_w("Invalid UTF8: %s", value.c_str());
for(unsigned i = 0; i < value.length(); ++i) {
Expand All @@ -75,8 +75,6 @@ void Json::escape(String& value) const
c = '_';
}
}

value.replace("\"", "\\\"");
}

} // namespace Format
29 changes: 21 additions & 8 deletions tests/HostTests/modules/Formatter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,29 @@ class FormatterTest : public TestGroup
void execute() override
{
DEFINE_FSTR_LOCAL(text1, "A JSON\ntest string\twith escapes\x12\0\n"
"Worth maybe \xc2\xa3"
"Worth \"maybe\" \xc2\xa3"
"0.53.")
DEFINE_FSTR_LOCAL(text1b, "A JSON\\ntest string\\twith escapes\\x12\\0\\n"
"Worth maybe \xc2\xa3"
"0.53.")

Serial << text1 << endl;
String s(text1);
Format::json.escape(s);
REQUIRE_EQ(s, text1b);
TEST_CASE("JSON")
{
DEFINE_FSTR_LOCAL(text1b, "A JSON\\ntest string\\twith escapes\\u0012\\u0000\\n"
"Worth \\\"maybe\\\" \xc2\xa3"
"0.53.")

Serial << text1 << endl;
String s(text1);
Format::json.escape(s);
REQUIRE_EQ(s, text1b);
}

TEST_CASE("C++")
{
DEFINE_FSTR_LOCAL(text1b, "A JSON\\ntest string\\twith escapes\\x12\\0\\nWorth \\\"maybe\\\" £0.53.")

String s(text1);
Format::escapeControls(s, Format::Option::doublequote | Format::Option::backslash);
REQUIRE_EQ(s, text1b);
}
}
};

Expand Down

0 comments on commit 23cd5d9

Please sign in to comment.