311 lines
7.6 KiB
C++
311 lines
7.6 KiB
C++
/*
|
|
SWR INI Tool - Switchroot INI Configuration Editor
|
|
Copyright (C) 2026 Switchroot
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
*/
|
|
|
|
#include "ini_handler.h"
|
|
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
|
|
static std::string trim(const std::string& str)
|
|
{
|
|
size_t first = str.find_first_not_of(" \t\r\n");
|
|
if (first == std::string::npos)
|
|
return "";
|
|
size_t last = str.find_last_not_of(" \t\r\n");
|
|
return str.substr(first, last - first + 1);
|
|
}
|
|
|
|
static std::string toLower(const std::string& str)
|
|
{
|
|
std::string result = str;
|
|
std::transform(result.begin(), result.end(), result.begin(), ::tolower);
|
|
return result;
|
|
}
|
|
|
|
IniHandler::IniHandler() : loaded(false)
|
|
{
|
|
}
|
|
|
|
bool IniHandler::load(const std::string& path)
|
|
{
|
|
this->filePath = path;
|
|
this->lines.clear();
|
|
this->loaded = false;
|
|
|
|
std::ifstream file(path);
|
|
if (!file.is_open())
|
|
return false;
|
|
|
|
std::string currentSection;
|
|
std::string rawLine;
|
|
|
|
while (std::getline(file, rawLine))
|
|
{
|
|
Line line;
|
|
line.raw = rawLine;
|
|
|
|
std::string trimmed = trim(rawLine);
|
|
|
|
if (trimmed.empty())
|
|
{
|
|
line.type = Line::BLANK;
|
|
}
|
|
else if (trimmed[0] == '#' || trimmed[0] == ';')
|
|
{
|
|
line.type = Line::COMMENT;
|
|
}
|
|
else if (trimmed[0] == '[' && trimmed.back() == ']')
|
|
{
|
|
line.type = Line::SECTION;
|
|
line.section = trim(trimmed.substr(1, trimmed.size() - 2));
|
|
currentSection = line.section;
|
|
}
|
|
else
|
|
{
|
|
size_t eqPos = trimmed.find('=');
|
|
if (eqPos != std::string::npos)
|
|
{
|
|
line.type = Line::KEY_VALUE;
|
|
line.section = currentSection;
|
|
line.key = trim(trimmed.substr(0, eqPos));
|
|
line.value = trim(trimmed.substr(eqPos + 1));
|
|
}
|
|
else
|
|
{
|
|
line.type = Line::COMMENT; // treat unparseable lines as comments
|
|
}
|
|
}
|
|
|
|
this->lines.push_back(line);
|
|
}
|
|
|
|
file.close();
|
|
this->loaded = true;
|
|
return true;
|
|
}
|
|
|
|
bool IniHandler::save()
|
|
{
|
|
return save(this->filePath);
|
|
}
|
|
|
|
bool IniHandler::save(const std::string& path)
|
|
{
|
|
std::ofstream file(path);
|
|
if (!file.is_open())
|
|
return false;
|
|
|
|
for (size_t i = 0; i < this->lines.size(); i++)
|
|
{
|
|
const Line& line = this->lines[i];
|
|
|
|
if (line.type == Line::KEY_VALUE)
|
|
{
|
|
file << line.key << "=" << line.value;
|
|
}
|
|
else
|
|
{
|
|
file << line.raw;
|
|
}
|
|
|
|
if (i < this->lines.size() - 1)
|
|
file << "\n";
|
|
}
|
|
|
|
file.close();
|
|
return true;
|
|
}
|
|
|
|
std::string IniHandler::get(const std::string& section, const std::string& key, const std::string& defaultValue) const
|
|
{
|
|
int idx = findLine(section, key);
|
|
if (idx >= 0)
|
|
return this->lines[idx].value;
|
|
return defaultValue;
|
|
}
|
|
|
|
int IniHandler::getInt(const std::string& section, const std::string& key, int defaultValue) const
|
|
{
|
|
std::string val = get(section, key, "");
|
|
if (val.empty())
|
|
return defaultValue;
|
|
|
|
try
|
|
{
|
|
return std::stoi(val);
|
|
}
|
|
catch (...)
|
|
{
|
|
return defaultValue;
|
|
}
|
|
}
|
|
|
|
bool IniHandler::getBool(const std::string& section, const std::string& key, bool defaultValue) const
|
|
{
|
|
return getInt(section, key, defaultValue ? 1 : 0) != 0;
|
|
}
|
|
|
|
void IniHandler::set(const std::string& section, const std::string& key, const std::string& value)
|
|
{
|
|
int idx = findLine(section, key);
|
|
if (idx >= 0)
|
|
{
|
|
this->lines[idx].value = value;
|
|
}
|
|
else
|
|
{
|
|
// Key doesn't exist - add it at the end of the section
|
|
int sectionEnd = findSectionEnd(section);
|
|
|
|
if (sectionEnd < 0)
|
|
{
|
|
// Section doesn't exist - create it
|
|
Line sectionLine;
|
|
sectionLine.type = Line::SECTION;
|
|
sectionLine.section = section;
|
|
sectionLine.raw = "[" + section + "]";
|
|
|
|
Line blankLine;
|
|
blankLine.type = Line::BLANK;
|
|
blankLine.raw = "";
|
|
|
|
Line kvLine;
|
|
kvLine.type = Line::KEY_VALUE;
|
|
kvLine.section = section;
|
|
kvLine.key = key;
|
|
kvLine.value = value;
|
|
|
|
this->lines.push_back(blankLine);
|
|
this->lines.push_back(sectionLine);
|
|
this->lines.push_back(kvLine);
|
|
}
|
|
else
|
|
{
|
|
Line kvLine;
|
|
kvLine.type = Line::KEY_VALUE;
|
|
kvLine.section = section;
|
|
kvLine.key = key;
|
|
kvLine.value = value;
|
|
|
|
this->lines.insert(this->lines.begin() + sectionEnd, kvLine);
|
|
}
|
|
}
|
|
}
|
|
|
|
void IniHandler::setInt(const std::string& section, const std::string& key, int value)
|
|
{
|
|
set(section, key, std::to_string(value));
|
|
}
|
|
|
|
void IniHandler::setBool(const std::string& section, const std::string& key, bool value)
|
|
{
|
|
setInt(section, key, value ? 1 : 0);
|
|
}
|
|
|
|
bool IniHandler::hasSection(const std::string& section) const
|
|
{
|
|
for (const Line& line : this->lines)
|
|
{
|
|
if (line.type == Line::SECTION && toLower(line.section) == toLower(section))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IniHandler::hasKey(const std::string& section, const std::string& key) const
|
|
{
|
|
return findLine(section, key) >= 0;
|
|
}
|
|
|
|
std::vector<std::string> IniHandler::getSections() const
|
|
{
|
|
std::vector<std::string> sections;
|
|
for (const Line& line : this->lines)
|
|
{
|
|
if (line.type == Line::SECTION)
|
|
sections.push_back(line.section);
|
|
}
|
|
return sections;
|
|
}
|
|
|
|
std::vector<std::pair<std::string, std::string>> IniHandler::getKeys(const std::string& section) const
|
|
{
|
|
std::vector<std::pair<std::string, std::string>> keys;
|
|
for (const Line& line : this->lines)
|
|
{
|
|
if (line.type == Line::KEY_VALUE && toLower(line.section) == toLower(section))
|
|
keys.push_back({line.key, line.value});
|
|
}
|
|
return keys;
|
|
}
|
|
|
|
std::string IniHandler::findOsSection() const
|
|
{
|
|
for (const Line& line : this->lines)
|
|
{
|
|
if (line.type == Line::SECTION && toLower(line.section) != "config")
|
|
return line.section;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
int IniHandler::findLine(const std::string& section, const std::string& key) const
|
|
{
|
|
std::string sectionLower = toLower(section);
|
|
std::string keyLower = toLower(key);
|
|
|
|
for (size_t i = 0; i < this->lines.size(); i++)
|
|
{
|
|
const Line& line = this->lines[i];
|
|
if (line.type == Line::KEY_VALUE &&
|
|
toLower(line.section) == sectionLower &&
|
|
toLower(line.key) == keyLower)
|
|
{
|
|
return (int)i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int IniHandler::findSectionEnd(const std::string& section) const
|
|
{
|
|
std::string sectionLower = toLower(section);
|
|
bool inSection = false;
|
|
int lastLine = -1;
|
|
|
|
for (size_t i = 0; i < this->lines.size(); i++)
|
|
{
|
|
const Line& line = this->lines[i];
|
|
if (line.type == Line::SECTION)
|
|
{
|
|
if (toLower(line.section) == sectionLower)
|
|
{
|
|
inSection = true;
|
|
lastLine = (int)i + 1;
|
|
}
|
|
else if (inSection)
|
|
{
|
|
return lastLine;
|
|
}
|
|
}
|
|
else if (inSection && line.type != Line::BLANK)
|
|
{
|
|
lastLine = (int)i + 1;
|
|
}
|
|
}
|
|
|
|
if (inSection)
|
|
return lastLine;
|
|
|
|
return -1;
|
|
}
|