From cf4c39d2be7d91abb5d2078d6c6e6a6fa0760c82 Mon Sep 17 00:00:00 2001 From: Pierre Bourdon Date: Fri, 6 Sep 2013 19:56:19 +0200 Subject: [PATCH] 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). --- Source/Core/Common/Src/IniFile.cpp | 167 +++++++++++++---------------- Source/Core/Common/Src/IniFile.h | 29 ++++- 2 files changed, 97 insertions(+), 99 deletions(-) diff --git a/Source/Core/Common/Src/IniFile.cpp b/Source/Core/Common/Src/IniFile.cpp index 2eca8df929..1c45a8095e 100644 --- a/Source/Core/Common/Src/IniFile.cpp +++ b/Source/Core/Common/Src/IniFile.cpp @@ -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::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& newV Set(key, temp.c_str()); } -bool IniFile::Section::Get(const char* key, std::vector& 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& out) { std::string temp; bool retval = Get(key, &temp, 0); @@ -145,7 +131,7 @@ bool IniFile::Section::Get(const char* key, std::vector& 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::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::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 &lines) { Section* section = GetOrCreateSection(sectionName); - section->lines.clear(); - for (std::vector::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::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& keys) c const Section* section = GetSection(sectionName); if (!section) return false; - keys.clear(); - for (std::vector::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::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; } diff --git a/Source/Core/Common/Src/IniFile.h b/Source/Core/Common/Src/IniFile.h index bad26ca090..0cf4ab902b 100644 --- a/Source/Core/Common/Src/IniFile.h +++ b/Source/Core/Common/Src/IniFile.h @@ -6,11 +6,21 @@ #ifndef _INIFILE_H_ #define _INIFILE_H_ +#include #include +#include #include #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 lines; std::string name; + + std::vector keys_order; + std::map values; + + std::vector 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()); }