diff --git a/Makefiles/Makefile.SDL.Win32.GCC b/Makefiles/Makefile.SDL.Win32.GCC index bed71ae..62c9b88 100644 --- a/Makefiles/Makefile.SDL.Win32.GCC +++ b/Makefiles/Makefile.SDL.Win32.GCC @@ -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_util.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 @@ -158,9 +159,9 @@ ppcd: $(BIN_DIR) $(OBJ_DIR) $(CXX) Src/CPU/PowerPC/PPCDisasm.cpp $(CPPFLAGS) -DSTANDALONE -o $(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 - $(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): mkdir $(BIN_DIR) @@ -259,6 +260,9 @@ $(OBJ_DIR)/%.o: Src/OSD/Windows/%.cpp $(OBJ_DIR)/%.o: Src/Pkgs/%.c $(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 $(CXX) $< $(CPPFLAGS) -o $(OBJ_DIR)/$(*F).o diff --git a/Src/Util/NewConfig.cpp b/Src/Util/NewConfig.cpp index 3e3b515..65e9685 100644 --- a/Src/Util/NewConfig.cpp +++ b/Src/Util/NewConfig.cpp @@ -67,8 +67,10 @@ #include "Util/NewConfig.h" #include "OSD/Logger.h" +#include "Pkgs/tinyxml2.h" #include #include +#include #include namespace Util @@ -198,6 +200,66 @@ namespace Util return std::shared_ptr(new Node()); } + static void PopulateFromXML(Util::Config::Node::Ptr_t &config, const tinyxml2::XMLDocument &xml) + { + using namespace tinyxml2; + std::queue> 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("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("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) { // 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. * The presence of children indicates a section, not a setting. * - 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. */ Node::Ptr_t MergeINISections(const Node &x, const Node &y) @@ -318,8 +383,16 @@ namespace Util // First copy settings from section x for (auto it = x.begin(); it != x.end(); ++it) { + auto &key = it->Key(); + auto &value = it->Value(); 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 for (auto it = y.begin(); it != y.end(); ++it) diff --git a/Src/Util/NewConfig.h b/Src/Util/NewConfig.h index fe401ff..c4324bb 100644 --- a/Src/Util/NewConfig.h +++ b/Src/Util/NewConfig.h @@ -21,7 +21,7 @@ namespace Util Ptr_t m_first_child; Ptr_t m_last_child; std::map m_children; - const std::string m_key; + const std::string m_key; // this cannot be changed (maps depend on it) std::string m_value; 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 &Get(const std::string &path) 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, const std::string &value); Node(const std::string &key); @@ -194,7 +197,10 @@ namespace Util ~Node(); }; + //TODO: CreateEmpty() should take a key that defaults to blank 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 MergeINISections(const Node &x, const Node &y); void WriteINIFile(const std::string &filename, const Node &config, const std::string &header_comment); diff --git a/Src/Util/Test_Config.cpp b/Src/Util/Test_Config.cpp index 0602571..5ee7752 100644 --- a/Src/Util/Test_Config.cpp +++ b/Src/Util/Test_Config.cpp @@ -56,6 +56,75 @@ int main() Util::Config::Node::Ptr_t copy = std::make_shared(*root); copy->Print(); std::cout << std::endl; + + // Parse from XML + const char *xml = + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \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; } \ No newline at end of file