282 lines
9.1 KiB
C++
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 §n, 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 §n, 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 §n, 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 §n, 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 §n, 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 §n, 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 §n, 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 §n, const String &item, int value) {
|
|
cfg[sectn][item].Format("%d", value);
|
|
}
|
|
|
|
void CfgWriteFloat(ConfigTree &cfg, const String §n, const String &item, float value) {
|
|
cfg[sectn][item].Format("%f", value);
|
|
}
|
|
|
|
void CfgWriteFloat(ConfigTree &cfg, const String §n, 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 §n, 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
|