mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2026-07-02 22:08:11 +00:00
fix: make info strings case insensitive (#871)
This commit is contained in:
@@ -1,24 +1,29 @@
|
|||||||
#include "InfoString.h"
|
#include "InfoString.h"
|
||||||
|
|
||||||
#include "Utils/Logging/Log.h"
|
#include "Utils/Logging/Log.h"
|
||||||
|
#include "Utils/StringUtils.h"
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <stack>
|
#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
|
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 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 EMPTY_VALUE;
|
||||||
|
|
||||||
return value->second;
|
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 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)
|
if (foundValue)
|
||||||
*foundValue = false;
|
*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)
|
void InfoString::SetValueForKey(const std::string& key, std::string value)
|
||||||
{
|
{
|
||||||
if (!HasKey(key))
|
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)
|
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())
|
if (value != m_value_lookup.end())
|
||||||
m_values.erase(value);
|
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
|
std::string InfoString::ToString() const
|
||||||
@@ -63,7 +74,7 @@ std::string InfoString::ToString() const
|
|||||||
|
|
||||||
for (const auto& key : m_keys_by_insertion)
|
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)
|
if (!first)
|
||||||
ss << '\\';
|
ss << '\\';
|
||||||
else
|
else
|
||||||
@@ -82,7 +93,7 @@ std::string InfoString::ToString(const std::string& prefix) const
|
|||||||
|
|
||||||
for (const auto& key : m_keys_by_insertion)
|
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;
|
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)
|
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;
|
gdtEntry.m_properties[key] = value->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,9 +113,6 @@ void InfoString::ToGdtProperties(const std::string& prefix, GdtEntry& gdtEntry)
|
|||||||
|
|
||||||
class InfoStringInputStream
|
class InfoStringInputStream
|
||||||
{
|
{
|
||||||
std::istream& m_stream;
|
|
||||||
int m_last_separator;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit InfoStringInputStream(std::istream& stream)
|
explicit InfoStringInputStream(std::istream& stream)
|
||||||
: m_stream(stream),
|
: m_stream(stream),
|
||||||
@@ -139,6 +147,10 @@ public:
|
|||||||
value = str.str();
|
value = str.str();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::istream& m_stream;
|
||||||
|
int m_last_separator;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool InfoString::FromStream(std::istream& stream)
|
bool InfoString::FromStream(std::istream& stream)
|
||||||
@@ -152,11 +164,11 @@ bool InfoString::FromStream(std::istream& stream)
|
|||||||
if (!infoStream.NextField(value))
|
if (!infoStream.NextField(value))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const auto existingEntry = m_values.find(key);
|
const auto existingEntry = m_value_lookup.find(key);
|
||||||
if (existingEntry == m_values.end())
|
if (existingEntry == m_value_lookup.end())
|
||||||
{
|
{
|
||||||
m_keys_by_insertion.push_back(key);
|
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
|
else
|
||||||
{
|
{
|
||||||
@@ -180,7 +192,7 @@ bool InfoString::FromStream(const std::string& prefix, std::istream& stream)
|
|||||||
|
|
||||||
if (prefix != readPrefix)
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,11 +216,11 @@ bool InfoString::FromStream(const std::string& prefix, std::istream& stream)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto existingEntry = m_values.find(key);
|
const auto existingEntry = m_value_lookup.find(key);
|
||||||
if (existingEntry == m_values.end())
|
if (existingEntry == m_value_lookup.end())
|
||||||
{
|
{
|
||||||
m_keys_by_insertion.push_back(key);
|
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
|
else
|
||||||
{
|
{
|
||||||
@@ -239,11 +251,11 @@ bool InfoString::FromGdtProperties(const GdtEntry& gdtEntry)
|
|||||||
|
|
||||||
for (const auto& [key, value] : currentEntry->m_properties)
|
for (const auto& [key, value] : currentEntry->m_properties)
|
||||||
{
|
{
|
||||||
auto existingEntry = m_values.find(key);
|
auto existingEntry = m_value_lookup.find(key);
|
||||||
if (existingEntry == m_values.end())
|
if (existingEntry == m_value_lookup.end())
|
||||||
{
|
{
|
||||||
m_keys_by_insertion.push_back(key);
|
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
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Obj/Gdt/GdtEntry.h"
|
#include "Obj/Gdt/GdtEntry.h"
|
||||||
|
#include "Utils/StreamUtils.h"
|
||||||
|
#include "Utils/StringUtils.h"
|
||||||
|
|
||||||
#include <istream>
|
#include <istream>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -9,13 +11,9 @@
|
|||||||
|
|
||||||
class InfoString
|
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:
|
public:
|
||||||
|
InfoString() = default;
|
||||||
|
|
||||||
[[nodiscard]] bool HasKey(const std::string& key) const;
|
[[nodiscard]] bool HasKey(const std::string& key) const;
|
||||||
[[nodiscard]] const std::string& GetValueForKey(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;
|
const std::string& GetValueForKey(const std::string& key, bool* foundValue) const;
|
||||||
@@ -29,4 +27,28 @@ public:
|
|||||||
bool FromStream(std::istream& stream);
|
bool FromStream(std::istream& stream);
|
||||||
bool FromStream(const std::string& prefix, std::istream& stream);
|
bool FromStream(const std::string& prefix, std::istream& stream);
|
||||||
bool FromGdtProperties(const GdtEntry& gdtEntry);
|
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 <cctype>
|
||||||
#include <sstream>
|
#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
|
namespace utils
|
||||||
{
|
{
|
||||||
std::string EscapeStringForQuotationMarks(const std::string_view& str)
|
std::string EscapeStringForQuotationMarks(const std::string_view& str)
|
||||||
@@ -115,6 +123,11 @@ namespace utils
|
|||||||
c = static_cast<char>(toupper(static_cast<unsigned char>(c)));
|
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)
|
void StringTrimL(std::string& str)
|
||||||
{
|
{
|
||||||
str.erase(str.begin(),
|
str.erase(str.begin(),
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace utils
|
namespace utils
|
||||||
@@ -18,6 +20,8 @@ namespace utils
|
|||||||
void MakeStringUpperCase(char* str);
|
void MakeStringUpperCase(char* str);
|
||||||
void MakeStringUpperCase(std::string& str);
|
void MakeStringUpperCase(std::string& str);
|
||||||
|
|
||||||
|
bool StringEqualsIgnoreCase(std::string_view lhs, std::string_view rhs);
|
||||||
|
|
||||||
void StringTrimL(std::string& str);
|
void StringTrimL(std::string& str);
|
||||||
void StringTrimR(std::string& str);
|
void StringTrimR(std::string& str);
|
||||||
void StringTrim(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