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/>.
8 /** @file ini_load.cpp Definition of the #IniLoadFile class, related to reading and storing '*.ini' files. */
11 #include "core/alloc_func.hpp"
12 #include "core/mem_func.hpp"
14 #include "string_func.h"
16 #include "safeguards.h"
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
);
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
);
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
);
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
;
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
);
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
);
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
; });
96 * Clear all items in the group
98 void IniGroup::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
;
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
;
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());
192 IniGroup
*group
= nullptr;
197 auto in
= this->OpenFile(filename
, subdir
, &end
);
198 if (!in
.has_value()) return;
202 /* for each line in the file */
203 while (static_cast<size_t>(ftell(*in
)) < end
&& fgets(buffer
, sizeof(buffer
), *in
)) {
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
--;
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
223 this->ReportFileError("ini: invalid group name '", buffer
, "'");
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
);
238 /* find end of keyname */
241 for (t
= s
; *t
!= '\0' && *t
!= '\"'; t
++) {}
242 if (*t
== '\"') *t
= ' ';
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 */
257 /* remove ending quotation marks */
259 if (e
> t
&& e
[-1] == '\"') e
--;
262 /* If the value was not quoted and empty, it must be nullptr */
263 if (!quoted
&& e
== t
) {
266 item
.value
= StrMakeValid(std::string_view(t
));
269 /* it's an orphan item */
270 this->ReportFileError("ini: '", buffer
, "' outside of group");
274 this->comment
= std::move(comment
);