Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions components/core/cmake/Options/options.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ endfunction()

function(validate_clp_s_archivereader_dependencies)
validate_clp_dependencies_for_target(CLP_BUILD_CLP_S_ARCHIVEREADER
CLP_BUILD_CLP_STRING_UTILS
CLP_BUILD_CLP_S_CLP_DEPENDENCIES
CLP_BUILD_CLP_S_IO
CLP_BUILD_CLP_S_TIMESTAMPPATTERN
Expand Down Expand Up @@ -300,6 +301,7 @@ endfunction()

function(validate_clp_s_search_dependencies)
validate_clp_dependencies_for_target(CLP_BUILD_CLP_S_SEARCH
CLP_BUILD_CLP_STRING_UTILS
CLP_BUILD_CLP_S_ARCHIVEREADER
CLP_BUILD_CLP_S_CLP_DEPENDENCIES
CLP_BUILD_CLP_S_SEARCH_AST
Expand Down
1 change: 1 addition & 0 deletions components/core/src/clp_s/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ if(CLP_BUILD_CLP_S_ARCHIVEREADER)
clp_s_archive_reader
PUBLIC
absl::flat_hash_map
clp::string_utils
clp_s::io
msgpack-cxx
nlohmann_json::nlohmann_json
Expand Down
8 changes: 7 additions & 1 deletion components/core/src/clp_s/DictionaryReader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <unordered_set>

#include <boost/algorithm/string/case_conv.hpp>
#include <string_utils/string_utils.hpp>

#include "ArchiveReaderAdaptor.hpp"
#include "DictionaryEntry.hpp"
Expand Down Expand Up @@ -191,7 +192,12 @@ void DictionaryReader<DictionaryIdType, EntryType>::get_entries_matching_wildcar
std::unordered_set<EntryType const*>& entries
) const {
for (auto const& entry : m_entries) {
if (StringUtils::wildcard_match_unsafe(entry.get_value(), wildcard_string, !ignore_case)) {
if (clp::string_utils::wildcard_match_unsafe(
entry.get_value(),
wildcard_string,
!ignore_case
))
{
entries.insert(&entry);
}
}
Expand Down
307 changes: 3 additions & 304 deletions components/core/src/clp_s/Utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <boost/url.hpp>
#include <fmt/core.h>
#include <spdlog/spdlog.h>
#include <string_utils/string_utils.hpp>

#include "archive_constants.hpp"

Expand Down Expand Up @@ -188,9 +189,9 @@ bool StringUtils::get_bounds_of_next_var(string const& msg, size_t& begin_pos, s
end_pos = begin_pos;
for (; end_pos < msg_length; ++end_pos) {
char c = msg[end_pos];
if (is_decimal_digit(c)) {
if (clp::string_utils::is_decimal_digit(c)) {
contains_decimal_digit = true;
} else if (is_alphabet(c)) {
} else if (clp::string_utils::is_alphabet(c)) {
contains_alphabet = true;
} else if (is_delim(c)) {
break;
Expand All @@ -212,308 +213,6 @@ bool StringUtils::get_bounds_of_next_var(string const& msg, size_t& begin_pos, s
return (msg_length != begin_pos);
}

size_t StringUtils::find_first_of(
string const& haystack,
char const* needles,
size_t search_start_pos,
size_t& needle_ix
) {
size_t haystack_length = haystack.length();
size_t needles_length = strlen(needles);
for (size_t i = search_start_pos; i < haystack_length; ++i) {
for (needle_ix = 0; needle_ix < needles_length; ++needle_ix) {
if (haystack[i] == needles[needle_ix]) {
return i;
}
}
}

return string::npos;
}

string StringUtils::replace_characters(
char const* characters_to_escape,
char const* replacement_characters,
string const& value,
bool escape
) {
string new_value;
size_t search_start_pos = 0;
while (true) {
size_t replace_char_ix;
size_t char_to_replace_pos
= find_first_of(value, characters_to_escape, search_start_pos, replace_char_ix);
if (string::npos == char_to_replace_pos) {
new_value.append(value, search_start_pos, string::npos);
break;
} else {
new_value.append(value, search_start_pos, char_to_replace_pos - search_start_pos);
if (escape) {
new_value += "\\";
}
new_value += replacement_characters[replace_char_ix];
search_start_pos = char_to_replace_pos + 1;
}
}
return new_value;
}

void StringUtils::to_lower(string& str) {
std::transform(str.cbegin(), str.cend(), str.begin(), [](unsigned char c) {
return std::tolower(c);
});
}

bool StringUtils::is_wildcard(char c) {
static constexpr char cWildcards[] = "?*";
for (size_t i = 0; i < strlen(cWildcards); ++i) {
if (cWildcards[i] == c) {
return true;
}
}
return false;
}

string StringUtils::clean_up_wildcard_search_string(string_view str) {
string cleaned_str;

bool is_escaped = false;
auto str_end = str.cend();
for (auto current = str.cbegin(); current != str_end;) {
auto c = *current;
if (is_escaped) {
is_escaped = false;

if (is_wildcard(c) || '\\' == c) {
// Keep escaping if c is a wildcard character or an escape character
cleaned_str += '\\';
}
cleaned_str += c;
++current;
} else if ('*' == c) {
cleaned_str += c;

// Skip over all '*' to find the next non-'*'
do {
++current;
} while (current != str_end && '*' == *current);
} else {
if ('\\' == c) {
is_escaped = true;
} else {
cleaned_str += c;
}
++current;
}
}

return cleaned_str;
}

bool StringUtils::advance_tame_to_next_match(
char const*& tame_current,
char const*& tame_bookmark,
char const* tame_end,
char const*& wild_current,
char const*& wild_bookmark
) {
auto w = *wild_current;
if ('?' != w) {
// No need to check for '*' since the caller ensures wild doesn't
// contain consecutive '*'

// Handle escaped characters
if ('\\' == w) {
++wild_current;
// This is safe without a bounds check since this the caller
// ensures there are no dangling escape characters
w = *wild_current;
}

// Advance tame_current until it matches wild_current
while (true) {
if (tame_end == tame_current) {
// Wild group is longer than last group in tame, so
// can't match
// e.g. "*abc" doesn't match "zab"
return false;
}
auto t = *tame_current;
if (t == w) {
break;
}
++tame_current;
}
}

tame_bookmark = tame_current;

return true;
}

bool
StringUtils::wildcard_match_unsafe(string_view tame, string_view wild, bool case_sensitive_match) {
if (case_sensitive_match) {
return wildcard_match_unsafe_case_sensitive(tame, wild);
} else {
// We convert to lowercase (rather than uppercase) anticipating that
// callers use lowercase more frequently, so little will need to change.
string lowercase_tame(tame);
to_lower(lowercase_tame);
string lowercase_wild(wild);
to_lower(lowercase_wild);
return wildcard_match_unsafe_case_sensitive(lowercase_tame, lowercase_wild);
}
}

/**
* The algorithm basically works as follows:
* Given a wild string "*abc*def*ghi*", it can be broken into groups of
* characters delimited by one or more '*' characters. The goal of the
* algorithm is then to determine whether the tame string contains each of
* those groups in the same order.
*
* Thus, the algorithm:
* 1. searches for the start of one of these groups in wild,
* 2. searches for a group in tame starting with the same character, and then
* 3. checks if the two match. If not, the search repeats with the next group in
* tame.
*/
bool StringUtils::wildcard_match_unsafe_case_sensitive(string_view tame, string_view wild) {
auto const tame_length = tame.length();
auto const wild_length = wild.length();
char const* tame_current = tame.data();
char const* wild_current = wild.data();
char const* tame_bookmark = nullptr;
char const* wild_bookmark = nullptr;
char const* tame_end = tame_current + tame_length;
char const* wild_end = wild_current + wild_length;

// Handle wild or tame being empty
if (0 == wild_length) {
return 0 == tame_length;
} else {
if (0 == tame_length) {
return "*" == wild;
}
}

char w;
char t;
bool is_escaped = false;
while (true) {
w = *wild_current;
if ('*' == w) {
++wild_current;
if (wild_end == wild_current) {
// Trailing '*' means everything remaining in tame will match
return true;
}

// Set wild and tame bookmarks
wild_bookmark = wild_current;
if (!advance_tame_to_next_match(
tame_current,
tame_bookmark,
tame_end,
wild_current,
wild_bookmark
))
{
return false;
}
} else {
// Handle escaped characters
if ('\\' == w) {
is_escaped = true;
++wild_current;
// This is safe without a bounds check since this the caller
// ensures there are no dangling escape characters
w = *wild_current;
}

// Handle a mismatch
t = *tame_current;
if (false == ((false == is_escaped && '?' == w) || t == w)) {
if (nullptr == wild_bookmark) {
// No bookmark to return to
return false;
}

wild_current = wild_bookmark;
tame_current = tame_bookmark + 1;
if (!advance_tame_to_next_match(
tame_current,
tame_bookmark,
tame_end,
wild_current,
wild_bookmark
))
{
return false;
}
}
}

++tame_current;
++wild_current;

// Handle reaching the end of tame or wild
if (tame_end == tame_current) {
return (wild_end == wild_current
|| ('*' == *wild_current && (wild_current + 1) == wild_end));
} else {
if (wild_end == wild_current) {
if (nullptr == wild_bookmark) {
// No bookmark to return to
return false;
} else {
wild_current = wild_bookmark;
tame_current = tame_bookmark + 1;
if (!advance_tame_to_next_match(
tame_current,
tame_bookmark,
tame_end,
wild_current,
wild_bookmark
))
{
return false;
}
}
}
}
}
}

bool StringUtils::convert_string_to_int64(std::string_view raw, int64_t& converted) {
auto raw_end = raw.cend();
auto result = std::from_chars(raw.cbegin(), raw_end, converted);
if (raw_end != result.ptr) {
return false;
} else {
return result.ec == std::errc();
}
}

bool StringUtils::convert_string_to_double(std::string const& raw, double& converted) {
if (raw.empty()) {
// Can't convert an empty string
return false;
}

char const* c_str = raw.c_str();
char* end_ptr;
// Reset errno so we can detect a new error
errno = 0;
double raw_as_double = strtod(c_str, &end_ptr);
if (ERANGE == errno || (end_ptr - c_str) < raw.length()) {
return false;
}
converted = raw_as_double;
return true;
}

void StringUtils::escape_json_string(std::string& destination, std::string_view const source) {
// Escaping is implemented using this `append_unescaped_slice` approach to offer a fast path
// when strings are mostly or entirely valid escaped JSON. Benchmarking shows that this offers
Expand Down
Loading