Skip to content

Commit

Permalink
Merge pull request #18241 from hrydgard/ini-rewrite
Browse files Browse the repository at this point in the history
Optimize IniFile for faster save/load of config
  • Loading branch information
hrydgard committed Sep 30, 2023
2 parents 526d304 + 2bd2292 commit 2a4d21e
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 150 deletions.
230 changes: 95 additions & 135 deletions Common/Data/Format/IniFile.cpp
Expand Up @@ -29,7 +29,16 @@

#include "Common/StringUtils.h"

static bool ParseLineKey(const std::string &line, size_t &pos, std::string *keyOut) {
bool StringViewEqualCaseInsensitive(const std::string_view lhs, const std::string_view rhs) {
if (lhs.size() != rhs.size()) {
return false;
}
return ::strncasecmp(lhs.data(), rhs.data(), rhs.size()) == 0;
}

// This unescapes # signs.
// NOTE: These parse functions can make better use of the string_view - the pos argument should not be needed, for example.
static bool ParseLineKey(std::string_view line, size_t &pos, std::string *keyOut) {
std::string key = "";

while (pos < line.size()) {
Expand All @@ -44,7 +53,8 @@ static bool ParseLineKey(const std::string &line, size_t &pos, std::string *keyO
}

// Escaped.
key += line.substr(pos, next - pos - 1) + "#";
key += line.substr(pos, next - pos - 1);
key.push_back('#');
pos = next + 1;
} else if (line[next] == '=') {
// Hurray, done.
Expand All @@ -60,11 +70,11 @@ static bool ParseLineKey(const std::string &line, size_t &pos, std::string *keyO
return true;
}

static bool ParseLineValue(const std::string &line, size_t &pos, std::string *valueOut) {
static bool ParseLineValue(std::string_view line, size_t &pos, std::string *valueOut) {
std::string value = "";

std::string strippedLine = StripSpaces(line.substr(pos));
if (strippedLine[0] == '"' && strippedLine[strippedLine.size()-1] == '"') {
std::string_view strippedLine = StripSpaces(line.substr(pos));
if (strippedLine.size() >= 2 && strippedLine[0] == '"' && strippedLine[strippedLine.size() - 1] == '"') {
// Don't remove comment if is surrounded by " "
value += line.substr(pos);
pos = line.npos; // Won't enter the while below
Expand All @@ -84,7 +94,8 @@ static bool ParseLineValue(const std::string &line, size_t &pos, std::string *va
break;
} else {
// Escaped.
value += line.substr(pos, next - pos - 1) + "#";
value += line.substr(pos, next - pos - 1);
value.push_back('#');
pos = next + 1;
}
}
Expand All @@ -96,7 +107,7 @@ static bool ParseLineValue(const std::string &line, size_t &pos, std::string *va
return true;
}

static bool ParseLineComment(const std::string& line, size_t &pos, std::string *commentOut) {
static bool ParseLineComment(std::string_view line, size_t &pos, std::string *commentOut) {
// Don't bother with anything if we don't need the comment data.
if (commentOut) {
// Include any whitespace/formatting in the comment.
Expand All @@ -117,8 +128,7 @@ static bool ParseLineComment(const std::string& line, size_t &pos, std::string *
return true;
}

// Ugh, this is ugly.
static bool ParseLine(const std::string& line, std::string* keyOut, std::string* valueOut, std::string* commentOut)
static bool ParseLine(std::string_view line, std::string* keyOut, std::string* valueOut, std::string* commentOut)
{
// Rules:
// 1. A line starting with ; is commented out.
Expand All @@ -142,7 +152,7 @@ static bool ParseLine(const std::string& line, std::string* keyOut, std::string*
return true;
}

static std::string EscapeComments(const std::string &value) {
static std::string EscapeHash(std::string_view value) {
std::string result = "";

for (size_t pos = 0; pos < value.size(); ) {
Expand All @@ -151,39 +161,53 @@ static std::string EscapeComments(const std::string &value) {
result += value.substr(pos);
pos = value.npos;
} else {
result += value.substr(pos, next - pos) + "\\#";
result += value.substr(pos, next - pos);
result += "\\#";
pos = next + 1;
}
}

return result;
}

void ParsedIniLine::ParseFrom(std::string_view line) {
line = StripSpaces(line);
if (line.empty()) {
key.clear();
value.clear();
comment.clear();
} else if (line[0] == '#') {
key.clear();
value.clear();
comment = line;
} else {
ParseLine(line, &key, &value, &comment);
}
}

void ParsedIniLine::Reconstruct(std::string *output) const {
if (!key.empty()) {
*output = EscapeHash(key) + " = " + EscapeHash(value) + comment;
} else {
*output = comment;
}
}

void Section::Clear() {
lines.clear();
lines_.clear();
}

std::string* Section::GetLine(const char* key, std::string* valueOut, std::string* commentOut)
{
for (std::vector<std::string>::iterator iter = lines.begin(); iter != lines.end(); ++iter)
{
std::string& line = *iter;
std::string lineKey;
ParseLine(line, &lineKey, valueOut, commentOut);
if (!strcasecmp(lineKey.c_str(), key))
ParsedIniLine *Section::GetLine(const char *key) {
for (auto &line : lines_) {
if (StringViewEqualCaseInsensitive(line.Key(), key))
return &line;
}
return nullptr;
}

const std::string* Section::GetLine(const char* key, std::string* valueOut, std::string* commentOut) const
{
for (std::vector<std::string>::const_iterator iter = lines.begin(); iter != lines.end(); ++iter)
{
const std::string& line = *iter;
std::string lineKey;
ParseLine(line, &lineKey, valueOut, commentOut);
if (!strcasecmp(lineKey.c_str(), key))
const ParsedIniLine *Section::GetLine(const char* key) const {
for (auto &line : lines_) {
if (StringViewEqualCaseInsensitive(line.Key(), key))
return &line;
}
return nullptr;
Expand All @@ -209,19 +233,13 @@ void Section::Set(const char* key, int newValue) {
Set(key, StringFromInt(newValue).c_str());
}

void Section::Set(const char* key, const char* newValue)
{
std::string value, commented;
std::string* line = GetLine(key, &value, &commented);
if (line)
{
// Change the value - keep the key and comment
*line = StripSpaces(key) + " = " + EscapeComments(newValue) + commented;
}
else
{
void Section::Set(const char* key, const char* newValue) {
ParsedIniLine *line = GetLine(key);
if (line) {
line->SetValue(newValue);
} else {
// The key did not already exist in this section - let's add it.
lines.emplace_back(std::string(key) + " = " + EscapeComments(newValue));
lines_.emplace_back(ParsedIniLine(key, newValue));
}
}

Expand All @@ -233,16 +251,15 @@ void Section::Set(const char* key, const std::string& newValue, const std::strin
Delete(key);
}

bool Section::Get(const char* key, std::string* value, const char* defaultValue) const
{
const std::string* line = GetLine(key, value, 0);
if (!line)
{
if (defaultValue)
{
bool Section::Get(const char* key, std::string* value, const char* defaultValue) const {
const ParsedIniLine *line = GetLine(key);
if (!line) {
if (defaultValue) {
*value = defaultValue;
}
return false;
} else {
*value = line->Value();
}
return true;
}
Expand Down Expand Up @@ -287,7 +304,7 @@ void Section::Set(const char* key, const std::vector<std::string>& newValues)
}

void Section::AddComment(const std::string &comment) {
lines.emplace_back("# " + comment);
lines_.emplace_back(ParsedIniLine::CommentOnly("# " + comment));
}

bool Section::Get(const char* key, std::vector<std::string>& values) const
Expand Down Expand Up @@ -378,39 +395,29 @@ bool Section::Get(const char* key, double* value, double defaultValue) const
return false;
}

bool 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, NULL);
if (!strcasecmp(lineKey.c_str(), key))
bool Section::Exists(const char *key) const {
for (auto &line : lines_) {
if (StringViewEqualCaseInsensitive(key, line.Key()))
return true;
}
return false;
}

std::map<std::string, std::string> Section::ToMap() const
{
std::map<std::string, std::string> Section::ToMap() const {
std::map<std::string, std::string> outMap;
for (std::vector<std::string>::const_iterator iter = lines.begin(); iter != lines.end(); ++iter)
{
std::string lineKey, lineValue;
if (ParseLine(*iter, &lineKey, &lineValue, NULL)) {
outMap[lineKey] = lineValue;
for (auto &line : lines_) {
if (!line.Key().empty()) {
outMap[std::string(line.Key())] = line.Value();
}
}
return outMap;
}

bool Section::Delete(const char *key)
{
std::string* line = GetLine(key, 0, 0);
for (std::vector<std::string>::iterator liter = lines.begin(); liter != lines.end(); ++liter)
{
if (line == &*liter)
{
lines.erase(liter);
bool Section::Delete(const char *key) {
ParsedIniLine *line = GetLine(key);
for (auto liter = lines_.begin(); liter != lines_.end(); ++liter) {
if (line == &*liter) {
lines_.erase(liter);
return true;
}
}
Expand Down Expand Up @@ -463,83 +470,33 @@ bool IniFile::Exists(const char* sectionName, const char* key) const {
return section->Exists(key);
}

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

bool IniFile::DeleteKey(const char* sectionName, const char* key)
{
bool IniFile::DeleteKey(const char* sectionName, const char* key) {
Section* section = GetSection(sectionName);
if (!section)
return false;
std::string* line = section->GetLine(key, 0, 0);
for (std::vector<std::string>::iterator liter = section->lines.begin(); liter != section->lines.end(); ++liter)
{
if (line == &(*liter))
{
section->lines.erase(liter);
ParsedIniLine *line = section->GetLine(key);
for (auto 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
bool IniFile::GetKeys(const char* sectionName, std::vector<std::string>& keys) const
{
const Section* section = GetSection(sectionName);
bool IniFile::GetKeys(const char* sectionName, std::vector<std::string>& keys) const {
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, 0);
if (!key.empty())
keys.push_back(key);
}
return true;
}

// Return a list of all lines in a section
bool IniFile::GetLines(const char* sectionName, std::vector<std::string>& lines, const bool remove_comments) const
{
const Section* section = GetSection(sectionName);
if (!section)
return false;

lines.clear();
for (std::vector<std::string>::const_iterator iter = section->lines.begin(); iter != section->lines.end(); ++iter)
{
std::string line = StripSpaces(*iter);

if (remove_comments)
{
int commentPos = (int)line.find('#');
if (commentPos == 0)
{
continue;
}

if (commentPos != (int)std::string::npos)
{
line = StripSpaces(line.substr(0, commentPos));
}
}

lines.push_back(line);
for (auto liter = section->lines_.begin(); liter != section->lines_.end(); ++liter) {
if (!liter->Key().empty())
keys.push_back(std::string(liter->Key()));
}

return true;
}


void IniFile::SortSections()
{
std::sort(sections.begin(), sections.end());
Expand Down Expand Up @@ -613,7 +570,9 @@ bool IniFile::Load(std::istream &in) {
if (sections.empty()) {
sections.push_back(std::unique_ptr<Section>(new Section("")));
}
sections.back()->lines.push_back(line);
ParsedIniLine parsedLine;
parsedLine.ParseFrom(line);
sections.back()->lines_.push_back(parsedLine);
}
}
}
Expand All @@ -634,12 +593,13 @@ bool IniFile::Save(const Path &filename)
fprintf(file, "\xEF\xBB\xBF");

for (const auto &section : sections) {
if (!section->name().empty() && (!section->lines.empty() || !section->comment.empty())) {
if (!section->name().empty() && (!section->lines_.empty() || !section->comment.empty())) {
fprintf(file, "[%s]%s\n", section->name().c_str(), section->comment.c_str());
}

for (const std::string &s : section->lines) {
fprintf(file, "%s\n", s.c_str());
for (const auto &line : section->lines_) {
std::string buffer;
line.Reconstruct(&buffer);
fprintf(file, "%s\n", buffer.c_str());
}
}

Expand Down

0 comments on commit 2a4d21e

Please sign in to comment.