/* * INI Processor - a small library which allows you parsing INI-files * * Copyright (c) 2015-2020 Vitaly Novichkov * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ //#define USE_FILE_MAPPER /* Stop parsing on first error (default is to keep parsing). */ //#define INI_STOP_ON_FIRST_ERROR #include "ini_processing.h" #include #include #include #include #include #include #include #include #ifdef _WIN32 #ifdef _MSC_VER #ifdef _WIN64 typedef __int64 ssize_t; #else typedef __int32 ssize_t; #endif #define NOMINMAX //Don't override std::min and std::max #endif #include #endif #ifdef USE_FILE_MAPPER /*****Replace this with right path to file mapper class*****/ #include "../fileMapper/file_mapper.h" #endif static const unsigned char utfbom[3] = {0xEF, 0xBB, 0xBF}; enum { Space = 0x01, Special = 0x02, INIParamEq = 0x04 }; static const unsigned char charTraits[256] = { // Space: '\t', '\n', '\r', ' ' // Special: '\n', '\r', '"', ';', '=', '\\' // INIParamEq: ':', '=' 0, 0, 0, 0, 0, 0, 0, 0, 0, Space, Space | Special, 0, 0, Space | Special, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Space, 0, Special, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, INIParamEq, Special, 0, Special | INIParamEq, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Special, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; #if 0//for speed comparison who faster - macro or inline function. Seems speeds are same #define IS_SPACE(c) (charTraits[static_cast(c)] & Space) #define IS_SPECIAL(c) (charTraits[static_cast(c)] & Special) #define IS_INIEQUAL(c) (charTraits[static_cast(c)] & INIParamEq) #else inline unsigned char IS_SPACE(char &c) { return (charTraits[static_cast(c)] & Space); } inline unsigned char IS_SPECIAL(char &c) { return (charTraits[static_cast(c)] & Special); } inline unsigned char IS_INIEQUAL(char &c) { return (charTraits[static_cast(c)] & INIParamEq); } #endif /* Strip whitespace chars off end of given string, in place. Return s. */ inline char *rstrip(char *s) { char *p = s + strlen(s); while(p > s && IS_SPACE(*--p)) *p = '\0'; return s; } /* Return pointer to first non-whitespace char in given string. */ inline char *lskip(char *s) { while(*s && IS_SPACE(*s)) s++; return reinterpret_cast(s); } inline char *lrtrim(char *s) { while(*s && IS_SPACE(*s)) s++; char *p = s + strlen(s); while(p > s && IS_SPACE(*--p)) *p = '\0'; return s; } /* Return pointer to first char c or ';' comment in given string, or pointer to null at end of string if neither found. ';' must be prefixed by a whitespace character to register as a comment. */ inline char *find_char_or_comment(char *s, char c) { unsigned char was_whitespace = 0; while(*s && *s != c && !(was_whitespace && *s == ';')) { was_whitespace = IS_SPACE(*s); s++; } return s; } inline char *find_inieq_or_comment(char *s) { unsigned char was_whitespace = 0; while(*s && (!IS_INIEQUAL(*s)) && !(was_whitespace && *s == ';')) { was_whitespace = IS_SPACE(*s); s++; } return s; } inline char *removeQuotes(char *begin, char *end) { if((*begin == '\0') || (begin == end)) return begin; if((*begin == '"') && (begin + 1 != end)) begin++; else return begin; if(*(end - 1) == '"') *(end - 1) = '\0'; return begin; } inline char *unescapeString(char* str) { char *src, *dst; src = str; dst = str; while(*src) { if(*src == '\\') { src++; switch(*src) { case 'n': *dst = '\n'; break; case 'r': *dst = '\r'; break; case 't': *dst = '\t'; break; default: *dst = *src; break; } } else if(src != dst) { *dst = *src; } src++; dst++; } *dst = '\0'; return str; } //Remove comment line from a tail of value inline void skipcomment(char *value) { unsigned char quoteDepth = 0; while(*value) { if(quoteDepth > 0) { if(*value == '\\') { value++; continue; } if(*value == '"') --quoteDepth; } else if(*value == '"') ++quoteDepth; if((quoteDepth == 0) && (*value == ';')) { *value = '\0'; break; } value++; } } inline bool memfgets(char *&line, char *data, char *&pos, char *end) { line = pos; while(pos != end) { if(*pos == '\n') { if((pos > data) && (*(pos - 1) == '\r')) *((pos++) - 1) = '\0';//Support CRLF too else *(pos++) = '\0'; break; } ++pos; } return (pos != line); //EOF is a moment when position wasn't changed. //If do check "pos != end", will be an inability to read last line. //this logic allows detect true EOF when line is really eof } /* See documentation in header file. */ bool IniProcessing::parseHelper(char *data, size_t size) { char *section = nullptr; #if defined(INI_ALLOW_MULTILINE) char *prev_name = nullptr; #endif char *start; char *end; char *name; char *value; int lineno = 0; int error = 0; char *line; char *pos_end = data + size; char *pos_cur = data; params::IniKeys *recentKeys = nullptr; /* Scan through file line by line */ //while (fgets(line, INI_MAX_LINE, file) != NULL) while(memfgets(line, data, pos_cur, pos_end)) { lineno++; start = line; if((lineno == 1) && (size >= 3) && (memcmp(start, utfbom, 3) == 0)) start += 3; start = lrtrim(start); if(!*start)//if empty line - skip it away! continue; switch(*start) { case ';': case '#': //if (*start == ';' || *start == '#') { // /* Per Python ConfigParser, allow '#' comments at start of line */ //} continue; case '[': { /* A "[section]" line */ end = find_char_or_comment(start + 1, ']'); if(*end == ']') { *end = '\0'; section = start + 1; //#if defined(INI_ALLOW_MULTILINE) // prev_name = nullptr; //#endif recentKeys = &m_params.iniData[section]; } else if(!error) { /* No ']' found on section line */ m_params.errorCode = ERR_SECTION_SYNTAX; error = lineno; } } break; default: { /* Not a comment, must be a name[=:]value pair */ end = find_inieq_or_comment(start); if(IS_INIEQUAL(*end)) { *end = '\0'; name = rstrip(start); value = lskip(end + 1); end = find_char_or_comment(value, '\0'); #ifndef CASE_SENSITIVE_KEYS for(char *iter = name; *iter != '\0'; ++iter) *iter = (char)tolower(*iter); #endif if(*end == ';') *end = '\0'; rstrip(value); { char *v = value; skipcomment(v); v = rstrip(v); if(!recentKeys) recentKeys = &m_params.iniData["General"]; #ifdef INIDEBUG printf("-> [%s]; %s = %s\n", section, name, v); #endif (*recentKeys)[name] = unescapeString( removeQuotes(v, v + strlen(v)) ); } } else if(!error) { /* No '=' or ':' found on name[=:]value line */ m_params.errorCode = ERR_KEY_SYNTAX; error = lineno; } break; } }//switch(*start) #if defined(INI_STOP_ON_FIRST_ERROR) if(error) break; #endif } m_params.lineWithError = error; return (error == 0); } /* See documentation in header file. */ bool IniProcessing::parseFile(const char *filename) { bool valid = true; char *tmp = nullptr; #ifdef USE_FILE_MAPPER //By mystical reasons, reading whole file form fread() is faster than mapper :-P PGE_FileMapper file(filename); if(!file.data) { m_params.errorCode = ERR_NOFILE; return -1; } tmp = reinterpret_cast(malloc(static_cast(file.size + 1))); if(!tmp) { m_params.errorCode = ERR_NO_MEMORY; return false; } memcpy(tmp, file.data, static_cast(file.size)); *(tmp + file.size) = '\0';//null terminate last line valid = ini_parse_file(tmp, static_cast(file.size)); #else #ifdef _WIN32 //Convert UTF8 file path into UTF16 to support non-ASCII paths on Windows std::wstring dest; dest.resize(std::strlen(filename)); int newSize = MultiByteToWideChar(CP_UTF8, 0, filename, (int)dest.size(), (wchar_t *)dest.c_str(), (int)dest.size()); dest.resize(newSize); FILE *cFile = _wfopen(dest.c_str(), L"rb"); #else FILE *cFile = fopen(filename, "rb"); #endif if(!cFile) { m_params.errorCode = ERR_NOFILE; return false; } fseek(cFile, 0, SEEK_END); ssize_t size = static_cast(ftell(cFile)); if(size < 0) { m_params.errorCode = ERR_KEY_SYNTAX; fclose(cFile); return false; } fseek(cFile, 0, SEEK_SET); tmp = reinterpret_cast(malloc(static_cast(size + 1))); if(!tmp) { fclose(cFile); m_params.errorCode = ERR_NO_MEMORY; return false; } if(fread(tmp, 1, static_cast(size), cFile) != static_cast(size)) valid = false; fclose(cFile); if(valid) { *(tmp + size) = '\0';//null terminate last line try { valid = parseHelper(tmp, static_cast(size)); } catch(...) { valid = false; m_params.errorCode = ERR_SECTION_SYNTAX; } } #endif free(tmp); return valid; } bool IniProcessing::parseMemory(char *mem, size_t size) { bool valid = true; char *tmp = nullptr; tmp = reinterpret_cast(malloc(size + 1)); if(!tmp) { m_params.errorCode = ERR_NO_MEMORY; return false; } memcpy(tmp, mem, static_cast(size)); *(tmp + size) = '\0';//null terminate last line valid = parseHelper(tmp, size); free(tmp); return valid; } IniProcessing::IniProcessing() : m_params{"", false, -1, ERR_OK, false, params::IniSections(), nullptr, ""} {} IniProcessing::IniProcessing(const char *iniFileName, int) : m_params{iniFileName, false, -1, ERR_OK, false, params::IniSections(), nullptr, ""} { open(iniFileName); } IniProcessing::IniProcessing(const std::string &iniFileName, int) : m_params{iniFileName, false, -1, ERR_OK, false, params::IniSections(), nullptr, ""} { open(iniFileName); } #ifdef INI_PROCESSING_ALLOW_QT_TYPES IniProcessing::IniProcessing(const QString &iniFileName, int) : m_params{iniFileName.toStdString(), false, -1, ERR_OK, false, params::IniSections(), nullptr, ""} { open(m_params.filePath); } #endif IniProcessing::IniProcessing(char *memory, size_t size): m_params{"", false, -1, ERR_OK, false, params::IniSections(), nullptr, ""} { openMem(memory, size); } IniProcessing::IniProcessing(const IniProcessing &ip) : m_params(ip.m_params) {} bool IniProcessing::open(const std::string &iniFileName) { std::setlocale(LC_NUMERIC, "C"); if(!iniFileName.empty()) { close(); m_params.errorCode = ERR_OK; m_params.filePath = iniFileName; bool res = parseFile(m_params.filePath.c_str()); #ifdef INIDEBUG if(res) printf("\n==========WOOHOO!!!==============\n\n"); else printf("\n==========OOOUCH!!!==============\n\n"); #endif m_params.opened = res; return res; } m_params.errorCode = ERR_NOFILE; return false; } bool IniProcessing::openMem(char *memory, size_t size) { std::setlocale(LC_NUMERIC, "C"); if((memory != nullptr) && (size > 0)) { close(); m_params.errorCode = ERR_OK; m_params.filePath.clear(); bool res = parseMemory(memory, size); m_params.opened = res; return res; } m_params.errorCode = ERR_NOFILE; return false; } void IniProcessing::close() { m_params.errorCode = ERR_OK; m_params.iniData.clear(); m_params.opened = false; m_params.lineWithError = -1; } IniProcessing::ErrCode IniProcessing::lastError() { return m_params.errorCode; } int IniProcessing::lineWithError() { return m_params.lineWithError; } bool IniProcessing::isOpened() { return m_params.opened; } bool IniProcessing::beginGroup(const std::string &groupName) { //Keep the group name. If not exist, will be created on value write m_params.currentGroupName = groupName; params::IniSections::iterator e = m_params.iniData.find(groupName); if(e == m_params.iniData.end()) return false; params::IniKeys &k = e->second; m_params.currentGroup = &k; return true; } bool IniProcessing::contains(const std::string &groupName) { if(!m_params.opened) return false; params::IniSections::iterator e = m_params.iniData.find(groupName); return (e != m_params.iniData.end()); } std::string IniProcessing::fileName() { return m_params.filePath; } std::string IniProcessing::group() { return m_params.currentGroupName; } std::vector IniProcessing::childGroups() { std::vector groups; groups.reserve(m_params.iniData.size()); for(params::IniSections::iterator e = m_params.iniData.begin(); e != m_params.iniData.end(); e++) { groups.push_back(e->first); } return groups; } bool IniProcessing::hasKey(const std::string &keyName) { if(!m_params.opened) return false; if(!m_params.currentGroup) return false; params::IniKeys::iterator e = m_params.currentGroup->find(keyName); return (e != m_params.currentGroup->end()); } std::vector IniProcessing::allKeys() { std::vector keys; if(!m_params.opened) return keys; if(!m_params.currentGroup) return keys; keys.reserve(m_params.currentGroup->size()); for(params::IniKeys::iterator it = m_params.currentGroup->begin(); it != m_params.currentGroup->end(); it++) { keys.push_back( it->first ); } return keys; } void IniProcessing::endGroup() { m_params.currentGroup = nullptr; m_params.currentGroupName.clear(); } void IniProcessing::read(const char *key, bool &dest, bool defVal) { bool ok = false; params::IniKeys::iterator e = readHelper(key, ok); if(!ok) { dest = defVal; return; } std::string &k = e->second; size_t i = 0; size_t ss = std::min(static_cast(4ul), k.size()); char buff[4] = {0, 0, 0, 0}; const char *pbufi = k.c_str(); char *pbuff = buff; for(; i < ss; i++) (*pbuff++) = static_cast(std::tolower(*pbufi++)); if(ss < 4) { if(ss == 0) { dest = false; return; } if(ss == 1) { dest = (buff[0] == '1'); return; } bool isNum = true; isNum &= std::isdigit(buff[i]) || (buff[i] == '-') || (buff[i] == '+'); for(size_t i = 1; i < ss; i++) isNum &= std::isdigit(buff[i]); if(isNum) { long num = std::strtol(buff, 0, 0); dest = num != 0l; return; } else { dest = (std::memcmp(buff, "yes", 3) == 0) || (std::memcmp(buff, "on", 2) == 0); return; } } if(std::memcmp(buff, "true", 4) == 0) { dest = true; return; } try { long num = std::strtol(buff, 0, 0); dest = num != 0l; return; } catch(...) { dest = false; return; } } void IniProcessing::read(const char *key, unsigned char &dest, unsigned char defVal) { bool ok = false; params::IniKeys::iterator e = readHelper(key, ok); if(!ok) { dest = defVal; return; } std::string &k = e->second; if(k.size() >= 1) dest = static_cast(k[0]); else dest = defVal; } void IniProcessing::read(const char *key, char &dest, char defVal) { bool ok = false; params::IniKeys::iterator e = readHelper(key, ok); if(!ok) { dest = defVal; return; } std::string &k = e->second; if(k.size() >= 1) dest = k[0]; else dest = defVal; } void IniProcessing::read(const char *key, unsigned short &dest, unsigned short defVal) { bool ok = false; params::IniKeys::iterator e = readHelper(key, ok); if(!ok) { dest = defVal; return; } dest = static_cast(std::strtoul(e->second.c_str(), nullptr, 0)); } void IniProcessing::read(const char *key, short &dest, short defVal) { bool ok = false; params::IniKeys::iterator e = readHelper(key, ok); if(!ok) { dest = defVal; return; } dest = static_cast(std::strtol(e->second.c_str(), nullptr, 0)); } void IniProcessing::read(const char *key, unsigned int &dest, unsigned int defVal) { bool ok = false; params::IniKeys::iterator e = readHelper(key, ok); if(!ok) { dest = defVal; return; } dest = static_cast(std::strtoul(e->second.c_str(), nullptr, 0)); } void IniProcessing::read(const char *key, int &dest, int defVal) { bool ok = false; params::IniKeys::iterator e = readHelper(key, ok); if(!ok) { dest = defVal; return; } dest = static_cast(std::strtol(e->second.c_str(), nullptr, 0)); } void IniProcessing::read(const char *key, unsigned long &dest, unsigned long defVal) { bool ok = false; params::IniKeys::iterator e = readHelper(key, ok); if(!ok) { dest = defVal; return; } dest = std::strtoul(e->second.c_str(), nullptr, 0); } void IniProcessing::read(const char *key, long &dest, long defVal) { bool ok = false; params::IniKeys::iterator e = readHelper(key, ok); if(!ok) { dest = defVal; return; } dest = std::strtol(e->second.c_str(), nullptr, 0); } void IniProcessing::read(const char *key, unsigned long long &dest, unsigned long long defVal) { bool ok = false; params::IniKeys::iterator e = readHelper(key, ok); if(!ok) { dest = defVal; return; } dest = std::strtoull(e->second.c_str(), nullptr, 0); } void IniProcessing::read(const char *key, long long &dest, long long defVal) { bool ok = false; params::IniKeys::iterator e = readHelper(key, ok); if(!ok) { dest = defVal; return; } dest = std::strtoll(e->second.c_str(), nullptr, 0); } void IniProcessing::read(const char *key, float &dest, float defVal) { bool ok = false; params::IniKeys::iterator e = readHelper(key, ok); if(!ok) { dest = defVal; return; } dest = std::strtof(e->second.c_str(), nullptr); } void IniProcessing::read(const char *key, double &dest, double defVal) { bool ok = false; params::IniKeys::iterator e = readHelper(key, ok); if(!ok) { dest = defVal; return; } dest = std::strtod(e->second.c_str(), nullptr); } void IniProcessing::read(const char *key, long double &dest, long double defVal) { bool ok = false; params::IniKeys::iterator e = readHelper(key, ok); if(!ok) { dest = defVal; return; } dest = std::strtold(e->second.c_str(), nullptr); } void IniProcessing::read(const char *key, std::string &dest, const std::string &defVal) { bool ok = false; params::IniKeys::iterator e = readHelper(key, ok); if(!ok) { dest = defVal; return; } dest = e->second; } #ifdef INI_PROCESSING_ALLOW_QT_TYPES void IniProcessing::read(const char *key, QString &dest, const QString &defVal) { bool ok = false; params::IniKeys::iterator e = readHelper(key, ok); if(!ok) { dest = defVal; return; } dest = QString::fromStdString(e->second); } #endif template inline void StrToNumVectorHelper(const std::string &source, TList &dest, const typename TList::value_type &def) { typedef typename TList::value_type T; dest.clear(); if(!source.empty()) { std::stringstream ss(source); std::string item; while(std::getline(ss, item, ',')) { std::remove(item.begin(), item.end(), ' '); try { if(std::is_same::value || std::is_same::value || std::is_same::value) dest.push_back(static_cast(std::strtol(item.c_str(), NULL, 0))); else if(std::is_same::value || std::is_same::value || std::is_same::value) dest.push_back(static_cast(std::strtoul(item.c_str(), NULL, 0))); else if(std::is_same::value) dest.push_back(std::strtof(item.c_str(), NULL)); else dest.push_back(std::strtod(item.c_str(), NULL)); } catch(...) { dest.pop_back(); } } if(dest.empty()) dest.push_back(def); } else dest.push_back(def); } template void readNumArrHelper(IniProcessing *self, const char *key, TList &dest, const TList &defVal) { bool ok = false; IniProcessing::params::IniKeys::iterator e = self->readHelper(key, ok); if(!ok) { dest = defVal; return; } StrToNumVectorHelper(e->second, dest, static_cast(0)); } void IniProcessing::read(const char *key, std::vector &dest, const std::vector &defVal) { readNumArrHelper, unsigned short>(this, key, dest, defVal); } void IniProcessing::read(const char *key, std::vector &dest, const std::vector &defVal) { readNumArrHelper, short>(this, key, dest, defVal); } void IniProcessing::read(const char *key, std::vector &dest, const std::vector &defVal) { readNumArrHelper, unsigned int>(this, key, dest, defVal); } void IniProcessing::read(const char *key, std::vector &dest, const std::vector &defVal) { readNumArrHelper, int>(this, key, dest, defVal); } void IniProcessing::read(const char *key, std::vector &dest, const std::vector &defVal) { readNumArrHelper, unsigned long>(this, key, dest, defVal); } void IniProcessing::read(const char *key, std::vector &dest, const std::vector &defVal) { readNumArrHelper, long>(this, key, dest, defVal); } void IniProcessing::read(const char *key, std::vector &dest, const std::vector &defVal) { readNumArrHelper, unsigned long long>(this, key, dest, defVal); } void IniProcessing::read(const char *key, std::vector &dest, const std::vector &defVal) { readNumArrHelper, long long>(this, key, dest, defVal); } void IniProcessing::read(const char *key, std::vector &dest, const std::vector &defVal) { readNumArrHelper, float>(this, key, dest, defVal); } void IniProcessing::read(const char *key, std::vector &dest, const std::vector &defVal) { readNumArrHelper, double>(this, key, dest, defVal); } void IniProcessing::read(const char *key, std::vector &dest, const std::vector &defVal) { readNumArrHelper, long double>(this, key, dest, defVal); } #ifdef INI_PROCESSING_ALLOW_QT_TYPES void IniProcessing::read(const char *key, QList &dest, const QList &defVal) { readNumArrHelper, short>(this, key, dest, defVal); } void IniProcessing::read(const char *key, QList &dest, const QList &defVal) { readNumArrHelper, unsigned short>(this, key, dest, defVal); } void IniProcessing::read(const char *key, QList &dest, const QList &defVal) { readNumArrHelper, int>(this, key, dest, defVal); } void IniProcessing::read(const char *key, QList &dest, const QList &defVal) { readNumArrHelper, unsigned int>(this, key, dest, defVal); } void IniProcessing::read(const char *key, QList &dest, const QList &defVal) { readNumArrHelper, long>(this, key, dest, defVal); } void IniProcessing::read(const char *key, QList &dest, const QList &defVal) { readNumArrHelper, unsigned long>(this, key, dest, defVal); } void IniProcessing::read(const char *key, QList &dest, const QList &defVal) { readNumArrHelper, long long>(this, key, dest, defVal); } void IniProcessing::read(const char *key, QList &dest, const QList &defVal) { readNumArrHelper, unsigned long long>(this, key, dest, defVal); } void IniProcessing::read(const char *key, QList &dest, const QList &defVal) { readNumArrHelper, float>(this, key, dest, defVal); } void IniProcessing::read(const char *key, QList &dest, const QList &defVal) { readNumArrHelper, double>(this, key, dest, defVal); } void IniProcessing::read(const char *key, QList &dest, const QList &defVal) { readNumArrHelper, long double>(this, key, dest, defVal); } void IniProcessing::read(const char *key, QVector &dest, const QVector &defVal) { readNumArrHelper, short>(this, key, dest, defVal); } void IniProcessing::read(const char *key, QVector &dest, const QVector &defVal) { readNumArrHelper, unsigned short>(this, key, dest, defVal); } void IniProcessing::read(const char *key, QVector &dest, const QVector &defVal) { readNumArrHelper, int>(this, key, dest, defVal); } void IniProcessing::read(const char *key, QVector &dest, const QVector &defVal) { readNumArrHelper, unsigned int>(this, key, dest, defVal); } void IniProcessing::read(const char *key, QVector &dest, const QVector &defVal) { readNumArrHelper, long>(this, key, dest, defVal); } void IniProcessing::read(const char *key, QVector &dest, const QVector &defVal) { readNumArrHelper, unsigned long>(this, key, dest, defVal); } void IniProcessing::read(const char *key, QVector &dest, const QVector &defVal) { readNumArrHelper, long long>(this, key, dest, defVal); } void IniProcessing::read(const char *key, QVector &dest, const QVector &defVal) { readNumArrHelper, unsigned long long>(this, key, dest, defVal); } void IniProcessing::read(const char *key, QVector &dest, const QVector &defVal) { readNumArrHelper, float>(this, key, dest, defVal); } void IniProcessing::read(const char *key, QVector &dest, const QVector &defVal) { readNumArrHelper, double>(this, key, dest, defVal); } void IniProcessing::read(const char *key, QVector &dest, const QVector &defVal) { readNumArrHelper, long double>(this, key, dest, defVal); } #endif IniProcessingVariant IniProcessing::value(const char *key, const IniProcessingVariant &defVal) { bool ok = false; params::IniKeys::iterator e = readHelper(key, ok); if(!ok) return defVal; std::string &k = e->second; return IniProcessingVariant(&k); } void IniProcessing::writeIniParam(const char *key, const std::string &value) { if(m_params.currentGroupName.empty()) return; bool ok = false; params::IniKeys::iterator e = readHelper(key, ok); if(ok) { e->second = value; } else { if(!m_params.currentGroup) { m_params.iniData.insert({m_params.currentGroupName, params::IniKeys()}); m_params.currentGroup = &m_params.iniData[m_params.currentGroupName]; } m_params.currentGroup->insert({std::string(key), value}); //Mark as opened m_params.opened = true; } } void IniProcessing::setValue(const char *key, unsigned short value) { writeIniParam(key, std::to_string(value)); } void IniProcessing::setValue(const char *key, short value) { writeIniParam(key, std::to_string(value)); } void IniProcessing::setValue(const char *key, unsigned int value) { writeIniParam(key, std::to_string(value)); } void IniProcessing::setValue(const char *key, int value) { writeIniParam(key, std::to_string(value)); } void IniProcessing::setValue(const char *key, unsigned long value) { writeIniParam(key, std::to_string(value)); } void IniProcessing::setValue(const char *key, long value) { writeIniParam(key, std::to_string(value)); } void IniProcessing::setValue(const char *key, unsigned long long value) { writeIniParam(key, std::to_string(value)); } void IniProcessing::setValue(const char *key, long long value) { writeIniParam(key, std::to_string(value)); } void IniProcessing::setValue(const char *key, float value) { writeIniParam(key, IniProcessing::to_string_with_precision(value)); } void IniProcessing::setValue(const char *key, double value) { writeIniParam(key, IniProcessing::to_string_with_precision(value)); } void IniProcessing::setValue(const char *key, long double value) { writeIniParam(key, IniProcessing::to_string_with_precision(value)); } void IniProcessing::setValue(const char *key, const char *value) { writeIniParam(key, value); } void IniProcessing::setValue(const char *key, const std::string &value) { writeIniParam(key, value); } #ifdef INI_PROCESSING_ALLOW_QT_TYPES void IniProcessing::setValue(const char *key, const QString &value) { writeIniParam(key, value.toStdString()); } #endif static inline bool isFloatValue(const std::string &str) { enum State { ST_SIGN = 0, ST_DOT, ST_EXPONENT, ST_EXPONENT_SIGN, ST_TAIL } st = ST_SIGN; for(const char &c : str) { if(!isdigit(c)) { switch(st) { case ST_SIGN: if(c != '-') return false; st = ST_DOT; continue; case ST_DOT: if(c != '.') return false; else if((c == 'E') || (c == 'e')) st = ST_EXPONENT_SIGN; else st = ST_EXPONENT; continue; case ST_EXPONENT: if((c != 'E') && (c != 'e')) return false; st = ST_EXPONENT_SIGN; continue; case ST_EXPONENT_SIGN: if(c != '-') return false; st = ST_TAIL; continue; case ST_TAIL: return false; } return false; } else { if(st == ST_SIGN) st = ST_DOT; } } return true; } bool IniProcessing::writeIniFile() { #ifdef _WIN32 //Convert UTF8 file path into UTF16 to support non-ASCII paths on Windows std::wstring dest; dest.resize(m_params.filePath.size()); int newSize = MultiByteToWideChar(CP_UTF8, 0, m_params.filePath.c_str(), dest.size(), (wchar_t *)dest.c_str(), dest.size()); dest.resize(newSize); FILE *cFile = _wfopen(dest.c_str(), L"wb"); #else FILE *cFile = fopen(m_params.filePath.c_str(), "wb"); #endif if(!cFile) return false; for(params::IniSections::iterator group = m_params.iniData.begin(); group != m_params.iniData.end(); group++) { fprintf(cFile, "[%s]\n", group->first.c_str()); for(params::IniKeys::iterator key = group->second.begin(); key != group->second.end(); key++) { if(isFloatValue(key->second)) { //Store as-is without quoting fprintf(cFile, "%s = %s\n", key->first.c_str(), key->second.c_str()); } else { //Set escape quotes and put the string with a quotes std::string &s = key->second; std::string escaped; escaped.reserve(s.length() * 2); for(char &c : s) { switch(c) { case '\n': escaped += "\\n"; break; case '\r': escaped += "\\r"; break; case '\t': escaped += "\\t"; break; default: if((c == '\\') || (c == '"')) escaped.push_back('\\'); escaped.push_back(c); } } fprintf(cFile, "%s = \"%s\"\n", key->first.c_str(), escaped.c_str()); } } fprintf(cFile, "\n"); fflush(cFile); } fclose(cFile); return true; }