2
0
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:
Jan
2026-06-30 20:19:18 +02:00
committed by GitHub
parent c9595794a3
commit 3ab002db1d
5 changed files with 147 additions and 33 deletions
+39 -27
View File
@@ -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
{
+28 -6
View File
@@ -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;
};
+13
View File
@@ -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(),
+4
View File
@@ -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