OpenTTD Source: src/ini_load.cpp Source File

OpenTTD Source 15.0-RC2

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