CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Common/Data/Format/IniFile.cpp
Views: 1401
// IniFile1// Taken from Dolphin but relicensed by me, Henrik Rydgard, under the MIT2// license as I wrote the whole thing originally and it has barely changed.34#include <cstdlib>5#include <cstdio>67#include <inttypes.h>89// Hm, what's this for?10#ifndef _MSC_VER11#include <strings.h>12#endif1314#include <algorithm>15#include <iostream>16#include <fstream>17#include <sstream>18#include <string>19#include <vector>2021#include "Common/Data/Format/IniFile.h"22#include "Common/Data/Text/Parsers.h"23#include "Common/File/VFS/VFS.h"24#include "Common/File/FileUtil.h"25#include "Common/Log.h"26#include "Common/Math/math_util.h"2728#include "Common/StringUtils.h"2930// This unescapes # signs.31// NOTE: These parse functions can make better use of the string_view - the pos argument should not be needed, for example.32static bool ParseLineKey(std::string_view line, size_t &pos, std::string *keyOut) {33std::string key = "";3435while (pos < line.size()) {36size_t next = line.find_first_of("=#", pos);37if (next == line.npos || next == 0) {38// Key never ended or empty, invalid.39return false;40} else if (line[next] == '#') {41if (line[next - 1] != '\\') {42// Value commented out before =, so not valid.43return false;44}4546// Escaped.47key += line.substr(pos, next - pos - 1);48key.push_back('#');49pos = next + 1;50} else if (line[next] == '=') {51// Hurray, done.52key += line.substr(pos, next - pos);53pos = next + 1;54break;55}56}5758if (keyOut) {59*keyOut = StripSpaces(key);60}61return true;62}6364static bool ParseLineValue(std::string_view line, size_t &pos, std::string *valueOut) {65std::string value = "";6667std::string_view strippedLine = StripSpaces(line.substr(pos));68if (strippedLine.size() >= 2 && strippedLine[0] == '"' && strippedLine[strippedLine.size() - 1] == '"') {69// Don't remove comment if is surrounded by " "70value += line.substr(pos);71pos = line.npos; // Won't enter the while below72}7374while (pos < line.size()) {75size_t next = line.find('#', pos);76if (next == line.npos) {77value += line.substr(pos);78pos = line.npos;79break;80} else if (line[next - 1] != '\\') {81// It wasn't escaped, so finish before the #.82value += line.substr(pos, next - pos);83// Include the comment's # in pos.84pos = next;85break;86} else {87// Escaped.88value += line.substr(pos, next - pos - 1);89value.push_back('#');90pos = next + 1;91}92}9394if (valueOut) {95*valueOut = StripQuotes(StripSpaces(value));96}9798return true;99}100101static bool ParseLineComment(std::string_view line, size_t &pos, std::string *commentOut) {102// Don't bother with anything if we don't need the comment data.103if (commentOut) {104// Include any whitespace/formatting in the comment.105size_t commentStartPos = pos;106if (commentStartPos != line.npos) {107while (commentStartPos > 0 && line[commentStartPos - 1] <= ' ') {108--commentStartPos;109}110111*commentOut = line.substr(commentStartPos);112} else {113// There was no comment.114commentOut->clear();115}116}117118pos = line.npos;119return true;120}121122static bool ParseLine(std::string_view line, std::string* keyOut, std::string* valueOut, std::string* commentOut)123{124// Rules:125// 1. A line starting with ; is commented out.126// 2. A # in a line (and all the space before it) is the comment.127// 3. A \# in a line is not part of a comment and becomes # in the value.128// 4. Whitespace around values is removed.129// 5. Double quotes around values is removed.130// 6. Value surrounded by double quotes don't parsed to strip comment.131132if (line.size() < 2 || line[0] == ';')133return false;134135size_t pos = 0;136if (!ParseLineKey(line, pos, keyOut))137return false;138if (!ParseLineValue(line, pos, valueOut))139return false;140if (!ParseLineComment(line, pos, commentOut))141return false;142143return true;144}145146static std::string EscapeHash(std::string_view value) {147std::string result = "";148149for (size_t pos = 0; pos < value.size(); ) {150size_t next = value.find('#', pos);151if (next == value.npos) {152result += value.substr(pos);153pos = value.npos;154} else {155result += value.substr(pos, next - pos);156result += "\\#";157pos = next + 1;158}159}160161return result;162}163164void ParsedIniLine::ParseFrom(std::string_view line) {165line = StripSpaces(line);166if (line.empty()) {167key.clear();168value.clear();169comment.clear();170} else if (line[0] == '#') {171key.clear();172value.clear();173comment = line;174} else {175ParseLine(line, &key, &value, &comment);176}177}178179void ParsedIniLine::Reconstruct(std::string *output) const {180if (!key.empty()) {181*output = EscapeHash(key) + " = " + EscapeHash(value) + comment;182} else {183*output = comment;184}185}186187void Section::Clear() {188lines_.clear();189}190191bool Section::GetKeys(std::vector<std::string> &keys) const {192keys.clear();193for (const auto &line : lines_) {194if (!line.Key().empty())195keys.emplace_back(line.Key());196}197return true;198}199200ParsedIniLine *Section::GetLine(std::string_view key) {201for (auto &line : lines_) {202if (equalsNoCase(line.Key(), key))203return &line;204}205return nullptr;206}207208const ParsedIniLine *Section::GetLine(std::string_view key) const {209for (auto &line : lines_) {210if (equalsNoCase(line.Key(), key))211return &line;212}213return nullptr;214}215216void Section::Set(std::string_view key, uint32_t newValue) {217char temp[128];218snprintf(temp, sizeof(temp), "0x%08x", newValue);219Set(key, (const char *)temp);220}221222void Section::Set(std::string_view key, uint64_t newValue) {223char temp[128];224snprintf(temp, sizeof(temp), "0x%016" PRIx64, newValue);225Set(key, (const char *)temp);226}227228void Section::Set(std::string_view key, float newValue) {229_dbg_assert_(!my_isnanorinf(newValue));230char temp[128];231snprintf(temp, sizeof(temp), "%f", newValue);232Set(key, (const char *)temp);233}234235void Section::Set(std::string_view key, double newValue) {236char temp[128];237snprintf(temp, sizeof(temp), "%f", newValue);238Set(key, (const char *)temp);239}240241void Section::Set(std::string_view key, int newValue) {242char temp[128];243snprintf(temp, sizeof(temp), "%d", newValue);244Set(key, (const char *)temp);245}246247void Section::Set(std::string_view key, const char* newValue) {248ParsedIniLine *line = GetLine(key);249if (line) {250line->SetValue(newValue);251} else {252// The key did not already exist in this section - let's add it.253lines_.emplace_back(ParsedIniLine(key, newValue));254}255}256257void Section::Set(std::string_view key, const std::string& newValue, const std::string& defaultValue)258{259if (newValue != defaultValue)260Set(key, newValue);261else262Delete(key);263}264265bool Section::Get(std::string_view key, std::string* value, const char* defaultValue) const {266const ParsedIniLine *line = GetLine(key);267if (!line) {268if (defaultValue) {269*value = defaultValue;270}271return false;272} else {273*value = line->Value();274}275return true;276}277278void Section::Set(std::string_view key, const float newValue, const float defaultValue)279{280if (newValue != defaultValue)281Set(key, newValue);282else283Delete(key);284}285286void Section::Set(std::string_view key, int newValue, int defaultValue)287{288if (newValue != defaultValue)289Set(key, newValue);290else291Delete(key);292}293294void Section::Set(std::string_view key, bool newValue, bool defaultValue)295{296if (newValue != defaultValue)297Set(key, newValue);298else299Delete(key);300}301302void Section::Set(std::string_view key, const std::vector<std::string>& newValues)303{304std::string temp;305// Join the strings with ,306for (const auto &value : newValues) {307temp += value + ",";308}309// remove last ,310if (temp.length())311temp.resize(temp.length() - 1);312Set(key, temp.c_str());313}314315void Section::AddComment(const std::string &comment) {316lines_.emplace_back(ParsedIniLine::CommentOnly("# " + comment));317}318319bool Section::Get(std::string_view key, std::vector<std::string>& values) const {320std::string temp;321bool retval = Get(key, &temp, 0);322if (!retval || temp.empty()) {323return false;324}325// ignore starting , if any326size_t subStart = temp.find_first_not_of(',');327size_t subEnd;328329// split by ,330while (subStart != std::string::npos) {331// Find next ,332subEnd = temp.find_first_of(',', subStart);333if (subStart != subEnd)334// take from first char until next ,335values.push_back(StripSpaces(temp.substr(subStart, subEnd - subStart)));336337// Find the next non , char338subStart = temp.find_first_not_of(',', subEnd);339}340341return true;342}343344bool Section::Get(std::string_view key, int* value, int defaultValue) const {345std::string temp;346bool retval = Get(key, &temp, 0);347if (retval && TryParse(temp, value))348return true;349*value = defaultValue;350return false;351}352353bool Section::Get(std::string_view key, uint32_t* value, uint32_t defaultValue) const {354std::string temp;355bool retval = Get(key, &temp, 0);356if (retval && TryParse(temp, value))357return true;358*value = defaultValue;359return false;360}361362bool Section::Get(std::string_view key, uint64_t* value, uint64_t defaultValue) const {363std::string temp;364bool retval = Get(key, &temp, 0);365if (retval && TryParse(temp, value))366return true;367*value = defaultValue;368return false;369}370371bool Section::Get(std::string_view key, bool* value, bool defaultValue) const {372std::string temp;373bool retval = Get(key, &temp, 0);374if (retval && TryParse(temp, value))375return true;376*value = defaultValue;377return false;378}379380bool Section::Get(std::string_view key, float* value, float defaultValue) const {381std::string temp;382bool retval = Get(key, &temp, 0);383if (retval && TryParse(temp, value))384return true;385*value = defaultValue;386return false;387}388389bool Section::Get(std::string_view key, double* value, double defaultValue) const {390std::string temp;391bool retval = Get(key, &temp, 0);392if (retval && TryParse(temp, value))393return true;394*value = defaultValue;395return false;396}397398bool Section::Exists(std::string_view key) const {399for (auto &line : lines_) {400if (equalsNoCase(key, line.Key()))401return true;402}403return false;404}405406std::map<std::string, std::string> Section::ToMap() const {407std::map<std::string, std::string> outMap;408for (auto &line : lines_) {409if (!line.Key().empty()) {410outMap[std::string(line.Key())] = line.Value();411}412}413return outMap;414}415416bool Section::Delete(std::string_view key) {417ParsedIniLine *line = GetLine(key);418for (auto liter = lines_.begin(); liter != lines_.end(); ++liter) {419if (line == &*liter) {420lines_.erase(liter);421return true;422}423}424return false;425}426427// IniFile428429const Section* IniFile::GetSection(const char* sectionName) const {430for (const auto &iter : sections)431if (!strcasecmp(iter->name().c_str(), sectionName))432return iter.get();433return nullptr;434}435436Section* IniFile::GetSection(const char* sectionName) {437for (const auto &iter : sections)438if (!strcasecmp(iter->name().c_str(), sectionName))439return iter.get();440return nullptr;441}442443Section* IniFile::GetOrCreateSection(const char* sectionName) {444Section* section = GetSection(sectionName);445if (!section) {446sections.push_back(std::make_unique<Section>(sectionName));447section = sections.back().get();448}449return section;450}451452bool IniFile::DeleteSection(const char* sectionName) {453Section* s = GetSection(sectionName);454if (!s)455return false;456457for (auto iter = sections.begin(); iter != sections.end(); ++iter) {458if (iter->get() == s) {459sections.erase(iter);460return true;461}462}463return false;464}465466bool IniFile::Exists(const char* sectionName, const char* key) const {467const Section* section = GetSection(sectionName);468if (!section)469return false;470return section->Exists(key);471}472473bool IniFile::DeleteKey(const char* sectionName, const char* key) {474Section* section = GetSection(sectionName);475if (!section)476return false;477ParsedIniLine *line = section->GetLine(key);478for (auto liter = section->lines_.begin(); liter != section->lines_.end(); ++liter) {479if (line == &(*liter)) {480section->lines_.erase(liter);481return true;482}483}484return false; //shouldn't happen485}486487// Return a list of all keys in a section488bool IniFile::GetKeys(const char* sectionName, std::vector<std::string>& keys) const {489const Section *section = GetSection(sectionName);490if (!section)491return false;492return section->GetKeys(keys);493}494495void IniFile::SortSections()496{497std::sort(sections.begin(), sections.end());498}499500bool IniFile::Load(const Path &path)501{502sections.clear();503sections.push_back(std::make_unique<Section>(""));504// first section consists of the comments before the first real section505506// Open file507std::string data;508if (!File::ReadTextFileToString(path, &data)) {509return false;510}511std::stringstream sstream(data);512bool success = Load(sstream);513return success;514}515516bool IniFile::LoadFromVFS(VFSInterface &vfs, const std::string &filename) {517size_t size;518uint8_t *data = vfs.ReadFile(filename.c_str(), &size);519if (!data)520return false;521std::string str((const char*)data, size);522delete [] data;523524std::stringstream sstream(str);525return Load(sstream);526}527528bool IniFile::Load(std::istream &in) {529// Maximum number of letters in a line530static const int MAX_BYTES = 1024*32;531char *templine = new char[MAX_BYTES]; // avoid using up massive stack space532533while (!(in.eof() || in.fail()))534{535in.getline(templine, MAX_BYTES);536std::string line = templine;537538// Remove UTF-8 byte order marks.539if (line.substr(0, 3) == "\xEF\xBB\xBF") {540line = line.substr(3);541}542543#ifndef _WIN32544// Check for CRLF eol and convert it to LF545if (!line.empty() && line.at(line.size()-1) == '\r') {546line.erase(line.size()-1);547}548#endif549550if (!line.empty()) {551size_t sectionNameEnd = std::string::npos;552if (line[0] == '[') {553sectionNameEnd = line.find(']');554}555556if (sectionNameEnd != std::string::npos) {557// New section!558std::string sub = line.substr(1, sectionNameEnd - 1);559sections.push_back(std::make_unique<Section>(sub));560561if (sectionNameEnd + 1 < line.size()) {562sections.back()->comment = line.substr(sectionNameEnd + 1);563}564} else {565if (sections.empty()) {566sections.push_back(std::make_unique<Section>(""));567}568ParsedIniLine parsedLine;569parsedLine.ParseFrom(line);570sections.back()->lines_.push_back(parsedLine);571}572}573}574575delete[] templine;576return true;577}578579bool IniFile::Save(const Path &filename)580{581FILE *file = File::OpenCFile(filename, "w");582if (!file) {583return false;584}585586// UTF-8 byte order mark. To make sure notepad doesn't go nuts.587// TODO: Do we still need this? It's annoying.588fprintf(file, "\xEF\xBB\xBF");589590for (const auto §ion : sections) {591if (!section->name().empty() && (!section->lines_.empty() || !section->comment.empty())) {592fprintf(file, "[%s]%s\n", section->name().c_str(), section->comment.c_str());593}594for (const auto &line : section->lines_) {595std::string buffer;596line.Reconstruct(&buffer);597fprintf(file, "%s\n", buffer.c_str());598}599}600601fclose(file);602return true;603}604605bool IniFile::Get(const char* sectionName, const char* key, std::string* value, const char* defaultValue)606{607Section* section = GetSection(sectionName);608if (!section) {609if (defaultValue) {610*value = defaultValue;611}612return false;613}614return section->Get(key, value, defaultValue);615}616617bool IniFile::Get(const char *sectionName, const char* key, std::vector<std::string>& values)618{619Section *section = GetSection(sectionName);620if (!section)621return false;622return section->Get(key, values);623}624625bool IniFile::Get(const char* sectionName, const char* key, int* value, int defaultValue)626{627Section *section = GetSection(sectionName);628if (!section) {629*value = defaultValue;630return false;631} else {632return section->Get(key, value, defaultValue);633}634}635636bool IniFile::Get(const char* sectionName, const char* key, uint32_t* value, uint32_t defaultValue)637{638Section *section = GetSection(sectionName);639if (!section) {640*value = defaultValue;641return false;642} else {643return section->Get(key, value, defaultValue);644}645}646647bool IniFile::Get(const char* sectionName, const char* key, uint64_t* value, uint64_t defaultValue)648{649Section *section = GetSection(sectionName);650if (!section) {651*value = defaultValue;652return false;653} else {654return section->Get(key, value, defaultValue);655}656}657658bool IniFile::Get(const char* sectionName, const char* key, bool* value, bool defaultValue)659{660Section *section = GetSection(sectionName);661if (!section) {662*value = defaultValue;663return false;664} else {665return section->Get(key, value, defaultValue);666}667}668669670