mirror of
https://github.com/RetroDECK/Supermodel.git
synced 2024-11-25 15:15:40 +00:00
XML parsing support for new config system
This commit is contained in:
parent
bcc663d4eb
commit
724dc93294
|
@ -136,7 +136,8 @@ OBJ = $(OBJ_DIR)/Format.o $(OBJ_DIR)/NewConfig.o \
|
||||||
$(OBJ_DIR)/amp_layer2.o $(OBJ_DIR)/amp_layer3.o $(OBJ_DIR)/amp_misc2.o $(OBJ_DIR)/amp_position.o $(OBJ_DIR)/amp_transform.o \
|
$(OBJ_DIR)/amp_layer2.o $(OBJ_DIR)/amp_layer3.o $(OBJ_DIR)/amp_misc2.o $(OBJ_DIR)/amp_position.o $(OBJ_DIR)/amp_transform.o \
|
||||||
$(OBJ_DIR)/amp_util.o \
|
$(OBJ_DIR)/amp_util.o \
|
||||||
$(OBJ_DIR)/Crypto.o \
|
$(OBJ_DIR)/Crypto.o \
|
||||||
$(OBJ_DIR)/Logger.o
|
$(OBJ_DIR)/Logger.o \
|
||||||
|
$(OBJ_DIR)/tinyxml2.o
|
||||||
|
|
||||||
|
|
||||||
# If built-in debugger enabled, include all debugging classes
|
# If built-in debugger enabled, include all debugging classes
|
||||||
|
@ -158,9 +159,9 @@ ppcd: $(BIN_DIR) $(OBJ_DIR)
|
||||||
$(CXX) Src/CPU/PowerPC/PPCDisasm.cpp $(CPPFLAGS) -DSTANDALONE -o $(OBJ_DIR)/ppcd.o
|
$(CXX) Src/CPU/PowerPC/PPCDisasm.cpp $(CPPFLAGS) -DSTANDALONE -o $(OBJ_DIR)/ppcd.o
|
||||||
$(LD) -o $(BIN_DIR)/ppcd.exe -mconsole $(OBJ_DIR)/ppcd.o
|
$(LD) -o $(BIN_DIR)/ppcd.exe -mconsole $(OBJ_DIR)/ppcd.o
|
||||||
|
|
||||||
tests: $(BIN_DIR) $(OBJ_DIR) $(OBJ) Src/Util/Test_Config.cpp
|
tests: $(BIN_DIR) $(OBJ_DIR) $(OBJ) Src/Util/Test_Config.cpp Src/Pkgs/tinyxml2.cpp
|
||||||
$(CXX) Src/Util/Test_Config.cpp $(CPPFLAGS) -o $(OBJ_DIR)/Test_Config.o
|
$(CXX) Src/Util/Test_Config.cpp $(CPPFLAGS) -o $(OBJ_DIR)/Test_Config.o
|
||||||
$(LD) -o $(BIN_DIR)/test_config.exe -mconsole $(OBJ_DIR)/Test_Config.o $(OBJ_DIR)/Format.o $(OBJ_DIR)/NewConfig.o $(OBJ_DIR)/Logger.o
|
$(LD) -o $(BIN_DIR)/test_config.exe -mconsole $(OBJ_DIR)/Test_Config.o $(OBJ_DIR)/Format.o $(OBJ_DIR)/NewConfig.o $(OBJ_DIR)/Logger.o $(OBJ_DIR)/tinyxml2.o
|
||||||
|
|
||||||
$(BIN_DIR):
|
$(BIN_DIR):
|
||||||
mkdir $(BIN_DIR)
|
mkdir $(BIN_DIR)
|
||||||
|
@ -259,6 +260,9 @@ $(OBJ_DIR)/%.o: Src/OSD/Windows/%.cpp
|
||||||
$(OBJ_DIR)/%.o: Src/Pkgs/%.c
|
$(OBJ_DIR)/%.o: Src/Pkgs/%.c
|
||||||
$(CC) $< $(CFLAGS) -o $(OBJ_DIR)/$(*F).o
|
$(CC) $< $(CFLAGS) -o $(OBJ_DIR)/$(*F).o
|
||||||
|
|
||||||
|
$(OBJ_DIR)/%.o: Src/Pkgs/%.cpp Src/Pkgs/%.h
|
||||||
|
$(CXX) $< $(CPPFLAGS) -o $(OBJ_DIR)/$(*F).o
|
||||||
|
|
||||||
$(OBJ_DIR)/%.o: Src/Util/%.cpp Src/Util/%.h
|
$(OBJ_DIR)/%.o: Src/Util/%.cpp Src/Util/%.h
|
||||||
$(CXX) $< $(CPPFLAGS) -o $(OBJ_DIR)/$(*F).o
|
$(CXX) $< $(CPPFLAGS) -o $(OBJ_DIR)/$(*F).o
|
||||||
|
|
||||||
|
|
|
@ -67,8 +67,10 @@
|
||||||
|
|
||||||
#include "Util/NewConfig.h"
|
#include "Util/NewConfig.h"
|
||||||
#include "OSD/Logger.h"
|
#include "OSD/Logger.h"
|
||||||
|
#include "Pkgs/tinyxml2.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <queue>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
namespace Util
|
namespace Util
|
||||||
|
@ -198,6 +200,66 @@ namespace Util
|
||||||
return std::shared_ptr<Node>(new Node());
|
return std::shared_ptr<Node>(new Node());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void PopulateFromXML(Util::Config::Node::Ptr_t &config, const tinyxml2::XMLDocument &xml)
|
||||||
|
{
|
||||||
|
using namespace tinyxml2;
|
||||||
|
std::queue<std::pair<const XMLElement *, Util::Config::Node *>> q;
|
||||||
|
|
||||||
|
// Push the top level of the XML tree
|
||||||
|
for (const XMLElement *e = xml.RootElement(); e != 0; e = e->NextSiblingElement())
|
||||||
|
q.push( { e, config.get() } );
|
||||||
|
|
||||||
|
// Process the elements in order, pushing subsequent levels along with the
|
||||||
|
// config nodes to add them to
|
||||||
|
while (!q.empty())
|
||||||
|
{
|
||||||
|
const XMLElement *element = q.front().first;
|
||||||
|
Util::Config::Node *parent_node = q.front().second;
|
||||||
|
q.pop();
|
||||||
|
|
||||||
|
// Create a config entry for this XML element
|
||||||
|
Util::Config::Node *node = &parent_node->Create(element->Name(), element->GetText() ? std::string(element->GetText()) : std::string());
|
||||||
|
|
||||||
|
// Create entries for each attribute
|
||||||
|
for (const XMLAttribute *a = element->FirstAttribute(); a != 0; a = a->Next())
|
||||||
|
node->Create(a->Name(), a->Value());
|
||||||
|
|
||||||
|
// Push all child elements
|
||||||
|
for (const XMLElement *e = element->FirstChildElement(); e != 0; e = e->NextSiblingElement())
|
||||||
|
q.push( { e, node } );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Node::Ptr_t FromXML(const std::string &text)
|
||||||
|
{
|
||||||
|
Node::Ptr_t config = std::make_shared<Util::Config::Node>("xml");
|
||||||
|
using namespace tinyxml2;
|
||||||
|
XMLDocument xml;
|
||||||
|
auto ret = xml.Parse(text.c_str());
|
||||||
|
if (ret != XML_SUCCESS)
|
||||||
|
{
|
||||||
|
ErrorLog("Failed to parse XML (%s).", xml.ErrorName());
|
||||||
|
return CreateEmpty();
|
||||||
|
}
|
||||||
|
PopulateFromXML(config, xml);
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node::Ptr_t FromXMLFile(const std::string &filename)
|
||||||
|
{
|
||||||
|
Node::Ptr_t config = std::make_shared<Util::Config::Node>("xml");
|
||||||
|
using namespace tinyxml2;
|
||||||
|
XMLDocument xml;
|
||||||
|
auto ret = xml.LoadFile(filename.c_str());
|
||||||
|
if (ret != XML_SUCCESS)
|
||||||
|
{
|
||||||
|
ErrorLog("Failed to parse %s (%s).", filename.c_str(), xml.ErrorName());
|
||||||
|
return CreateEmpty();
|
||||||
|
}
|
||||||
|
PopulateFromXML(config, xml);
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
static std::string StripComment(const std::string &line)
|
static std::string StripComment(const std::string &line)
|
||||||
{
|
{
|
||||||
// Find first semicolon not enclosed in ""
|
// Find first semicolon not enclosed in ""
|
||||||
|
@ -309,6 +371,9 @@ namespace Util
|
||||||
* - Nodes from x and y with children are ignored as per INI semantics.
|
* - Nodes from x and y with children are ignored as per INI semantics.
|
||||||
* The presence of children indicates a section, not a setting.
|
* The presence of children indicates a section, not a setting.
|
||||||
* - Settings from y override settings in x if already present.
|
* - Settings from y override settings in x if already present.
|
||||||
|
* - If multiple settings with the same key are present in either of the
|
||||||
|
* source configs (which technically violates INI semantics), the last
|
||||||
|
* ends up being used.
|
||||||
* - x's key is retained.
|
* - x's key is retained.
|
||||||
*/
|
*/
|
||||||
Node::Ptr_t MergeINISections(const Node &x, const Node &y)
|
Node::Ptr_t MergeINISections(const Node &x, const Node &y)
|
||||||
|
@ -318,8 +383,16 @@ namespace Util
|
||||||
// First copy settings from section x
|
// First copy settings from section x
|
||||||
for (auto it = x.begin(); it != x.end(); ++it)
|
for (auto it = x.begin(); it != x.end(); ++it)
|
||||||
{
|
{
|
||||||
|
auto &key = it->Key();
|
||||||
|
auto &value = it->Value();
|
||||||
if (it->IsLeaf())
|
if (it->IsLeaf())
|
||||||
merged.Create(it->Key(), it->Value());
|
{
|
||||||
|
// INI semantics: take care to only create a single setting per key
|
||||||
|
if (merged[key].Empty())
|
||||||
|
merged.Create(key, value);
|
||||||
|
else
|
||||||
|
merged.Get(key).SetValue(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Merge in settings from section y
|
// Merge in settings from section y
|
||||||
for (auto it = y.begin(); it != y.end(); ++it)
|
for (auto it = y.begin(); it != y.end(); ++it)
|
||||||
|
|
|
@ -21,7 +21,7 @@ namespace Util
|
||||||
Ptr_t m_first_child;
|
Ptr_t m_first_child;
|
||||||
Ptr_t m_last_child;
|
Ptr_t m_last_child;
|
||||||
std::map<std::string, Ptr_t> m_children;
|
std::map<std::string, Ptr_t> m_children;
|
||||||
const std::string m_key;
|
const std::string m_key; // this cannot be changed (maps depend on it)
|
||||||
std::string m_value;
|
std::string m_value;
|
||||||
static Node s_empty_node; // key, value, and children must always be empty
|
static Node s_empty_node; // key, value, and children must always be empty
|
||||||
|
|
||||||
|
@ -186,6 +186,9 @@ namespace Util
|
||||||
const Node &operator[](const std::string &path) const;
|
const Node &operator[](const std::string &path) const;
|
||||||
const Node &Get(const std::string &path) const;
|
const Node &Get(const std::string &path) const;
|
||||||
void Print(size_t indent_level = 0) const;
|
void Print(size_t indent_level = 0) const;
|
||||||
|
//TODO: this API is confusing. Create() -> Add() and add a Set() which
|
||||||
|
// modifies existing settings if they exist (INI semantics should
|
||||||
|
// use this).
|
||||||
Node &Create(const std::string &key);
|
Node &Create(const std::string &key);
|
||||||
Node &Create(const std::string &key, const std::string &value);
|
Node &Create(const std::string &key, const std::string &value);
|
||||||
Node(const std::string &key);
|
Node(const std::string &key);
|
||||||
|
@ -194,7 +197,10 @@ namespace Util
|
||||||
~Node();
|
~Node();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//TODO: CreateEmpty() should take a key that defaults to blank
|
||||||
Node::Ptr_t CreateEmpty();
|
Node::Ptr_t CreateEmpty();
|
||||||
|
Node::Ptr_t FromXML(const std::string &text);
|
||||||
|
Node::Ptr_t FromXMLFile(const std::string &filename);
|
||||||
Node::Ptr_t FromINIFile(const std::string &filename);
|
Node::Ptr_t FromINIFile(const std::string &filename);
|
||||||
Node::Ptr_t MergeINISections(const Node &x, const Node &y);
|
Node::Ptr_t MergeINISections(const Node &x, const Node &y);
|
||||||
void WriteINIFile(const std::string &filename, const Node &config, const std::string &header_comment);
|
void WriteINIFile(const std::string &filename, const Node &config, const std::string &header_comment);
|
||||||
|
|
|
@ -56,6 +56,75 @@ int main()
|
||||||
Util::Config::Node::Ptr_t copy = std::make_shared<Util::Config::Node>(*root);
|
Util::Config::Node::Ptr_t copy = std::make_shared<Util::Config::Node>(*root);
|
||||||
copy->Print();
|
copy->Print();
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
|
|
||||||
|
// Parse from XML
|
||||||
|
const char *xml =
|
||||||
|
"<game name=\"scud\"> \n"
|
||||||
|
" <roms> \n"
|
||||||
|
" <region name=\"crom\" stride=\"8\" byte_swap=\"true\"> \n"
|
||||||
|
" <file offset=\"0\" name=\"epr-19734.20\" crc32=\"0xBE897336\" /> \n"
|
||||||
|
" <file offset=\"2\" name=\"epr-19733.19\" crc32=\"0x6565E29A\" /> \n"
|
||||||
|
" <file offset=\"4\" name=\"epr-19732.18\" crc32=\"0x23E864BB\" /> \n"
|
||||||
|
" <file offset=\"6\" name=\"epr-19731.17\" crc32=\"0x3EE6447E\" /> \n"
|
||||||
|
" </region> \n"
|
||||||
|
" <region name=\"banked_crom\" stride=\"8\" byte_swap=\"true\"> \n"
|
||||||
|
" <file offset=\"0x0000000\" name=\"mpr-20364.4\" crc32=\"0xA2A68EF2\" /> \n"
|
||||||
|
" <file offset=\"0x0000002\" name=\"mpr-20363.3\" crc32=\"0x3E3CC6FF\" /> \n"
|
||||||
|
" <file offset=\"0x0000004\" name=\"mpr-20362.2\" crc32=\"0xF7E60DFD\" /> \n"
|
||||||
|
" <file offset=\"0x0000006\" name=\"mpr-20361.1\" crc32=\"0xDDB66C2F\" /> \n"
|
||||||
|
" </region> \n"
|
||||||
|
" </roms> \n"
|
||||||
|
"</game> \n";
|
||||||
|
const char *expected_xml_config_tree =
|
||||||
|
"xml children={ game } \n"
|
||||||
|
" game children={ name roms } \n"
|
||||||
|
" name=scud children={ } \n"
|
||||||
|
" roms children={ region } \n"
|
||||||
|
" region children={ byte_swap file name stride } \n"
|
||||||
|
" name=crom children={ } \n"
|
||||||
|
" stride=8 children={ } \n"
|
||||||
|
" byte_swap=true children={ } \n"
|
||||||
|
" file children={ crc32 name offset } \n"
|
||||||
|
" offset=0 children={ } \n"
|
||||||
|
" name=epr-19734.20 children={ } \n"
|
||||||
|
" crc32=0xBE897336 children={ } \n"
|
||||||
|
" file children={ crc32 name offset } \n"
|
||||||
|
" offset=2 children={ } \n"
|
||||||
|
" name=epr-19733.19 children={ } \n"
|
||||||
|
" crc32=0x6565E29A children={ } \n"
|
||||||
|
" file children={ crc32 name offset } \n"
|
||||||
|
" offset=4 children={ } \n"
|
||||||
|
" name=epr-19732.18 children={ } \n"
|
||||||
|
" crc32=0x23E864BB children={ } \n"
|
||||||
|
" file children={ crc32 name offset } \n"
|
||||||
|
" offset=6 children={ } \n"
|
||||||
|
" name=epr-19731.17 children={ } \n"
|
||||||
|
" crc32=0x3EE6447E children={ } \n"
|
||||||
|
" region children={ byte_swap file name stride } \n"
|
||||||
|
" name=banked_crom children={ } \n"
|
||||||
|
" stride=8 children={ } \n"
|
||||||
|
" byte_swap=true children={ } \n"
|
||||||
|
" file children={ crc32 name offset } \n"
|
||||||
|
" offset=0x0000000 children={ } \n"
|
||||||
|
" name=mpr-20364.4 children={ } \n"
|
||||||
|
" crc32=0xA2A68EF2 children={ } \n"
|
||||||
|
" file children={ crc32 name offset } \n"
|
||||||
|
" offset=0x0000002 children={ } \n"
|
||||||
|
" name=mpr-20363.3 children={ } \n"
|
||||||
|
" crc32=0x3E3CC6FF children={ } \n"
|
||||||
|
" file children={ crc32 name offset } \n"
|
||||||
|
" offset=0x0000004 children={ } \n"
|
||||||
|
" name=mpr-20362.2 children={ } \n"
|
||||||
|
" crc32=0xF7E60DFD children={ } \n"
|
||||||
|
" file children={ crc32 name offset } \n"
|
||||||
|
" offset=0x0000006 children={ } \n"
|
||||||
|
" name=mpr-20361.1 children={ } \n"
|
||||||
|
" crc32=0xDDB66C2F children={ } \n";
|
||||||
|
std::cout << "Expected output:" << std::endl << std::endl << expected_xml_config_tree << std::endl;
|
||||||
|
std::cout << "Actual config tree:" << std::endl << std::endl;
|
||||||
|
Util::Config::Node::Ptr_t xml_config = Util::Config::FromXML(xml);
|
||||||
|
xml_config->Print();
|
||||||
|
std::cout << std::endl;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue