XML parsing support for new config system

This commit is contained in:
Bart Trzynadlowski 2016-07-07 04:59:10 +00:00
parent bcc663d4eb
commit 724dc93294
4 changed files with 157 additions and 5 deletions

View file

@ -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

View file

@ -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)

View file

@ -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);

View file

@ -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;
} }