OpenTTD Source 20260206-master-g4d4e37dbf1
ini_load.cpp
Go to the documentation of this file.
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 <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
10#include "stdafx.h"
12#include "ini_type.h"
13#include "string_func.h"
14
15#include "safeguards.h"
16
21IniItem::IniItem(std::string_view name)
22{
23 this->name = StrMakeValid(name);
24}
25
30void IniItem::SetValue(std::string_view value)
31{
32 this->value.emplace(value);
33}
34
41{
42 this->name = StrMakeValid(name);
43}
44
50const IniItem *IniGroup::GetItem(std::string_view name) const
51{
52 for (const IniItem &item : this->items) {
53 if (item.name == name) return &item;
54 }
55
56 return nullptr;
57}
58
65{
66 for (IniItem &item : this->items) {
67 if (item.name == name) return item;
68 }
69
70 /* Item doesn't exist, make a new one. */
71 return this->CreateItem(name);
72}
73
80{
81 return this->items.emplace_back(name);
82}
83
88void IniGroup::RemoveItem(std::string_view name)
89{
90 this->items.remove_if([&name](const IniItem &item) { return item.name == name; });
91}
92
97{
98 this->items.clear();
99}
100
111
117const IniGroup *IniLoadFile::GetGroup(std::string_view name) const
118{
119 for (const IniGroup &group : this->groups) {
120 if (group.name == name) return &group;
121 }
122
123 return nullptr;
124}
125
131IniGroup *IniLoadFile::GetGroup(std::string_view name)
132{
133 for (IniGroup &group : this->groups) {
134 if (group.name == name) return &group;
135 }
136
137 return nullptr;
138}
139
146{
147 for (IniGroup &group : this->groups) {
148 if (group.name == name) return group;
149 }
150
151 /* Group doesn't exist, make a new one. */
152 return this->CreateGroup(name);
153}
154
160IniGroup &IniLoadFile::CreateGroup(std::string_view name)
161{
163 if (std::ranges::find(this->list_group_names, name) != this->list_group_names.end()) type = IGT_LIST;
164 if (std::ranges::find(this->seq_group_names, name) != this->seq_group_names.end()) type = IGT_SEQUENCE;
165
166 return this->groups.emplace_back(name, type);
167}
168
173void IniLoadFile::RemoveGroup(std::string_view name)
174{
175 this->groups.remove_if([&name](const IniGroup &group) { return group.name.starts_with(name); });
176}
177
184void IniLoadFile::LoadFromDisk(std::string_view filename, Subdirectory subdir)
185{
186 assert(this->groups.empty());
187
188 char buffer[1024];
189 IniGroup *group = nullptr;
190
191 std::string comment;
192
193 size_t end;
194 auto in = this->OpenFile(filename, subdir, &end);
195 if (!in.has_value()) return;
196
197 end += ftell(*in);
198
199 size_t line = 0;
200 /* for each line in the file */
201 while (static_cast<size_t>(ftell(*in)) < end && fgets(buffer, sizeof(buffer), *in)) {
202 ++line;
203 StringConsumer consumer{StrTrimView(buffer, StringConsumer::WHITESPACE_OR_NEWLINE)};
204
205 /* Skip comments and empty lines outside IGT_SEQUENCE groups. */
206 if ((group == nullptr || group->type != IGT_SEQUENCE) && (!consumer.AnyBytesLeft() || consumer.PeekCharIfIn("#;"))) {
207 comment += consumer.GetOrigData();
208 comment += "\n";
209 continue;
210 }
211
212 /* it's a group? */
213 if (consumer.ReadCharIf('[')) {
214 std::string_view group_name = consumer.ReadUntilChar(']', StringConsumer::KEEP_SEPARATOR);
215 if (!consumer.ReadCharIf(']') || consumer.AnyBytesLeft()) {
216 this->ReportFileError(fmt::format("ini [{}]: invalid group name '{}'", line, consumer.GetOrigData()));
217 }
218 group = &this->CreateGroup(group_name);
219 group->comment = std::move(comment);
220 comment.clear(); // std::move leaves comment in a "valid but unspecified state" according to the specification.
221 } else if (group != nullptr) {
222 if (group->type == IGT_SEQUENCE) {
223 /* A sequence group, use the line as item name without further interpretation. */
224 IniItem &item = group->CreateItem(consumer.GetOrigData());
225 item.comment = std::move(comment);
226 comment.clear(); // std::move leaves comment in a "valid but unspecified state" according to the specification.
227 continue;
228 }
229
230 static const std::string_view key_parameter_separators = "=\t ";
231 std::string_view key;
232 /* find end of keyname */
233 if (consumer.ReadCharIf('\"')) {
235 } else {
236 key = consumer.ReadUntilCharIn(key_parameter_separators);
237 }
238
239 /* it's an item in an existing group */
240 IniItem &item = group->CreateItem(key);
241 item.comment = std::move(comment);
242 comment.clear(); // std::move leaves comment in a "valid but unspecified state" according to the specification.
243
244 /* find start of parameter */
245 consumer.SkipUntilCharNotIn(key_parameter_separators);
246
247 if (consumer.ReadCharIf('\"')) {
248 /* There is no escaping in our loader, so we just remove the first and last quote. */
249 std::string_view value = consumer.GetLeftData();
250 if (value.ends_with("\"")) value.remove_suffix(1);
251 item.value = StrMakeValid(value);
252 } else if (!consumer.AnyBytesLeft()) {
253 /* If the value was not quoted and empty, it must be nullptr */
254 item.value.reset();
255 } else {
256 item.value = StrMakeValid(consumer.GetLeftData());
257 }
258 } else {
259 /* it's an orphan item */
260 this->ReportFileError(fmt::format("ini [{}]: '{}' is outside of group", line, consumer.GetOrigData()));
261 }
262 }
263
264 this->comment = std::move(comment);
265}
266
Parse data from a string / buffer.
bool ReadCharIf(char c)
Check whether the next 8-bit char matches 'c', and skip it.
std::string_view GetOrigData() const noexcept
Get the original data, as passed to the constructor.
std::string_view ReadUntilChar(char c, SeparatorUsage sep)
Read data until the first occurrence of 8-bit char 'c', and advance reader.
@ SKIP_ONE_SEPARATOR
Read and discard one separator, do not include it in the result.
@ KEEP_SEPARATOR
Keep the separator in the data as next value to be read.
std::optional< char > PeekCharIfIn(std::string_view chars) const
Check whether the next 8-bit char is in 'chars'.
bool AnyBytesLeft() const noexcept
Check whether any bytes left to read.
static const std::string_view WHITESPACE_OR_NEWLINE
ASCII whitespace characters, including new-line.
void SkipUntilCharNotIn(std::string_view chars)
Skip 8-bit chars, while they are in 'chars', until they are not.
std::string_view ReadUntilCharIn(std::string_view chars)
Read 8-bit chars, while they are not in 'chars', until they are; and advance reader.
std::string_view GetLeftData() const noexcept
Get data left to read.
Subdirectory
The different kinds of subdirectories OpenTTD uses.
Definition fileio_type.h:88
Types related to reading/writing '*.ini' files.
IniGroupType
Types of groups.
Definition ini_type.h:16
@ IGT_SEQUENCE
A list of uninterpreted lines, terminated by the next group block.
Definition ini_type.h:19
@ IGT_VARIABLES
Values of the form "landscape = hilly".
Definition ini_type.h:17
@ IGT_LIST
A list of values, separated by and terminated by the next group block.
Definition ini_type.h:18
A number of safeguards to prevent using unsafe methods.
Definition of base types and functions in a cross-platform compatible way.
static void StrMakeValid(Builder &builder, StringConsumer &consumer, StringValidationSettings settings)
Copies the valid (UTF-8) characters from consumer to the builder.
Definition string.cpp:119
Parse strings.
Functions related to low-level strings.
A group within an ini file.
Definition ini_type.h:34
const IniItem * GetItem(std::string_view name) const
Get the item with the given name.
Definition ini_load.cpp:50
std::string comment
comment for group
Definition ini_type.h:38
IniGroup(std::string_view name, IniGroupType type)
Construct a new in-memory group of an Ini file.
Definition ini_load.cpp:40
IniGroupType type
type of group
Definition ini_type.h:36
void Clear()
Clear all items in the group.
Definition ini_load.cpp:96
void RemoveItem(std::string_view name)
Remove the item with the given name.
Definition ini_load.cpp:88
std::string name
name of group
Definition ini_type.h:37
IniItem & CreateItem(std::string_view name)
Create an item with the given name.
Definition ini_load.cpp:79
IniItem & GetOrCreateItem(std::string_view name)
Get the item with the given name, and if it doesn't exist create a new item.
Definition ini_load.cpp:64
std::list< IniItem > items
all items in the group
Definition ini_type.h:35
A single "line" in an ini file.
Definition ini_type.h:23
std::optional< std::string > value
The value of this item.
Definition ini_type.h:25
std::string name
The name of this item.
Definition ini_type.h:24
std::string comment
The comment associated with this item.
Definition ini_type.h:26
IniItem(std::string_view name)
Construct a new in-memory item of an Ini file.
Definition ini_load.cpp:21
void SetValue(std::string_view value)
Replace the current value with another value.
Definition ini_load.cpp:30
std::list< IniGroup > groups
all groups in the ini
Definition ini_type.h:53
void RemoveGroup(std::string_view name)
Remove the group with the given name.
Definition ini_load.cpp:173
const IniGroupNameList seq_group_names
list of group names that are sequences.
Definition ini_type.h:56
virtual std::optional< FileHandle > OpenFile(std::string_view filename, Subdirectory subdir, size_t *size)=0
Open the INI file.
IniLoadFile(const IniGroupNameList &list_group_names={}, const IniGroupNameList &seq_group_names={})
Construct a new in-memory Ini file representation.
Definition ini_load.cpp:106
const IniGroup * GetGroup(std::string_view name) const
Get the group with the given name.
Definition ini_load.cpp:117
virtual void ReportFileError(std::string_view message)=0
Report an error about the file contents.
std::string comment
last comment in file
Definition ini_type.h:54
void LoadFromDisk(std::string_view filename, Subdirectory subdir)
Load the Ini file's data from the disk.
Definition ini_load.cpp:184
IniGroup & CreateGroup(std::string_view name)
Create an group with the given name.
Definition ini_load.cpp:160
const IniGroupNameList list_group_names
list of group names that are lists
Definition ini_type.h:55
IniGroup & GetOrCreateGroup(std::string_view name)
Get the group with the given name, and if it doesn't exist create a new group.
Definition ini_load.cpp:145