mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2026-07-02 13:58:05 +00:00
fix: make info strings case insensitive (#871)
This commit is contained in:
@@ -1,24 +1,29 @@
|
||||
#include "InfoString.h"
|
||||
|
||||
#include "Utils/Logging/Log.h"
|
||||
#include "Utils/StringUtils.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <stack>
|
||||
|
||||
const std::string InfoString::EMPTY_VALUE;
|
||||
namespace
|
||||
{
|
||||
constexpr const char* GDT_PREFIX_FIELD = "configstringFileType";
|
||||
const std::string EMPTY_VALUE;
|
||||
} // namespace
|
||||
|
||||
bool InfoString::HasKey(const std::string& key) const
|
||||
{
|
||||
return m_values.find(key) != m_values.end();
|
||||
return m_value_lookup.contains(key);
|
||||
}
|
||||
|
||||
const std::string& InfoString::GetValueForKey(const std::string& key) const
|
||||
{
|
||||
const auto& value = m_values.find(key);
|
||||
const auto& value = m_value_lookup.find(key);
|
||||
|
||||
if (value == m_values.end())
|
||||
if (value == m_value_lookup.end())
|
||||
return EMPTY_VALUE;
|
||||
|
||||
return value->second;
|
||||
@@ -26,9 +31,9 @@ const std::string& InfoString::GetValueForKey(const std::string& key) const
|
||||
|
||||
const std::string& InfoString::GetValueForKey(const std::string& key, bool* foundValue) const
|
||||
{
|
||||
const auto& value = m_values.find(key);
|
||||
const auto& value = m_value_lookup.find(key);
|
||||
|
||||
if (value == m_values.end())
|
||||
if (value == m_value_lookup.end())
|
||||
{
|
||||
if (foundValue)
|
||||
*foundValue = false;
|
||||
@@ -43,17 +48,23 @@ const std::string& InfoString::GetValueForKey(const std::string& key, bool* foun
|
||||
void InfoString::SetValueForKey(const std::string& key, std::string value)
|
||||
{
|
||||
if (!HasKey(key))
|
||||
m_keys_by_insertion.push_back(key);
|
||||
m_keys_by_insertion.emplace_back(key);
|
||||
|
||||
m_values[key] = std::move(value);
|
||||
m_value_lookup[key] = std::move(value);
|
||||
}
|
||||
|
||||
void InfoString::RemoveKey(const std::string& key)
|
||||
{
|
||||
const auto& value = m_values.find(key);
|
||||
const auto& value = m_value_lookup.find(key);
|
||||
|
||||
if (value != m_values.end())
|
||||
m_values.erase(value);
|
||||
if (value != m_value_lookup.end())
|
||||
m_value_lookup.erase(value);
|
||||
|
||||
const auto insertion = std::ranges::find_if(m_keys_by_insertion,
|
||||
[&key](const std::string& keyByInsertion)
|
||||
{
|
||||
return utils::StringEqualsIgnoreCase(key, keyByInsertion);
|
||||
});
|
||||
}
|
||||
|
||||
std::string InfoString::ToString() const
|
||||
@@ -63,7 +74,7 @@ std::string InfoString::ToString() const
|
||||
|
||||
for (const auto& key : m_keys_by_insertion)
|
||||
{
|
||||
const auto value = m_values.find(key);
|
||||
const auto value = m_value_lookup.find(key);
|
||||
if (!first)
|
||||
ss << '\\';
|
||||
else
|
||||
@@ -82,7 +93,7 @@ std::string InfoString::ToString(const std::string& prefix) const
|
||||
|
||||
for (const auto& key : m_keys_by_insertion)
|
||||
{
|
||||
const auto value = m_values.find(key);
|
||||
const auto value = m_value_lookup.find(key);
|
||||
ss << '\\' << key << '\\' << value->second;
|
||||
}
|
||||
|
||||
@@ -93,7 +104,7 @@ void InfoString::ToGdtProperties(const std::string& prefix, GdtEntry& gdtEntry)
|
||||
{
|
||||
for (const auto& key : m_keys_by_insertion)
|
||||
{
|
||||
const auto value = m_values.find(key);
|
||||
const auto value = m_value_lookup.find(key);
|
||||
gdtEntry.m_properties[key] = value->second;
|
||||
}
|
||||
|
||||
@@ -102,9 +113,6 @@ void InfoString::ToGdtProperties(const std::string& prefix, GdtEntry& gdtEntry)
|
||||
|
||||
class InfoStringInputStream
|
||||
{
|
||||
std::istream& m_stream;
|
||||
int m_last_separator;
|
||||
|
||||
public:
|
||||
explicit InfoStringInputStream(std::istream& stream)
|
||||
: m_stream(stream),
|
||||
@@ -139,6 +147,10 @@ public:
|
||||
value = str.str();
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::istream& m_stream;
|
||||
int m_last_separator;
|
||||
};
|
||||
|
||||
bool InfoString::FromStream(std::istream& stream)
|
||||
@@ -152,11 +164,11 @@ bool InfoString::FromStream(std::istream& stream)
|
||||
if (!infoStream.NextField(value))
|
||||
return false;
|
||||
|
||||
const auto existingEntry = m_values.find(key);
|
||||
if (existingEntry == m_values.end())
|
||||
const auto existingEntry = m_value_lookup.find(key);
|
||||
if (existingEntry == m_value_lookup.end())
|
||||
{
|
||||
m_keys_by_insertion.push_back(key);
|
||||
m_values.emplace(std::make_pair(key, value));
|
||||
m_value_lookup.emplace(std::make_pair(key, value));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -180,7 +192,7 @@ bool InfoString::FromStream(const std::string& prefix, std::istream& stream)
|
||||
|
||||
if (prefix != readPrefix)
|
||||
{
|
||||
con::error("Invalid info string: Prefix \"{}\" did not match expected prefix \"{}\"", readPrefix, prefix);
|
||||
con::error(R"(Invalid info string: Prefix "{}" did not match expected prefix "{}")", readPrefix, prefix);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -204,11 +216,11 @@ bool InfoString::FromStream(const std::string& prefix, std::istream& stream)
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto existingEntry = m_values.find(key);
|
||||
if (existingEntry == m_values.end())
|
||||
const auto existingEntry = m_value_lookup.find(key);
|
||||
if (existingEntry == m_value_lookup.end())
|
||||
{
|
||||
m_keys_by_insertion.push_back(key);
|
||||
m_values.emplace(std::make_pair(key, value));
|
||||
m_value_lookup.emplace(std::make_pair(key, value));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -239,11 +251,11 @@ bool InfoString::FromGdtProperties(const GdtEntry& gdtEntry)
|
||||
|
||||
for (const auto& [key, value] : currentEntry->m_properties)
|
||||
{
|
||||
auto existingEntry = m_values.find(key);
|
||||
if (existingEntry == m_values.end())
|
||||
auto existingEntry = m_value_lookup.find(key);
|
||||
if (existingEntry == m_value_lookup.end())
|
||||
{
|
||||
m_keys_by_insertion.push_back(key);
|
||||
m_values.emplace(std::make_pair(key, value));
|
||||
m_value_lookup.emplace(std::make_pair(key, value));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "Obj/Gdt/GdtEntry.h"
|
||||
#include "Utils/StreamUtils.h"
|
||||
#include "Utils/StringUtils.h"
|
||||
|
||||
#include <istream>
|
||||
#include <string>
|
||||
@@ -9,13 +11,9 @@
|
||||
|
||||
class InfoString
|
||||
{
|
||||
static constexpr const char* GDT_PREFIX_FIELD = "configstringFileType";
|
||||
|
||||
static const std::string EMPTY_VALUE;
|
||||
std::unordered_map<std::string, std::string> m_values;
|
||||
std::vector<std::string> m_keys_by_insertion;
|
||||
|
||||
public:
|
||||
InfoString() = default;
|
||||
|
||||
[[nodiscard]] bool HasKey(const std::string& key) const;
|
||||
[[nodiscard]] const std::string& GetValueForKey(const std::string& key) const;
|
||||
const std::string& GetValueForKey(const std::string& key, bool* foundValue) const;
|
||||
@@ -29,4 +27,28 @@ public:
|
||||
bool FromStream(std::istream& stream);
|
||||
bool FromStream(const std::string& prefix, std::istream& stream);
|
||||
bool FromGdtProperties(const GdtEntry& gdtEntry);
|
||||
|
||||
private:
|
||||
class HashValue
|
||||
{
|
||||
public:
|
||||
std::size_t operator()(const std::string& s) const noexcept
|
||||
{
|
||||
std::string lower(s);
|
||||
utils::MakeStringLowerCase(lower);
|
||||
return std::hash<std::string>{}(lower);
|
||||
}
|
||||
};
|
||||
|
||||
class EqualValue
|
||||
{
|
||||
public:
|
||||
constexpr bool operator()(const std::string& lhs, const std::string& rhs) const
|
||||
{
|
||||
return utils::StringEqualsIgnoreCase(lhs, rhs);
|
||||
}
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, std::string, HashValue, EqualValue> m_value_lookup;
|
||||
std::vector<std::string> m_keys_by_insertion;
|
||||
};
|
||||
|
||||
@@ -4,6 +4,14 @@
|
||||
#include <cctype>
|
||||
#include <sstream>
|
||||
|
||||
namespace
|
||||
{
|
||||
bool ichar_equals(const char a, const char b)
|
||||
{
|
||||
return std::tolower(static_cast<unsigned char>(a)) == std::tolower(static_cast<unsigned char>(b));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace utils
|
||||
{
|
||||
std::string EscapeStringForQuotationMarks(const std::string_view& str)
|
||||
@@ -115,6 +123,11 @@ namespace utils
|
||||
c = static_cast<char>(toupper(static_cast<unsigned char>(c)));
|
||||
}
|
||||
|
||||
bool StringEqualsIgnoreCase(std::string_view lhs, std::string_view rhs)
|
||||
{
|
||||
return std::ranges::equal(lhs, rhs, ichar_equals);
|
||||
}
|
||||
|
||||
void StringTrimL(std::string& str)
|
||||
{
|
||||
str.erase(str.begin(),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace utils
|
||||
@@ -18,6 +20,8 @@ namespace utils
|
||||
void MakeStringUpperCase(char* str);
|
||||
void MakeStringUpperCase(std::string& str);
|
||||
|
||||
bool StringEqualsIgnoreCase(std::string_view lhs, std::string_view rhs);
|
||||
|
||||
void StringTrimL(std::string& str);
|
||||
void StringTrimR(std::string& str);
|
||||
void StringTrim(std::string& str);
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
#include "InfoString/InfoString.h"
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/generators/catch_generators.hpp>
|
||||
#include <sstream>
|
||||
|
||||
namespace
|
||||
{
|
||||
TEST_CASE("InfoString: Can parse from stream")
|
||||
{
|
||||
InfoString info;
|
||||
|
||||
SECTION("without prefix")
|
||||
{
|
||||
std::istringstream ss(R"(foo\bar\test\value)");
|
||||
info.FromStream(ss);
|
||||
}
|
||||
SECTION("with prefix")
|
||||
{
|
||||
std::istringstream ss(R"(FOOFILE\foo\bar\test\value)");
|
||||
info.FromStream("FOOFILE", ss);
|
||||
}
|
||||
SECTION("from gdt")
|
||||
{
|
||||
GdtEntry entry;
|
||||
entry.m_properties.emplace("foo", "bar");
|
||||
entry.m_properties.emplace("test", "value");
|
||||
info.FromGdtProperties(entry);
|
||||
}
|
||||
|
||||
REQUIRE(info.HasKey("foo"));
|
||||
REQUIRE(info.HasKey("test"));
|
||||
|
||||
REQUIRE(info.GetValueForKey("foo") == "bar");
|
||||
REQUIRE(info.GetValueForKey("test") == "value");
|
||||
}
|
||||
|
||||
TEST_CASE("InfoString: Can create string")
|
||||
{
|
||||
InfoString info;
|
||||
std::istringstream ss(R"(foo\bar\test\value)");
|
||||
info.FromStream(ss);
|
||||
|
||||
SECTION("without prefix")
|
||||
{
|
||||
REQUIRE(info.ToString() == R"(foo\bar\test\value)");
|
||||
}
|
||||
SECTION("with prefix")
|
||||
{
|
||||
REQUIRE(info.ToString("FOOFILE") == R"(FOOFILE\foo\bar\test\value)");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("InfoString: Is case insensitive")
|
||||
{
|
||||
InfoString info;
|
||||
std::istringstream ss(R"(foo\bar\test\value)");
|
||||
info.FromStream(ss);
|
||||
|
||||
REQUIRE(info.GetValueForKey("FOO") == "bar");
|
||||
REQUIRE(info.GetValueForKey("TEST") == "value");
|
||||
}
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user