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:
Pierre Bourdon 2013-09-06 19:56:19 +02:00
parent d1e96c7282
commit cf4c39d2be
2 changed files with 97 additions and 99 deletions

View File

@ -20,7 +20,7 @@
namespace { 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] == '#') if (line[0] == '#')
return; 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) void IniFile::Section::Set(const char* key, const char* newValue)
{ {
std::string value; auto it = values.find(key);
std::string* line = GetLine(key, &value); if (it != values.end())
if (line) it->second = newValue;
{
// Change the value - keep the key and comment
*line = StripSpaces(key) + " = " + newValue;
}
else else
{ {
// The key did not already exist in this section - let's add it. values[key] = newValue;
lines.push_back(std::string(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); 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) void IniFile::Section::Set(const char* key, const float newValue, const float defaultValue)
{ {
if (newValue != 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()); 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; std::string temp;
bool retval = Get(key, &temp, 0); 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); subEnd = temp.find_first_of(",", subStart);
if (subStart != subEnd) if (subStart != subEnd)
// take from first char until next , // 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 // Find the next non , char
subStart = temp.find_first_not_of(",", subEnd); 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 bool IniFile::Section::Exists(const char *key) const
{ {
for (std::vector<std::string>::const_iterator iter = lines.begin(); iter != lines.end(); ++iter) return values.find(key) != values.end();
{
std::string lineKey;
ParseLine(*iter, &lineKey, NULL);
if (!strcasecmp(lineKey.c_str(), key))
return true;
}
return false;
} }
bool IniFile::Section::Delete(const char *key) bool IniFile::Section::Delete(const char *key)
{ {
std::string* line = GetLine(key, 0); auto it = values.find(key);
for (std::vector<std::string>::iterator liter = lines.begin(); liter != lines.end(); ++liter) if (it == values.end())
{ return false;
if (line == &*liter)
{ values.erase(it);
lines.erase(liter); keys_order.erase(std::find(keys_order.begin(), keys_order.end(), key));
return true; return true;
}
}
return false;
} }
// IniFile // 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) void IniFile::SetLines(const char* sectionName, const std::vector<std::string> &lines)
{ {
Section* section = GetOrCreateSection(sectionName); Section* section = GetOrCreateSection(sectionName);
section->lines.clear(); section->lines = lines;
for (std::vector<std::string>::const_iterator iter = lines.begin(); iter != lines.end(); ++iter)
{
section->lines.push_back(*iter);
}
} }
bool IniFile::DeleteKey(const char* sectionName, const char* key) 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); Section* section = GetSection(sectionName);
if (!section) if (!section)
return false; return false;
std::string* line = section->GetLine(key, 0); return section->Delete(key);
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 a list of all keys in a section // 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); const Section* section = GetSection(sectionName);
if (!section) if (!section)
return false; return false;
keys.clear(); keys = section->keys_order;
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);
}
return true; return true;
} }
@ -364,13 +321,13 @@ void IniFile::SortSections()
std::sort(sections.begin(), sections.end()); 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 // Maximum number of letters in a line
static const int MAX_BYTES = 1024*32; static const int MAX_BYTES = 1024*32;
sections.clear(); if (!keep_current_data)
sections.push_back(Section("")); sections.clear();
// first section consists of the comments before the first real section // first section consists of the comments before the first real section
// Open file // Open file
@ -379,12 +336,13 @@ bool IniFile::Load(const char* filename)
if (in.fail()) return false; if (in.fail()) return false;
Section* current_section = NULL;
while (!in.eof()) while (!in.eof())
{ {
char templine[MAX_BYTES]; char templine[MAX_BYTES];
in.getline(templine, MAX_BYTES); in.getline(templine, MAX_BYTES);
std::string line = templine; std::string line = templine;
#ifndef _WIN32 #ifndef _WIN32
// Check for CRLF eol and convert it to LF // Check for CRLF eol and convert it to LF
if (!line.empty() && line.at(line.size()-1) == '\r') if (!line.empty() && line.at(line.size()-1) == '\r')
@ -405,12 +363,25 @@ bool IniFile::Load(const char* filename)
{ {
// New section! // New section!
std::string sub = line.substr(1, endpos - 1); std::string sub = line.substr(1, endpos - 1);
sections.push_back(Section(sub)); current_section = GetOrCreateSection(sub.c_str());
} }
} }
else 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; 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) for (auto iter = sections.begin(); iter != sections.end(); ++iter)
{ {
const Section& section = *iter; const Section& section = *iter;
if (section.name != "") if (section.keys_order.size() != 0 || section.lines.size() != 0)
{
out << "[" << section.name << "]" << std::endl; 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; for (auto liter = section.lines.begin(); liter != section.lines.end(); ++liter)
out << s << std::endl; {
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(); out.close();
return true; return true;
} }

View File

@ -6,11 +6,21 @@
#ifndef _INIFILE_H_ #ifndef _INIFILE_H_
#define _INIFILE_H_ #define _INIFILE_H_
#include <map>
#include <string> #include <string>
#include <set>
#include <vector> #include <vector>
#include "StringUtil.h" #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 class IniFile
{ {
public: public:
@ -25,7 +35,6 @@ public:
bool Exists(const char *key) const; bool Exists(const char *key) const;
bool Delete(const char *key); 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 char* newValue);
void Set(const char* key, const std::string& newValue, const std::string& defaultValue); void Set(const char* key, const std::string& newValue, const std::string& defaultValue);
@ -68,12 +77,24 @@ public:
} }
protected: protected:
std::vector<std::string> lines;
std::string name; 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 char* filename);
bool Save(const std::string &filename) { return Save(filename.c_str()); } bool Save(const std::string &filename) { return Save(filename.c_str()); }