Files
scummvm-cursorfix/engines/ags/shared/util/ini_util.cpp
2026-02-02 04:50:13 +01:00

282 lines
9.1 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/std/memory.h"
#include "ags/shared/util/file.h"
#include "ags/shared/util/ini_util.h"
#include "ags/shared/util/ini_file.h"
#include "ags/shared/util/stream.h"
#include "ags/shared/util/string_utils.h"
#include "ags/shared/util/text_stream_writer.h"
namespace AGS3 {
namespace AGS {
namespace Shared {
//-----------------------------------------------------------------------------
// ConfigReader
//-----------------------------------------------------------------------------
bool CfgReadItem(const ConfigTree &cfg, const String &sectn, const String &item, String &value) {
const auto sec_it = cfg.find(sectn);
if (sec_it != cfg.end()) {
const auto item_it = sec_it->_value.find(item);
if (item_it != sec_it->_value.end()) {
value = item_it->_value;
return true;
}
}
return false;
}
int CfgReadInt(const ConfigTree &cfg, const String &sectn, const String &item, int def) {
String str;
if (!CfgReadItem(cfg, sectn, item, str))
return def;
return StrUtil::StringToInt(str, def);
}
int CfgReadInt(const ConfigTree &cfg, const String &sectn, const String &item, int min, int max, int def) {
int val = CfgReadInt(cfg, sectn, item, def);
if ((val < min) || (val > max))
return def;
return val;
}
float CfgReadFloat(const ConfigTree &cfg, const String &sectn, const String &item, float def) {
String str;
if (!CfgReadItem(cfg, sectn, item, str))
return def;
return StrUtil::StringToFloat(str, def);
}
float CfgReadFloat(const ConfigTree &cfg, const String &sectn, const String &item, float min, float max, float def) {
float val = CfgReadFloat(cfg, sectn, item, def);
if ((val < min) || (val > max))
return def;
return val;
}
String CfgReadString(const ConfigTree &cfg, const String &sectn, const String &item, const String &def) {
String str;
if (!CfgReadItem(cfg, sectn, item, str))
return def;
return str;
}
String CfgFindKey(const ConfigTree &cfg, const String &sectn, const String &item, bool nocase) {
const auto sec_it = cfg.find(sectn);
if (sec_it == cfg.end())
return "";
if (nocase) {
for (auto item_it : sec_it->_value) {
if (item_it._key.CompareNoCase(item) == 0)
return item_it._key;
}
} else {
const auto item_it = sec_it->_value.find(item);
if (item_it != sec_it->_value.end())
return item_it->_key;
}
return "";
}
//-----------------------------------------------------------------------------
// ConfigWriter
//-----------------------------------------------------------------------------
void CfgWriteInt(ConfigTree &cfg, const String &sectn, const String &item, int value) {
cfg[sectn][item].Format("%d", value);
}
void CfgWriteFloat(ConfigTree &cfg, const String &sectn, const String &item, float value) {
cfg[sectn][item].Format("%f", value);
}
void CfgWriteFloat(ConfigTree &cfg, const String &sectn, const String &item, float value, unsigned precision) {
char fmt[10];
snprintf(fmt, sizeof(fmt), "%%0.%df", precision);
cfg[sectn][item].Format(fmt, value);
}
void CfgWriteString(ConfigTree &cfg, const String &sectn, const String &item, const String &value) {
cfg[sectn][item] = value;
}
//-----------------------------------------------------------------------------
// IniUtil
//-----------------------------------------------------------------------------
typedef std::unique_ptr<Stream> UStream;
typedef StringOrderMap::const_iterator StrStrOIter;
typedef ConfigTree::const_iterator ConfigNode;
typedef IniFile::SectionIterator SectionIterator;
typedef IniFile::ConstSectionIterator CSectionIterator;
typedef IniFile::ItemIterator ItemIterator;
typedef IniFile::ConstItemIterator CItemIterator;
static bool ReadIni(const String &file, IniFile &ini) {
UStream fs(File::OpenFileRead(file));
if (fs.get()) {
ini.Read(fs.get());
return true;
}
return false;
}
bool IniUtil::Read(const String &file, ConfigTree &tree) {
// Read ini content
IniFile ini;
if (!ReadIni(file, ini))
return false;
// Copy items into key-value tree
for (CSectionIterator sec = ini.CBegin(); sec != ini.CEnd(); ++sec) {
if (!sec->GetItemCount())
continue; // skip empty sections
StringOrderMap &subtree = tree[sec->GetName()];
for (CItemIterator item = sec->CBegin(); item != sec->CEnd(); ++item) {
if (!item->IsKeyValue())
continue; // skip non key-value items
subtree[item->GetKey()] = item->GetValue();
}
}
return true;
}
void IniUtil::Write(const String &file, const ConfigTree &tree) {
UStream fs(File::CreateFile(file));
TextStreamWriter writer(fs.get());
for (ConfigNode it_sec = tree.begin(); it_sec != tree.end(); ++it_sec) {
const String &sec_key = it_sec->_key;
const StringOrderMap &sec_tree = it_sec->_value;
if (!sec_tree.size())
continue; // skip empty sections
// write section name
if (!sec_key.IsEmpty()) {
writer.WriteFormat("[%s]", sec_key.GetCStr());
writer.WriteLineBreak();
}
// write all items
for (StrStrOIter keyval = sec_tree.begin(); keyval != sec_tree.end(); ++keyval) {
const String &item_key = keyval->_key;
const String &item_value = keyval->_value;
writer.WriteFormat("%s = %s", item_key.GetCStr(), item_value.GetCStr());
writer.WriteLineBreak();
}
}
writer.ReleaseStream();
}
void IniUtil::WriteToString(String &s, const ConfigTree &tree) {
for (ConfigNode it_sec = tree.begin(); it_sec != tree.end(); ++it_sec) {
const String &sec_key = it_sec->_key;
const StringOrderMap &sec_tree = it_sec->_value;
if (!sec_tree.size())
continue; // skip empty sections
// write section name
if (!sec_key.IsEmpty())
s.Append(String::FromFormat("[%s]\n", sec_key.GetCStr()));
// write all items
for (StrStrOIter keyval = sec_tree.begin(); keyval != sec_tree.end(); ++keyval)
s.Append(String::FromFormat("%s = %s\n", keyval->_key.GetCStr(), keyval->_value.GetCStr()));
}
}
bool IniUtil::Merge(const String &file, const ConfigTree &tree) {
// Read ini content
IniFile ini;
ReadIni(file, ini); // NOTE: missing file is a valid case
// Remember the sections we find in file, if some sections are not found,
// they will be appended to the end of file.
std::map<String, bool> sections_found;
for (ConfigNode it = tree.begin(); it != tree.end(); ++it)
sections_found[it->_key] = false;
// Merge existing sections
for (SectionIterator sec = ini.Begin(); sec != ini.End(); ++sec) {
if (!sec->GetItemCount())
continue; // skip empty sections
String secname = sec->GetName();
ConfigNode tree_node = tree.find(secname);
if (tree_node == tree.end())
continue; // this section is not interesting for us
// Remember the items we find in this section, if some items are not found,
// they will be appended to the end of section.
const StringOrderMap &subtree = tree_node->_value;
std::map<String, bool> items_found;
for (StrStrOIter keyval = subtree.begin(); keyval != subtree.end(); ++keyval)
items_found[keyval->_key] = false;
// Replace matching items
for (ItemIterator item = sec->Begin(); item != sec->End(); ++item) {
String key = item->GetKey();
StrStrOIter keyval = subtree.find(key);
if (keyval == subtree.end())
continue; // this item is not interesting for us
String old_value = item->GetValue();
String new_value = keyval->_value;
if (old_value != new_value)
item->SetValue(new_value);
items_found[key] = true;
}
// Append new items
if (!sections_found[secname]) {
for (std::map<String, bool>::const_iterator item_f = items_found.begin(); item_f != items_found.end(); ++item_f) {
if (item_f->_value)
continue; // item was already found
StrStrOIter keyval = subtree.find(item_f->_key);
ini.InsertItem(sec, sec->End(), keyval->_key, keyval->_value);
}
sections_found[secname] = true; // mark section as known
}
}
// Add new sections
for (std::map<String, bool>::const_iterator sec_f = sections_found.begin(); sec_f != sections_found.end(); ++sec_f) {
if (sec_f->_value)
continue;
SectionIterator sec = ini.InsertSection(ini.End(), sec_f->_key);
const StringOrderMap &subtree = tree.find(sec_f->_key)->_value;
for (StrStrOIter keyval = subtree.begin(); keyval != subtree.end(); ++keyval)
ini.InsertItem(sec, sec->End(), keyval->_key, keyval->_value);
}
// Write the resulting set of lines
UStream fs(File::CreateFile(file));
if (!fs.get())
return false;
ini.Write(fs.get());
return true;
}
} // namespace Shared
} // namespace AGS
} // namespace AGS3