IniFile: Support extending the list of loaded keys and sections with data from other ini files.
Changes a lot of parsing code which previously was not aware of the notion of key/value, and operated only with raw lines. Now key/value is the default and lines are handled as raw only if they do not contain =, or they start with $ or + (for Gecko/AR compatibility).
This commit is contained in:
parent
d1e96c7282
commit
cf4c39d2be
|
@ -20,7 +20,7 @@
|
|||
|
||||
namespace {
|
||||
|
||||
static void ParseLine(const std::string& line, std::string* keyOut, std::string* valueOut)
|
||||
void ParseLine(const std::string& line, std::string* keyOut, std::string* valueOut)
|
||||
{
|
||||
if (line[0] == '#')
|
||||
return;
|
||||
|
@ -37,32 +37,15 @@ static void ParseLine(const std::string& line, std::string* keyOut, std::string*
|
|||
|
||||
}
|
||||
|
||||
std::string* IniFile::Section::GetLine(const char* key, std::string* valueOut)
|
||||
{
|
||||
for (std::vector<std::string>::iterator iter = lines.begin(); iter != lines.end(); ++iter)
|
||||
{
|
||||
std::string& line = *iter;
|
||||
std::string lineKey;
|
||||
ParseLine(line, &lineKey, valueOut);
|
||||
if (!strcasecmp(lineKey.c_str(), key))
|
||||
return &line;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void IniFile::Section::Set(const char* key, const char* newValue)
|
||||
{
|
||||
std::string value;
|
||||
std::string* line = GetLine(key, &value);
|
||||
if (line)
|
||||
{
|
||||
// Change the value - keep the key and comment
|
||||
*line = StripSpaces(key) + " = " + newValue;
|
||||
}
|
||||
auto it = values.find(key);
|
||||
if (it != values.end())
|
||||
it->second = newValue;
|
||||
else
|
||||
{
|
||||
// The key did not already exist in this section - let's add it.
|
||||
lines.push_back(std::string(key) + " = " + newValue);
|
||||
values[key] = newValue;
|
||||
keys_order.push_back(key);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,20 +57,6 @@ void IniFile::Section::Set(const char* key, const std::string& newValue, const s
|
|||
Delete(key);
|
||||
}
|
||||
|
||||
bool IniFile::Section::Get(const char* key, std::string* value, const char* defaultValue)
|
||||
{
|
||||
std::string* line = GetLine(key, value);
|
||||
if (!line)
|
||||
{
|
||||
if (defaultValue)
|
||||
{
|
||||
*value = defaultValue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void IniFile::Section::Set(const char* key, const float newValue, const float defaultValue)
|
||||
{
|
||||
if (newValue != defaultValue)
|
||||
|
@ -126,7 +95,24 @@ void IniFile::Section::Set(const char* key, const std::vector<std::string>& newV
|
|||
Set(key, temp.c_str());
|
||||
}
|
||||
|
||||
bool IniFile::Section::Get(const char* key, std::vector<std::string>& values)
|
||||
bool IniFile::Section::Get(const char* key, std::string* value, const char* defaultValue)
|
||||
{
|
||||
auto it = values.find(key);
|
||||
if (it != values.end())
|
||||
{
|
||||
*value = it->second;
|
||||
return true;
|
||||
}
|
||||
else if (defaultValue)
|
||||
{
|
||||
*value = defaultValue;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IniFile::Section::Get(const char* key, std::vector<std::string>& out)
|
||||
{
|
||||
std::string temp;
|
||||
bool retval = Get(key, &temp, 0);
|
||||
|
@ -145,7 +131,7 @@ bool IniFile::Section::Get(const char* key, std::vector<std::string>& values)
|
|||
subEnd = temp.find_first_of(",", subStart);
|
||||
if (subStart != subEnd)
|
||||
// take from first char until next ,
|
||||
values.push_back(StripSpaces(temp.substr(subStart, subEnd - subStart)));
|
||||
out.push_back(StripSpaces(temp.substr(subStart, subEnd - subStart)));
|
||||
|
||||
// Find the next non , char
|
||||
subStart = temp.find_first_not_of(",", subEnd);
|
||||
|
@ -206,28 +192,18 @@ bool IniFile::Section::Get(const char* key, double* value, double defaultValue)
|
|||
|
||||
bool IniFile::Section::Exists(const char *key) const
|
||||
{
|
||||
for (std::vector<std::string>::const_iterator iter = lines.begin(); iter != lines.end(); ++iter)
|
||||
{
|
||||
std::string lineKey;
|
||||
ParseLine(*iter, &lineKey, NULL);
|
||||
if (!strcasecmp(lineKey.c_str(), key))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return values.find(key) != values.end();
|
||||
}
|
||||
|
||||
bool IniFile::Section::Delete(const char *key)
|
||||
{
|
||||
std::string* line = GetLine(key, 0);
|
||||
for (std::vector<std::string>::iterator liter = lines.begin(); liter != lines.end(); ++liter)
|
||||
{
|
||||
if (line == &*liter)
|
||||
{
|
||||
lines.erase(liter);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
auto it = values.find(key);
|
||||
if (it == values.end())
|
||||
return false;
|
||||
|
||||
values.erase(it);
|
||||
keys_order.erase(std::find(keys_order.begin(), keys_order.end(), key));
|
||||
return true;
|
||||
}
|
||||
|
||||
// IniFile
|
||||
|
@ -286,11 +262,7 @@ bool IniFile::Exists(const char* sectionName, const char* key) const
|
|||
void IniFile::SetLines(const char* sectionName, const std::vector<std::string> &lines)
|
||||
{
|
||||
Section* section = GetOrCreateSection(sectionName);
|
||||
section->lines.clear();
|
||||
for (std::vector<std::string>::const_iterator iter = lines.begin(); iter != lines.end(); ++iter)
|
||||
{
|
||||
section->lines.push_back(*iter);
|
||||
}
|
||||
section->lines = lines;
|
||||
}
|
||||
|
||||
bool IniFile::DeleteKey(const char* sectionName, const char* key)
|
||||
|
@ -298,16 +270,7 @@ bool IniFile::DeleteKey(const char* sectionName, const char* key)
|
|||
Section* section = GetSection(sectionName);
|
||||
if (!section)
|
||||
return false;
|
||||
std::string* line = section->GetLine(key, 0);
|
||||
for (std::vector<std::string>::iterator liter = section->lines.begin(); liter != section->lines.end(); ++liter)
|
||||
{
|
||||
if (line == &(*liter))
|
||||
{
|
||||
section->lines.erase(liter);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false; //shouldn't happen
|
||||
return section->Delete(key);
|
||||
}
|
||||
|
||||
// Return a list of all keys in a section
|
||||
|
@ -316,13 +279,7 @@ bool IniFile::GetKeys(const char* sectionName, std::vector<std::string>& keys) c
|
|||
const Section* section = GetSection(sectionName);
|
||||
if (!section)
|
||||
return false;
|
||||
keys.clear();
|
||||
for (std::vector<std::string>::const_iterator liter = section->lines.begin(); liter != section->lines.end(); ++liter)
|
||||
{
|
||||
std::string key;
|
||||
ParseLine(*liter, &key, 0);
|
||||
keys.push_back(key);
|
||||
}
|
||||
keys = section->keys_order;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -364,13 +321,13 @@ void IniFile::SortSections()
|
|||
std::sort(sections.begin(), sections.end());
|
||||
}
|
||||
|
||||
bool IniFile::Load(const char* filename)
|
||||
bool IniFile::Load(const char* filename, bool keep_current_data)
|
||||
{
|
||||
// Maximum number of letters in a line
|
||||
static const int MAX_BYTES = 1024*32;
|
||||
|
||||
sections.clear();
|
||||
sections.push_back(Section(""));
|
||||
if (!keep_current_data)
|
||||
sections.clear();
|
||||
// first section consists of the comments before the first real section
|
||||
|
||||
// Open file
|
||||
|
@ -379,12 +336,13 @@ bool IniFile::Load(const char* filename)
|
|||
|
||||
if (in.fail()) return false;
|
||||
|
||||
Section* current_section = NULL;
|
||||
while (!in.eof())
|
||||
{
|
||||
char templine[MAX_BYTES];
|
||||
in.getline(templine, MAX_BYTES);
|
||||
std::string line = templine;
|
||||
|
||||
|
||||
#ifndef _WIN32
|
||||
// Check for CRLF eol and convert it to LF
|
||||
if (!line.empty() && line.at(line.size()-1) == '\r')
|
||||
|
@ -405,12 +363,25 @@ bool IniFile::Load(const char* filename)
|
|||
{
|
||||
// New section!
|
||||
std::string sub = line.substr(1, endpos - 1);
|
||||
sections.push_back(Section(sub));
|
||||
current_section = GetOrCreateSection(sub.c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sections[sections.size() - 1].lines.push_back(line);
|
||||
if (current_section)
|
||||
{
|
||||
std::string key, value;
|
||||
ParseLine(line, &key, &value);
|
||||
|
||||
// Lines starting with '$' or '+' are kept verbatim. Kind
|
||||
// of a hack, but the support for raw lines inside an INI
|
||||
// is a hack anyway.
|
||||
if ((key == "" && value == "")
|
||||
|| (line.size() >= 1 && (line[0] == '$' || line[0] == '+')))
|
||||
current_section->lines.push_back(line.c_str());
|
||||
else
|
||||
current_section->Set(key, value.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -429,27 +400,33 @@ bool IniFile::Save(const char* filename)
|
|||
return false;
|
||||
}
|
||||
|
||||
// Currently testing if dolphin community can handle the requirements of C++11 compilation
|
||||
// If you get a compiler error on this line, your compiler is probably old.
|
||||
// Update to g++ 4.4 or a recent version of clang (XCode 4.2 on OS X).
|
||||
// If you don't want to update, complain in a google code issue, the dolphin forums or #dolphin-emu.
|
||||
for (auto iter = sections.begin(); iter != sections.end(); ++iter)
|
||||
{
|
||||
const Section& section = *iter;
|
||||
|
||||
if (section.name != "")
|
||||
{
|
||||
if (section.keys_order.size() != 0 || section.lines.size() != 0)
|
||||
out << "[" << section.name << "]" << std::endl;
|
||||
}
|
||||
|
||||
for (std::vector<std::string>::const_iterator liter = section.lines.begin(); liter != section.lines.end(); ++liter)
|
||||
if (section.keys_order.size() == 0)
|
||||
{
|
||||
std::string s = *liter;
|
||||
out << s << std::endl;
|
||||
for (auto liter = section.lines.begin(); liter != section.lines.end(); ++liter)
|
||||
{
|
||||
std::string s = *liter;
|
||||
out << s << std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto kvit = section.keys_order.begin(); kvit != section.keys_order.end(); ++kvit)
|
||||
{
|
||||
auto pair = section.values.find(*kvit);
|
||||
out << pair->first << " = " << pair->second << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,11 +6,21 @@
|
|||
#ifndef _INIFILE_H_
|
||||
#define _INIFILE_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "StringUtil.h"
|
||||
|
||||
struct CaseInsensitiveStringCompare
|
||||
{
|
||||
bool operator() (const std::string& a, const std::string& b) const
|
||||
{
|
||||
return strcasecmp(a.c_str(), b.c_str()) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
class IniFile
|
||||
{
|
||||
public:
|
||||
|
@ -25,7 +35,6 @@ public:
|
|||
bool Exists(const char *key) const;
|
||||
bool Delete(const char *key);
|
||||
|
||||
std::string* GetLine(const char* key, std::string* valueOut);
|
||||
void Set(const char* key, const char* newValue);
|
||||
void Set(const char* key, const std::string& newValue, const std::string& defaultValue);
|
||||
|
||||
|
@ -68,12 +77,24 @@ public:
|
|||
}
|
||||
|
||||
protected:
|
||||
std::vector<std::string> lines;
|
||||
std::string name;
|
||||
|
||||
std::vector<std::string> keys_order;
|
||||
std::map<std::string, std::string, CaseInsensitiveStringCompare> values;
|
||||
|
||||
std::vector<std::string> lines;
|
||||
};
|
||||
|
||||
bool Load(const char* filename);
|
||||
bool Load(const std::string &filename) { return Load(filename.c_str()); }
|
||||
/**
|
||||
* Loads sections and keys.
|
||||
* @param filename filename of the ini file which should be loaded
|
||||
* @param keep_current_data If true, "extends" the currently loaded list of sections and keys with the loaded data (and replaces existing entries). If false, existing data will be erased.
|
||||
* @warning Using any other operations than "Get*" and "Exists" is untested and will behave unexpectedly
|
||||
* @todo This really is just a hack to support having two levels of gameinis (defaults and user-specified) and should eventually be replaced with a less stupid system.
|
||||
*/
|
||||
bool Load(const char* filename, bool keep_current_data = false);
|
||||
bool Load(const std::string &filename, bool keep_current_data = false) { return Load(filename.c_str(), keep_current_data); }
|
||||
|
||||
bool Save(const char* filename);
|
||||
bool Save(const std::string &filename) { return Save(filename.c_str()); }
|
||||
|
||||
|
|
Loading…
Reference in New Issue