/* 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 . * */ #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 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 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 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::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::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