mirror of
https://github.com/Laupetin/OpenAssetTools.git
synced 2025-04-20 00:02:55 +00:00
Add Gdt parsing
This commit is contained in:
parent
abb268a819
commit
78ebeaaa7b
@ -0,0 +1,321 @@
|
||||
#include "Gdt.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
class GdtConst
|
||||
{
|
||||
public:
|
||||
static constexpr const char* VERSION_ENTRY_NAME = "version";
|
||||
static constexpr const char* VERSION_ENTRY_GDF = "version.gdf";
|
||||
static constexpr const char* VERSION_KEY_GAME = "game";
|
||||
static constexpr const char* VERSION_KEY_VERSION = "version";
|
||||
};
|
||||
|
||||
class GdtReader
|
||||
{
|
||||
std::istream& m_stream;
|
||||
char m_char;
|
||||
bool m_peeked;
|
||||
int m_line;
|
||||
|
||||
void PrintError(const std::string& message) const
|
||||
{
|
||||
std::cout << "GDT Error at line " << m_line << ": " << message << "\n";
|
||||
}
|
||||
|
||||
int PeekChar()
|
||||
{
|
||||
if (m_peeked)
|
||||
return m_char;
|
||||
|
||||
int c;
|
||||
do
|
||||
{
|
||||
c = m_stream.get();
|
||||
}
|
||||
while (isspace(c));
|
||||
|
||||
m_peeked = true;
|
||||
m_char = c;
|
||||
return c;
|
||||
}
|
||||
|
||||
int NextChar()
|
||||
{
|
||||
if (m_peeked)
|
||||
{
|
||||
m_peeked = false;
|
||||
return m_char;
|
||||
}
|
||||
|
||||
int c;
|
||||
do
|
||||
{
|
||||
c = m_stream.get();
|
||||
}
|
||||
while (isspace(c));
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
bool ReadStringContent(std::string& str)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
|
||||
if (NextChar() != '"')
|
||||
{
|
||||
PrintError("Expected string opening tag");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto c = m_stream.get();
|
||||
while (c != '"' && c != '\n' && c != EOF)
|
||||
{
|
||||
ss << static_cast<char>(c);
|
||||
c = m_stream.get();
|
||||
}
|
||||
|
||||
if (c == '"')
|
||||
{
|
||||
str = ss.str();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static GdtEntry* GetEntryByName(const Gdt& gdt, const std::string& name)
|
||||
{
|
||||
for (const auto& entry : gdt.m_entries)
|
||||
{
|
||||
if (entry->m_name == name)
|
||||
return entry.get();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool ReadProperties(GdtEntry& entry)
|
||||
{
|
||||
while (PeekChar() == '"')
|
||||
{
|
||||
std::string propertyKey;
|
||||
std::string propertyValue;
|
||||
|
||||
if (!ReadStringContent(propertyKey))
|
||||
return false;
|
||||
|
||||
if (PeekChar() != '"')
|
||||
{
|
||||
PrintError("Expected value string");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadStringContent(propertyValue))
|
||||
return false;
|
||||
|
||||
entry.m_properties.emplace(std::move(propertyKey), std::move(propertyValue));
|
||||
}
|
||||
|
||||
|
||||
if (NextChar() != '}')
|
||||
{
|
||||
PrintError("Expected closing tags");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AddEntry(Gdt& gdt, GdtEntry& entry) const
|
||||
{
|
||||
if(entry.m_name == GdtConst::VERSION_ENTRY_NAME
|
||||
&& entry.m_gdf_name == GdtConst::VERSION_ENTRY_GDF)
|
||||
{
|
||||
auto foundEntry = entry.m_properties.find(GdtConst::VERSION_KEY_GAME);
|
||||
if(foundEntry == entry.m_properties.end())
|
||||
{
|
||||
PrintError("Version does not feature game property");
|
||||
return false;
|
||||
}
|
||||
gdt.m_version.m_game = foundEntry->second;
|
||||
|
||||
foundEntry = entry.m_properties.find(GdtConst::VERSION_KEY_VERSION);
|
||||
if (foundEntry == entry.m_properties.end())
|
||||
{
|
||||
PrintError("Version does not feature version property");
|
||||
return false;
|
||||
}
|
||||
gdt.m_version.m_version = strtol(foundEntry->second.c_str(), nullptr, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
gdt.m_entries.emplace_back(std::make_unique<GdtEntry>(std::move(entry)));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
explicit GdtReader(std::istream& stream)
|
||||
: m_stream(stream),
|
||||
m_char(0),
|
||||
m_peeked(false),
|
||||
m_line(0)
|
||||
{
|
||||
}
|
||||
|
||||
bool Read(Gdt& gdt)
|
||||
{
|
||||
if (NextChar() != '{')
|
||||
{
|
||||
PrintError("Expected opening tag");
|
||||
return false;
|
||||
}
|
||||
|
||||
while (PeekChar() == '"')
|
||||
{
|
||||
GdtEntry entry;
|
||||
|
||||
if (!ReadStringContent(entry.m_name))
|
||||
{
|
||||
PrintError("Failed to read string");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (PeekChar() == '(')
|
||||
{
|
||||
NextChar();
|
||||
if (!ReadStringContent(entry.m_gdf_name))
|
||||
return false;
|
||||
if (NextChar() != ')')
|
||||
{
|
||||
PrintError("Expected closing parenthesis");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (PeekChar() == '[')
|
||||
{
|
||||
NextChar();
|
||||
std::string parentName;
|
||||
if (!ReadStringContent(parentName))
|
||||
return false;
|
||||
if (NextChar() != ']')
|
||||
{
|
||||
PrintError("Expected closing square brackets");
|
||||
return false;
|
||||
}
|
||||
entry.m_parent = GetEntryByName(gdt, parentName);
|
||||
if (entry.m_parent == nullptr)
|
||||
{
|
||||
PrintError("Could not find parent with name");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (NextChar() != '{')
|
||||
{
|
||||
PrintError("Expected opening tag for entries");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ReadProperties(entry))
|
||||
return false;
|
||||
|
||||
if (!AddEntry(gdt, entry))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (NextChar() != '}')
|
||||
{
|
||||
PrintError("Expected closing tags");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class GdtWriter
|
||||
{
|
||||
std::ostream& m_stream;
|
||||
unsigned m_intendation_level;
|
||||
|
||||
void DoIntendation() const
|
||||
{
|
||||
for (auto i = 0u; i < m_intendation_level; i++)
|
||||
m_stream << "\t";
|
||||
}
|
||||
|
||||
void WriteVersion(const GdtVersion& gdtVersion)
|
||||
{
|
||||
GdtEntry versionEntry;
|
||||
versionEntry.m_name = GdtConst::VERSION_ENTRY_NAME;
|
||||
versionEntry.m_gdf_name = GdtConst::VERSION_ENTRY_GDF;
|
||||
versionEntry.m_properties[GdtConst::VERSION_KEY_GAME] = gdtVersion.m_game;
|
||||
versionEntry.m_properties[GdtConst::VERSION_KEY_VERSION] = std::to_string(gdtVersion.m_version);
|
||||
|
||||
WriteEntry(versionEntry);
|
||||
}
|
||||
|
||||
void WriteEntry(const GdtEntry& entry)
|
||||
{
|
||||
DoIntendation();
|
||||
m_stream << "\"" << entry.m_name << "\" ";
|
||||
if (entry.m_parent)
|
||||
m_stream << "[ \"" << entry.m_parent->m_name << "\" ]\n";
|
||||
else
|
||||
m_stream << "( \"" << entry.m_gdf_name << " )\n";
|
||||
m_stream << "{\n";
|
||||
|
||||
m_intendation_level++;
|
||||
|
||||
for (const auto& [propertyKey, propertyValue] : entry.m_properties)
|
||||
{
|
||||
DoIntendation();
|
||||
m_stream << "\"" << propertyKey << "\" \"" << propertyValue << "\"\n";
|
||||
}
|
||||
|
||||
m_intendation_level--;
|
||||
m_stream << "}\n";
|
||||
}
|
||||
|
||||
public:
|
||||
explicit GdtWriter(std::ostream& stream)
|
||||
: m_stream(stream),
|
||||
m_intendation_level(0)
|
||||
{
|
||||
}
|
||||
|
||||
void Write(const Gdt& gdt)
|
||||
{
|
||||
m_stream << "{\n";
|
||||
m_intendation_level++;
|
||||
|
||||
WriteVersion(gdt.m_version);
|
||||
for (const auto& entry : gdt.m_entries)
|
||||
{
|
||||
WriteEntry(*entry);
|
||||
}
|
||||
|
||||
m_intendation_level--;
|
||||
m_stream << "}";
|
||||
}
|
||||
};
|
||||
|
||||
bool Gdt::ReadFromStream(std::istream& stream)
|
||||
{
|
||||
GdtReader reader(stream);
|
||||
return reader.Read(*this);
|
||||
}
|
||||
|
||||
void Gdt::WriteToStream(std::ostream& stream) const
|
||||
{
|
||||
GdtWriter writer(stream);
|
||||
writer.Write(*this);
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
#include "GdtEntry.h"
|
||||
#include "GdtVersion.h"
|
||||
|
||||
class Gdt
|
||||
{
|
||||
public:
|
||||
GdtVersion m_version;
|
||||
std::vector<std::unique_ptr<GdtEntry>> m_entries;
|
||||
|
||||
bool ReadFromStream(std::istream& stream);
|
||||
void WriteToStream(std::ostream& stream) const;
|
||||
};
|
@ -0,0 +1,19 @@
|
||||
#include "GdtEntry.h"
|
||||
|
||||
GdtEntry::GdtEntry()
|
||||
: m_parent(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
GdtEntry::GdtEntry(std::string name, std::string gdfName)
|
||||
: m_name(std::move(name)),
|
||||
m_gdf_name(std::move(gdfName)),
|
||||
m_parent(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
GdtEntry::GdtEntry(std::string name, GdtEntry* parent)
|
||||
: m_name(std::move(name)),
|
||||
m_parent(parent)
|
||||
{
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
class GdtEntry
|
||||
{
|
||||
public:
|
||||
std::string m_name;
|
||||
std::string m_gdf_name;
|
||||
GdtEntry* m_parent;
|
||||
std::map<std::string, std::string> m_properties;
|
||||
|
||||
GdtEntry();
|
||||
GdtEntry(std::string name, std::string gdfName);
|
||||
GdtEntry(std::string name, GdtEntry* parent);
|
||||
};
|
12
src/ObjCommon/Obj/Gdt/GdtVersion.cpp
Normal file
12
src/ObjCommon/Obj/Gdt/GdtVersion.cpp
Normal file
@ -0,0 +1,12 @@
|
||||
#include "GdtVersion.h"
|
||||
|
||||
GdtVersion::GdtVersion()
|
||||
: m_version(0)
|
||||
{
|
||||
}
|
||||
|
||||
GdtVersion::GdtVersion(std::string game, const int version)
|
||||
: m_game(std::move(game)),
|
||||
m_version(version)
|
||||
{
|
||||
}
|
13
src/ObjCommon/Obj/Gdt/GdtVersion.h
Normal file
13
src/ObjCommon/Obj/Gdt/GdtVersion.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
class GdtVersion
|
||||
{
|
||||
public:
|
||||
std::string m_game;
|
||||
int m_version;
|
||||
|
||||
GdtVersion();
|
||||
GdtVersion(std::string game, int version);
|
||||
};
|
240
test/ObjCommonTests/Obj/Gdt/GdtTests.cpp
Normal file
240
test/ObjCommonTests/Obj/Gdt/GdtTests.cpp
Normal file
@ -0,0 +1,240 @@
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "Obj/Gdt/Gdt.h"
|
||||
|
||||
namespace obj::gdt
|
||||
{
|
||||
TEST_CASE("Gdt: Ensure can parse simple gdt", "[gdt]")
|
||||
{
|
||||
std::string gdtString = "{\n"
|
||||
"\t\"test_entry\" ( \"test.gdf\" )\n"
|
||||
"\t{\n"
|
||||
"\t\t\"testkey\" \"testvalue\"\n"
|
||||
"\t\t\"test2key\" \"test2value\"\n"
|
||||
"\t}\n"
|
||||
"}";
|
||||
std::istringstream ss(gdtString);
|
||||
|
||||
Gdt gdt;
|
||||
REQUIRE(gdt.ReadFromStream(ss));
|
||||
|
||||
REQUIRE(gdt.m_entries.size() == 1);
|
||||
|
||||
{
|
||||
const auto& entry = *gdt.m_entries[0];
|
||||
REQUIRE(entry.m_name == "test_entry");
|
||||
REQUIRE(entry.m_gdf_name == "test.gdf");
|
||||
REQUIRE(entry.m_parent == nullptr);
|
||||
REQUIRE(entry.m_properties.size() == 2);
|
||||
|
||||
REQUIRE(entry.m_properties.at("testkey") == "testvalue");
|
||||
REQUIRE(entry.m_properties.at("test2key") == "test2value");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Gdt: Ensure can parse compact gdt", "[gdt]")
|
||||
{
|
||||
std::string gdtString = "{"
|
||||
R"("test_entry"("test.gdf"))"
|
||||
"{"
|
||||
R"("testkey""testvalue")"
|
||||
R"("test2key""test2value")"
|
||||
"}"
|
||||
"}";
|
||||
std::istringstream ss(gdtString);
|
||||
|
||||
Gdt gdt;
|
||||
REQUIRE(gdt.ReadFromStream(ss));
|
||||
|
||||
REQUIRE(gdt.m_entries.size() == 1);
|
||||
|
||||
{
|
||||
const auto& entry = *gdt.m_entries[0];
|
||||
REQUIRE(entry.m_name == "test_entry");
|
||||
REQUIRE(entry.m_gdf_name == "test.gdf");
|
||||
REQUIRE(entry.m_parent == nullptr);
|
||||
REQUIRE(entry.m_properties.size() == 2);
|
||||
|
||||
REQUIRE(entry.m_properties.at("testkey") == "testvalue");
|
||||
REQUIRE(entry.m_properties.at("test2key") == "test2value");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Gdt: Ensure can parse version section", "[gdt]")
|
||||
{
|
||||
std::string gdtString = "{"
|
||||
R"("version" ( "version.gdf" ))"
|
||||
"{"
|
||||
R"("game" "t6")"
|
||||
R"("version" "1337")"
|
||||
"}"
|
||||
"}";
|
||||
std::istringstream ss(gdtString);
|
||||
|
||||
Gdt gdt;
|
||||
REQUIRE(gdt.ReadFromStream(ss));
|
||||
|
||||
REQUIRE(gdt.m_entries.empty());
|
||||
REQUIRE(gdt.m_version.m_game == "t6");
|
||||
REQUIRE(gdt.m_version.m_version == 1337);
|
||||
}
|
||||
|
||||
TEST_CASE("Gdt: Ensure can parse version section and entries", "[gdt]")
|
||||
{
|
||||
std::string gdtString = "{"
|
||||
R"("version" ( "version.gdf" ))"
|
||||
"{"
|
||||
R"("game" "t6")"
|
||||
R"("version" "1337")"
|
||||
"}"
|
||||
R"("test_entry" ( "another_test.gdf" ))"
|
||||
"{"
|
||||
R"("game" "t6")"
|
||||
R"("version" "420")"
|
||||
"}"
|
||||
"}";
|
||||
std::istringstream ss(gdtString);
|
||||
|
||||
Gdt gdt;
|
||||
REQUIRE(gdt.ReadFromStream(ss));
|
||||
|
||||
REQUIRE(gdt.m_version.m_game == "t6");
|
||||
REQUIRE(gdt.m_version.m_version == 1337);
|
||||
|
||||
REQUIRE(gdt.m_entries.size() == 1);
|
||||
|
||||
{
|
||||
const auto& entry = *gdt.m_entries[0];
|
||||
REQUIRE(entry.m_name == "test_entry");
|
||||
REQUIRE(entry.m_gdf_name == "another_test.gdf");
|
||||
REQUIRE(entry.m_parent == nullptr);
|
||||
REQUIRE(entry.m_properties.size() == 2);
|
||||
|
||||
REQUIRE(entry.m_properties.at("game") == "t6");
|
||||
REQUIRE(entry.m_properties.at("version") == "420");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Gdt: Ensure can parse multiple entries", "[gdt]")
|
||||
{
|
||||
std::string gdtString = "{"
|
||||
R"("version" ( "version.gdf" ))"
|
||||
"{"
|
||||
R"("game" "t6")"
|
||||
R"("version" "1337")"
|
||||
"}"
|
||||
R"("test_entry" ( "another_test.gdf" ))"
|
||||
"{"
|
||||
R"("game" "t6")"
|
||||
R"("version" "420")"
|
||||
"}"
|
||||
R"("yet_another_entry" ( "super_kewl_asset_type.gdf" ))"
|
||||
"{"
|
||||
R"("name" "hello")"
|
||||
R"("value" "asdf")"
|
||||
R"("value2" "22")"
|
||||
"}"
|
||||
R"("final_entry" ( "quite_boring.gdf" ))"
|
||||
"{"
|
||||
R"("_HI_" "Hello World")"
|
||||
"}"
|
||||
"}";
|
||||
std::istringstream ss(gdtString);
|
||||
|
||||
Gdt gdt;
|
||||
REQUIRE(gdt.ReadFromStream(ss));
|
||||
|
||||
REQUIRE(gdt.m_version.m_game == "t6");
|
||||
REQUIRE(gdt.m_version.m_version == 1337);
|
||||
|
||||
REQUIRE(gdt.m_entries.size() == 3);
|
||||
|
||||
{
|
||||
const auto& entry = *gdt.m_entries[0];
|
||||
REQUIRE(entry.m_name == "test_entry");
|
||||
REQUIRE(entry.m_gdf_name == "another_test.gdf");
|
||||
REQUIRE(entry.m_parent == nullptr);
|
||||
REQUIRE(entry.m_properties.size() == 2);
|
||||
|
||||
REQUIRE(entry.m_properties.at("game") == "t6");
|
||||
REQUIRE(entry.m_properties.at("version") == "420");
|
||||
}
|
||||
|
||||
{
|
||||
const auto& entry = *gdt.m_entries[1];
|
||||
REQUIRE(entry.m_name == "yet_another_entry");
|
||||
REQUIRE(entry.m_gdf_name == "super_kewl_asset_type.gdf");
|
||||
REQUIRE(entry.m_parent == nullptr);
|
||||
REQUIRE(entry.m_properties.size() == 3);
|
||||
|
||||
REQUIRE(entry.m_properties.at("name") == "hello");
|
||||
REQUIRE(entry.m_properties.at("value") == "asdf");
|
||||
REQUIRE(entry.m_properties.at("value2") == "22");
|
||||
}
|
||||
|
||||
{
|
||||
const auto& entry = *gdt.m_entries[2];
|
||||
REQUIRE(entry.m_name == "final_entry");
|
||||
REQUIRE(entry.m_gdf_name == "quite_boring.gdf");
|
||||
REQUIRE(entry.m_parent == nullptr);
|
||||
REQUIRE(entry.m_properties.size() == 1);
|
||||
|
||||
REQUIRE(entry.m_properties.at("_HI_") == "Hello World");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Gdt: Ensure can parse entries with parent", "[gdt]")
|
||||
{
|
||||
std::string gdtString = "{"
|
||||
R"("version" ( "version.gdf" ))"
|
||||
"{"
|
||||
R"("game" "t6")"
|
||||
R"("version" "1337")"
|
||||
"}"
|
||||
R"("test_entry" ( "another_test.gdf" ))"
|
||||
"{"
|
||||
R"("game" "t6")"
|
||||
R"("version" "420")"
|
||||
"}"
|
||||
R"("yet_another_entry" [ "test_entry" ])"
|
||||
"{"
|
||||
R"("name" "hello")"
|
||||
R"("value" "asdf")"
|
||||
R"("value2" "22")"
|
||||
"}"
|
||||
"}";
|
||||
std::istringstream ss(gdtString);
|
||||
|
||||
Gdt gdt;
|
||||
REQUIRE(gdt.ReadFromStream(ss));
|
||||
|
||||
REQUIRE(gdt.m_version.m_game == "t6");
|
||||
REQUIRE(gdt.m_version.m_version == 1337);
|
||||
|
||||
REQUIRE(gdt.m_entries.size() == 2);
|
||||
|
||||
{
|
||||
const auto& entry = *gdt.m_entries[0];
|
||||
REQUIRE(entry.m_name == "test_entry");
|
||||
REQUIRE(entry.m_gdf_name == "another_test.gdf");
|
||||
REQUIRE(entry.m_parent == nullptr);
|
||||
REQUIRE(entry.m_properties.size() == 2);
|
||||
|
||||
REQUIRE(entry.m_properties.at("game") == "t6");
|
||||
REQUIRE(entry.m_properties.at("version") == "420");
|
||||
}
|
||||
|
||||
{
|
||||
const auto& entry = *gdt.m_entries[1];
|
||||
REQUIRE(entry.m_name == "yet_another_entry");
|
||||
REQUIRE(entry.m_parent == gdt.m_entries[0].get());
|
||||
REQUIRE(entry.m_properties.size() == 3);
|
||||
|
||||
REQUIRE(entry.m_properties.at("name") == "hello");
|
||||
REQUIRE(entry.m_properties.at("value") == "asdf");
|
||||
REQUIRE(entry.m_properties.at("value2") == "22");
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user