Update: Translations from eints
[openttd-github.git] / src / ini_load.cpp
blobb20d3ffa13d2ff002736085102a67c49b9482e35
1 /*
2 * This file is part of OpenTTD.
3 * OpenTTD 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, version 2.
4 * OpenTTD 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.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
6 */
8 /** @file ini_load.cpp Definition of the #IniLoadFile class, related to reading and storing '*.ini' files. */
10 #include "stdafx.h"
11 #include "core/alloc_func.hpp"
12 #include "core/mem_func.hpp"
13 #include "ini_type.h"
14 #include "string_func.h"
16 #include "safeguards.h"
18 /**
19 * Construct a new in-memory item of an Ini file.
20 * @param parent the group we belong to
21 * @param name the name of the item
23 IniItem::IniItem(std::string_view name)
25 this->name = StrMakeValid(name);
28 /**
29 * Replace the current value with another value.
30 * @param value the value to replace with.
32 void IniItem::SetValue(std::string_view value)
34 this->value.emplace(value);
37 /**
38 * Construct a new in-memory group of an Ini file.
39 * @param parent the file we belong to
40 * @param name the name of the group
42 IniGroup::IniGroup(std::string_view name, IniGroupType type) : type(type), comment("\n")
44 this->name = StrMakeValid(name);
47 /**
48 * Get the item with the given name.
49 * @param name name of the item to find.
50 * @return the requested item or nullptr if not found.
52 const IniItem *IniGroup::GetItem(std::string_view name) const
54 for (const IniItem &item : this->items) {
55 if (item.name == name) return &item;
58 return nullptr;
61 /**
62 * Get the item with the given name, and if it doesn't exist create a new item.
63 * @param name name of the item to find.
64 * @return the requested item.
66 IniItem &IniGroup::GetOrCreateItem(std::string_view name)
68 for (IniItem &item : this->items) {
69 if (item.name == name) return item;
72 /* Item doesn't exist, make a new one. */
73 return this->CreateItem(name);
76 /**
77 * Create an item with the given name. This does not reuse an existing item of the same name.
78 * @param name name of the item to create.
79 * @return the created item.
81 IniItem &IniGroup::CreateItem(std::string_view name)
83 return this->items.emplace_back(name);
86 /**
87 * Remove the item with the given name.
88 * @param name Name of the item to remove.
90 void IniGroup::RemoveItem(std::string_view name)
92 this->items.remove_if([&name](const IniItem &item) { return item.name == name; });
95 /**
96 * Clear all items in the group
98 void IniGroup::Clear()
100 this->items.clear();
104 * Construct a new in-memory Ini file representation.
105 * @param list_group_names A list with group names that should be loaded as lists instead of variables. @see IGT_LIST
106 * @param seq_group_names A list with group names that should be loaded as lists of names. @see IGT_SEQUENCE
108 IniLoadFile::IniLoadFile(const IniGroupNameList &list_group_names, const IniGroupNameList &seq_group_names) :
109 list_group_names(list_group_names),
110 seq_group_names(seq_group_names)
115 * Get the group with the given name.
116 * @param name name of the group to find.
117 * @return The requested group or \c nullptr if not found.
119 const IniGroup *IniLoadFile::GetGroup(std::string_view name) const
121 for (const IniGroup &group : this->groups) {
122 if (group.name == name) return &group;
125 return nullptr;
129 * Get the group with the given name.
130 * @param name name of the group to find.
131 * @return The requested group or \c nullptr if not found.
133 IniGroup *IniLoadFile::GetGroup(std::string_view name)
135 for (IniGroup &group : this->groups) {
136 if (group.name == name) return &group;
139 return nullptr;
143 * Get the group with the given name, and if it doesn't exist create a new group.
144 * @param name name of the group to find.
145 * @return the requested group.
147 IniGroup &IniLoadFile::GetOrCreateGroup(std::string_view name)
149 for (IniGroup &group : this->groups) {
150 if (group.name == name) return group;
153 /* Group doesn't exist, make a new one. */
154 return this->CreateGroup(name);
158 * Create an group with the given name. This does not reuse an existing group of the same name.
159 * @param name name of the group to create.
160 * @return the created group.
162 IniGroup &IniLoadFile::CreateGroup(std::string_view name)
164 IniGroupType type = IGT_VARIABLES;
165 if (std::find(this->list_group_names.begin(), this->list_group_names.end(), name) != this->list_group_names.end()) type = IGT_LIST;
166 if (std::find(this->seq_group_names.begin(), this->seq_group_names.end(), name) != this->seq_group_names.end()) type = IGT_SEQUENCE;
168 return this->groups.emplace_back(name, type);
172 * Remove the group with the given name.
173 * @param name name of the group to remove.
175 void IniLoadFile::RemoveGroup(std::string_view name)
177 size_t len = name.length();
178 this->groups.remove_if([&name, &len](const IniGroup &group) { return group.name.compare(0, len, name) == 0; });
182 * Load the Ini file's data from the disk.
183 * @param filename the file to load.
184 * @param subdir the sub directory to load the file from.
185 * @pre nothing has been loaded yet.
187 void IniLoadFile::LoadFromDisk(const std::string &filename, Subdirectory subdir)
189 assert(this->groups.empty());
191 char buffer[1024];
192 IniGroup *group = nullptr;
194 std::string comment;
196 size_t end;
197 auto in = this->OpenFile(filename, subdir, &end);
198 if (!in.has_value()) return;
200 end += ftell(*in);
202 /* for each line in the file */
203 while (static_cast<size_t>(ftell(*in)) < end && fgets(buffer, sizeof(buffer), *in)) {
204 char c, *s;
205 /* trim whitespace from the left side */
206 for (s = buffer; *s == ' ' || *s == '\t'; s++) {}
208 /* trim whitespace from right side. */
209 char *e = s + strlen(s);
210 while (e > s && ((c = e[-1]) == '\n' || c == '\r' || c == ' ' || c == '\t')) e--;
211 *e = '\0';
213 /* Skip comments and empty lines outside IGT_SEQUENCE groups. */
214 if ((group == nullptr || group->type != IGT_SEQUENCE) && (*s == '#' || *s == ';' || *s == '\0')) {
215 comment += std::string_view(s, e - s);
216 comment += '\n'; // comment newline
217 continue;
220 /* it's a group? */
221 if (s[0] == '[') {
222 if (e[-1] != ']') {
223 this->ReportFileError("ini: invalid group name '", buffer, "'");
224 } else {
225 e--;
227 s++; // skip [
228 group = &this->CreateGroup(std::string_view(s, e - s));
229 group->comment = std::move(comment);
230 } else if (group != nullptr) {
231 if (group->type == IGT_SEQUENCE) {
232 /* A sequence group, use the line as item name without further interpretation. */
233 IniItem &item = group->CreateItem(std::string_view(buffer, e - buffer));
234 item.comment = std::move(comment);
235 continue;
237 char *t;
238 /* find end of keyname */
239 if (*s == '\"') {
240 s++;
241 for (t = s; *t != '\0' && *t != '\"'; t++) {}
242 if (*t == '\"') *t = ' ';
243 } else {
244 for (t = s; *t != '\0' && *t != '=' && *t != '\t' && *t != ' '; t++) {}
247 /* it's an item in an existing group */
248 IniItem &item = group->CreateItem(std::string_view(s, t - s));
249 item.comment = std::move(comment);
251 /* find start of parameter */
252 while (*t == '=' || *t == ' ' || *t == '\t') t++;
254 bool quoted = (*t == '\"');
255 /* remove starting quotation marks */
256 if (*t == '\"') t++;
257 /* remove ending quotation marks */
258 e = t + strlen(t);
259 if (e > t && e[-1] == '\"') e--;
260 *e = '\0';
262 /* If the value was not quoted and empty, it must be nullptr */
263 if (!quoted && e == t) {
264 item.value.reset();
265 } else {
266 item.value = StrMakeValid(std::string_view(t));
268 } else {
269 /* it's an orphan item */
270 this->ReportFileError("ini: '", buffer, "' outside of group");
274 this->comment = std::move(comment);