overworked reference token parsing
This commit is contained in:
parent
2cb925c186
commit
94af8abdff
3 changed files with 151 additions and 40 deletions
80
src/json.hpp
80
src/json.hpp
|
@ -8911,8 +8911,19 @@ 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
|
||||||
|
|
||||||
|
@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& f,
|
||||||
const std::string& t)
|
const std::string& t)
|
||||||
{
|
{
|
||||||
|
@ -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 (
|
||||||
|
// search for the first slash after the first character
|
||||||
|
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))
|
||||||
{
|
{
|
||||||
reference_tokens.push_back(ptr);
|
// use the text between the beginning of the reference token
|
||||||
ptr = std::strtok(NULL, "/");
|
// (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'");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// special case: reference string was just "/"
|
|
||||||
if (reference_tokens.empty())
|
|
||||||
{
|
|
||||||
reference_tokens.push_back("");
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
||||||
/*!
|
/*!
|
||||||
@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)
|
||||||
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,8 +8190,19 @@ 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
|
||||||
|
|
||||||
|
@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& f,
|
||||||
const std::string& t)
|
const std::string& t)
|
||||||
{
|
{
|
||||||
|
@ -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 (
|
||||||
|
// search for the first slash after the first character
|
||||||
|
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))
|
||||||
{
|
{
|
||||||
reference_tokens.push_back(ptr);
|
// use the text between the beginning of the reference token
|
||||||
ptr = std::strtok(NULL, "/");
|
// (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'");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// special case: reference string was just "/"
|
|
||||||
if (reference_tokens.empty())
|
|
||||||
{
|
|
||||||
reference_tokens.push_back("");
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
||||||
/*!
|
/*!
|
||||||
@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)
|
||||||
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")
|
||||||
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'");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue