Commit 94af8abd authored by Niels's avatar Niels

overworked reference token parsing

parent 2cb925c1
...@@ -8911,10 +8911,21 @@ basic_json_parser_64: ...@@ -8911,10 +8911,21 @@ basic_json_parser_64:
/// the reference tokens /// the reference tokens
std::vector<std::string> reference_tokens {}; std::vector<std::string> reference_tokens {};
/// replace all occurrences of a substring by another string /*!
void replace_substring(std::string& s, @brief replace all occurrences of a substring by another string
const std::string& f,
const std::string& t) @param[in,out] s the string to manipulate
@param[in] f the substring to replace with @a t
@param[out] t the string to replace @a f
@return The string @a s where all occurrences of @a f are replaced
with @a t.
@pre The search string @f must not be empty.
*/
static void replace_substring(std::string& s,
const std::string& f,
const std::string& t)
{ {
assert(not f.empty()); assert(not f.empty());
...@@ -8941,26 +8952,49 @@ basic_json_parser_64: ...@@ -8941,26 +8952,49 @@ basic_json_parser_64:
throw std::domain_error("JSON pointer must be empty or begin with '/'"); throw std::domain_error("JSON pointer must be empty or begin with '/'");
} }
// tokenize reference string // extract the reference tokens:
auto ptr = std::strtok(&reference_string[0], "/"); // - slash: position of the last read slash (or end of string)
while (ptr != nullptr) // - start: position after the previous slash
{ for (
reference_tokens.push_back(ptr); // search for the first slash after the first character
ptr = std::strtok(NULL, "/"); size_t slash = reference_string.find_first_of("/", 1),
} // set the beginning of the first reference token
start = 1;
// special case: reference string was just "/" // we can stop if start == string::npos+1 = 0
if (reference_tokens.empty()) start != 0;
{ // set the beginning of the next reference token
reference_tokens.push_back(""); // (could be 0 if slash == std::string::npos)
} start = slash + 1,
// find next slash
slash = reference_string.find_first_of("/", start))
{
// use the text between the beginning of the reference token
// (start) and the last slash (slash).
auto reference_token = reference_string.substr(start, slash - start);
// check reference tokens are properly escaped
for (size_t pos = reference_token.find_first_of("~");
pos != std::string::npos;
pos = reference_token.find_first_of("~", pos + 1))
{
assert(reference_token[pos] == '~');
// ~ must be followed by 0 or 1
if (pos == reference_token.size() - 1 or
(reference_token[pos + 1] != '0' and
reference_token[pos + 1] != '1'))
{
throw std::domain_error("escape error: '~' must be followed with '0' or '1'");
}
}
for (auto& reference_token : reference_tokens)
{
// first transform any occurrence of the sequence '~1' to '/' // first transform any occurrence of the sequence '~1' to '/'
replace_substring(reference_token, "~1", "/"); replace_substring(reference_token, "~1", "/");
// then transform any occurrence of the sequence '~0' to '~' // then transform any occurrence of the sequence '~0' to '~'
replace_substring(reference_token, "~0", "~"); replace_substring(reference_token, "~0", "~");
// store the reference token
reference_tokens.push_back(reference_token);
} }
} }
}; };
...@@ -9026,9 +9060,9 @@ struct hash<nlohmann::json> ...@@ -9026,9 +9060,9 @@ struct hash<nlohmann::json>
/*! /*!
@brief user-defined string literal for JSON values @brief user-defined string literal for JSON values
This operator implements a user-defined string literal for JSON objects. It can This operator implements a user-defined string literal for JSON objects. It
be used by adding \p "_json" to a string literal and returns a JSON object if can be used by adding \p "_json" to a string literal and returns a JSON object
no parse error occurred. if no parse error occurred.
@param[in] s a string representation of a JSON object @param[in] s a string representation of a JSON object
@return a JSON object @return a JSON object
...@@ -9040,6 +9074,16 @@ inline nlohmann::json operator "" _json(const char* s, std::size_t) ...@@ -9040,6 +9074,16 @@ inline nlohmann::json operator "" _json(const char* s, std::size_t)
return nlohmann::json::parse(reinterpret_cast<const nlohmann::json::string_t::value_type*>(s)); return nlohmann::json::parse(reinterpret_cast<const nlohmann::json::string_t::value_type*>(s));
} }
/*!
@brief user-defined string literal for JSON pointer
@since version 2.0.0
*/
inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t)
{
return nlohmann::json::json_pointer(s);
}
// restore GCC/clang diagnostic settings // restore GCC/clang diagnostic settings
#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
......
...@@ -8190,10 +8190,21 @@ class basic_json ...@@ -8190,10 +8190,21 @@ class basic_json
/// the reference tokens /// the reference tokens
std::vector<std::string> reference_tokens {}; std::vector<std::string> reference_tokens {};
/// replace all occurrences of a substring by another string /*!
void replace_substring(std::string& s, @brief replace all occurrences of a substring by another string
const std::string& f,
const std::string& t) @param[in,out] s the string to manipulate
@param[in] f the substring to replace with @a t
@param[out] t the string to replace @a f
@return The string @a s where all occurrences of @a f are replaced
with @a t.
@pre The search string @f must not be empty.
*/
static void replace_substring(std::string& s,
const std::string& f,
const std::string& t)
{ {
assert(not f.empty()); assert(not f.empty());
...@@ -8220,26 +8231,49 @@ class basic_json ...@@ -8220,26 +8231,49 @@ class basic_json
throw std::domain_error("JSON pointer must be empty or begin with '/'"); throw std::domain_error("JSON pointer must be empty or begin with '/'");
} }
// tokenize reference string // extract the reference tokens:
auto ptr = std::strtok(&reference_string[0], "/"); // - slash: position of the last read slash (or end of string)
while (ptr != nullptr) // - start: position after the previous slash
{ for (
reference_tokens.push_back(ptr); // search for the first slash after the first character
ptr = std::strtok(NULL, "/"); size_t slash = reference_string.find_first_of("/", 1),
} // set the beginning of the first reference token
start = 1;
// we can stop if start == string::npos+1 = 0
start != 0;
// set the beginning of the next reference token
// (could be 0 if slash == std::string::npos)
start = slash + 1,
// find next slash
slash = reference_string.find_first_of("/", start))
{
// use the text between the beginning of the reference token
// (start) and the last slash (slash).
auto reference_token = reference_string.substr(start, slash - start);
// check reference tokens are properly escaped
for (size_t pos = reference_token.find_first_of("~");
pos != std::string::npos;
pos = reference_token.find_first_of("~", pos + 1))
{
assert(reference_token[pos] == '~');
// special case: reference string was just "/" // ~ must be followed by 0 or 1
if (reference_tokens.empty()) if (pos == reference_token.size() - 1 or
{ (reference_token[pos + 1] != '0' and
reference_tokens.push_back(""); reference_token[pos + 1] != '1'))
} {
throw std::domain_error("escape error: '~' must be followed with '0' or '1'");
}
}
for (auto& reference_token : reference_tokens)
{
// first transform any occurrence of the sequence '~1' to '/' // first transform any occurrence of the sequence '~1' to '/'
replace_substring(reference_token, "~1", "/"); replace_substring(reference_token, "~1", "/");
// then transform any occurrence of the sequence '~0' to '~' // then transform any occurrence of the sequence '~0' to '~'
replace_substring(reference_token, "~0", "~"); replace_substring(reference_token, "~0", "~");
// store the reference token
reference_tokens.push_back(reference_token);
} }
} }
}; };
...@@ -8305,9 +8339,9 @@ struct hash<nlohmann::json> ...@@ -8305,9 +8339,9 @@ struct hash<nlohmann::json>
/*! /*!
@brief user-defined string literal for JSON values @brief user-defined string literal for JSON values
This operator implements a user-defined string literal for JSON objects. It can This operator implements a user-defined string literal for JSON objects. It
be used by adding \p "_json" to a string literal and returns a JSON object if can be used by adding \p "_json" to a string literal and returns a JSON object
no parse error occurred. if no parse error occurred.
@param[in] s a string representation of a JSON object @param[in] s a string representation of a JSON object
@return a JSON object @return a JSON object
...@@ -8319,6 +8353,16 @@ inline nlohmann::json operator "" _json(const char* s, std::size_t) ...@@ -8319,6 +8353,16 @@ inline nlohmann::json operator "" _json(const char* s, std::size_t)
return nlohmann::json::parse(reinterpret_cast<const nlohmann::json::string_t::value_type*>(s)); return nlohmann::json::parse(reinterpret_cast<const nlohmann::json::string_t::value_type*>(s));
} }
/*!
@brief user-defined string literal for JSON pointer
@since version 2.0.0
*/
inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t)
{
return nlohmann::json::json_pointer(s);
}
// restore GCC/clang diagnostic settings // restore GCC/clang diagnostic settings
#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) #if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
......
...@@ -12118,6 +12118,29 @@ TEST_CASE("JSON pointers") ...@@ -12118,6 +12118,29 @@ TEST_CASE("JSON pointers")
CHECK(j_const[""] == json::json_pointer("/").get(j_const)); CHECK(j_const[""] == json::json_pointer("/").get(j_const));
CHECK(j_const[" "] == json::json_pointer("/ ").get(j_const)); CHECK(j_const[" "] == json::json_pointer("/ ").get(j_const));
} }
SECTION("user-defined string literal")
{
// the whole document
CHECK(""_json_pointer.get(j) == j);
// array access
CHECK("/foo"_json_pointer.get(j) == j["foo"]);
CHECK("/foo/0"_json_pointer.get(j) == j["foo"][0]);
CHECK("/foo/1"_json_pointer.get(j) == j["foo"][1]);
}
SECTION("errors")
{
CHECK_THROWS_AS(json::json_pointer("foo"), std::domain_error);
CHECK_THROWS_WITH(json::json_pointer("foo"), "JSON pointer must be empty or begin with '/'");
CHECK_THROWS_AS(json::json_pointer("/~~"), std::domain_error);
CHECK_THROWS_WITH(json::json_pointer("/~~"), "escape error: '~' must be followed with '0' or '1'");
CHECK_THROWS_AS(json::json_pointer("/~"), std::domain_error);
CHECK_THROWS_WITH(json::json_pointer("/~"), "escape error: '~' must be followed with '0' or '1'");
}
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment