mirror of https://github.com/OpenTTD/OpenTTD
Codechange: rewrite ini file parsing with the StringConsumer
parent
6a9f694158
commit
85f1110569
|
@ -108,7 +108,7 @@ bool IniFile::SaveToDisk(const std::string &filename)
|
|||
return FioFOpenFile(filename, "rb", subdir, size);
|
||||
}
|
||||
|
||||
/* virtual */ void IniFile::ReportFileError(std::string_view pre, std::string_view buffer, std::string_view post)
|
||||
/* virtual */ void IniFile::ReportFileError(std::string_view message)
|
||||
{
|
||||
ShowInfo("{}{}{}", pre, buffer, post);
|
||||
ShowInfo("{}", message);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "stdafx.h"
|
||||
#include "core/mem_func.hpp"
|
||||
#include "core/string_consumer.hpp"
|
||||
#include "ini_type.h"
|
||||
#include "string_func.h"
|
||||
|
||||
|
@ -198,78 +199,68 @@ void IniLoadFile::LoadFromDisk(std::string_view filename, Subdirectory subdir)
|
|||
|
||||
end += ftell(*in);
|
||||
|
||||
size_t line = 0;
|
||||
/* for each line in the file */
|
||||
while (static_cast<size_t>(ftell(*in)) < end && fgets(buffer, sizeof(buffer), *in)) {
|
||||
char c, *s;
|
||||
/* trim whitespace from the left side */
|
||||
for (s = buffer; *s == ' ' || *s == '\t'; s++) {}
|
||||
|
||||
/* trim whitespace from right side. */
|
||||
char *e = s + strlen(s);
|
||||
while (e > s && ((c = e[-1]) == '\n' || c == '\r' || c == ' ' || c == '\t')) e--;
|
||||
*e = '\0';
|
||||
++line;
|
||||
StringConsumer consumer{StrTrimView(buffer, StringConsumer::WHITESPACE_OR_NEWLINE)};
|
||||
|
||||
/* Skip comments and empty lines outside IGT_SEQUENCE groups. */
|
||||
if ((group == nullptr || group->type != IGT_SEQUENCE) && (*s == '#' || *s == ';' || *s == '\0')) {
|
||||
comment += std::string_view(s, e - s);
|
||||
comment += '\n'; // comment newline
|
||||
if ((group == nullptr || group->type != IGT_SEQUENCE) && (!consumer.AnyBytesLeft() || consumer.PeekCharIfIn("#;"))) {
|
||||
comment += consumer.GetOrigData();
|
||||
comment += "\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
/* it's a group? */
|
||||
if (s[0] == '[') {
|
||||
if (e[-1] != ']') {
|
||||
this->ReportFileError("ini: invalid group name '", buffer, "'");
|
||||
} else {
|
||||
e--;
|
||||
if (consumer.ReadCharIf('[')) {
|
||||
std::string_view group_name = consumer.ReadUntilChar(']', StringConsumer::KEEP_SEPARATOR);
|
||||
if (!consumer.ReadCharIf(']') || consumer.AnyBytesLeft()) {
|
||||
this->ReportFileError(fmt::format("ini [{}]: invalid group name '{}'", line, consumer.GetOrigData()));
|
||||
}
|
||||
s++; // skip [
|
||||
group = &this->CreateGroup(std::string_view(s, e - s));
|
||||
group = &this->CreateGroup(group_name);
|
||||
group->comment = std::move(comment);
|
||||
comment.clear(); // std::move leaves comment in a "valid but unspecified state" according to the specification.
|
||||
} else if (group != nullptr) {
|
||||
if (group->type == IGT_SEQUENCE) {
|
||||
/* A sequence group, use the line as item name without further interpretation. */
|
||||
IniItem &item = group->CreateItem(std::string_view(buffer, e - buffer));
|
||||
IniItem &item = group->CreateItem(consumer.GetOrigData());
|
||||
item.comment = std::move(comment);
|
||||
comment.clear(); // std::move leaves comment in a "valid but unspecified state" according to the specification.
|
||||
continue;
|
||||
}
|
||||
char *t;
|
||||
|
||||
static const std::string_view key_parameter_separators = "=\t ";
|
||||
std::string_view key;
|
||||
/* find end of keyname */
|
||||
if (*s == '\"') {
|
||||
s++;
|
||||
for (t = s; *t != '\0' && *t != '\"'; t++) {}
|
||||
if (*t == '\"') *t = ' ';
|
||||
if (consumer.ReadCharIf('\"')) {
|
||||
key = consumer.ReadUntilChar('\"', StringConsumer::SKIP_ONE_SEPARATOR);
|
||||
} else {
|
||||
for (t = s; *t != '\0' && *t != '=' && *t != '\t' && *t != ' '; t++) {}
|
||||
key = consumer.ReadUntilCharIn(key_parameter_separators);
|
||||
}
|
||||
|
||||
/* it's an item in an existing group */
|
||||
IniItem &item = group->CreateItem(std::string_view(s, t - s));
|
||||
IniItem &item = group->CreateItem(key);
|
||||
item.comment = std::move(comment);
|
||||
comment.clear(); // std::move leaves comment in a "valid but unspecified state" according to the specification.
|
||||
|
||||
/* find start of parameter */
|
||||
while (*t == '=' || *t == ' ' || *t == '\t') t++;
|
||||
consumer.SkipUntilCharNotIn(key_parameter_separators);
|
||||
|
||||
bool quoted = (*t == '\"');
|
||||
/* remove starting quotation marks */
|
||||
if (*t == '\"') t++;
|
||||
/* remove ending quotation marks */
|
||||
e = t + strlen(t);
|
||||
if (e > t && e[-1] == '\"') e--;
|
||||
*e = '\0';
|
||||
|
||||
/* If the value was not quoted and empty, it must be nullptr */
|
||||
if (!quoted && e == t) {
|
||||
if (consumer.ReadCharIf('\"')) {
|
||||
/* There is no escaping in our loader, so we just remove the first and last quote. */
|
||||
std::string_view value = consumer.GetLeftData();
|
||||
if (value.ends_with("\"")) value.remove_suffix(1);
|
||||
item.value = StrMakeValid(value);
|
||||
} else if (!consumer.AnyBytesLeft()) {
|
||||
/* If the value was not quoted and empty, it must be nullptr */
|
||||
item.value.reset();
|
||||
} else {
|
||||
item.value = StrMakeValid(std::string_view(t));
|
||||
item.value = StrMakeValid(consumer.GetLeftData());
|
||||
}
|
||||
} else {
|
||||
/* it's an orphan item */
|
||||
this->ReportFileError("ini: '", buffer, "' outside of group");
|
||||
this->ReportFileError(fmt::format("ini [{}]: '{}' is outside of group", line, consumer.GetOrigData()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -77,11 +77,9 @@ struct IniLoadFile {
|
|||
|
||||
/**
|
||||
* Report an error about the file contents.
|
||||
* @param pre Prefix text of the \a buffer part.
|
||||
* @param buffer Part of the file with the error.
|
||||
* @param post Suffix text of the \a buffer part.
|
||||
* @param message The message to show.
|
||||
*/
|
||||
virtual void ReportFileError(std::string_view pre, std::string_view buffer, std::string_view post) = 0;
|
||||
virtual void ReportFileError(std::string_view message) = 0;
|
||||
};
|
||||
|
||||
/** Ini file that supports both loading and saving. */
|
||||
|
@ -91,7 +89,7 @@ struct IniFile : IniLoadFile {
|
|||
bool SaveToDisk(const std::string &filename);
|
||||
|
||||
std::optional<FileHandle> OpenFile(std::string_view filename, Subdirectory subdir, size_t *size) override;
|
||||
void ReportFileError(std::string_view pre, std::string_view buffer, std::string_view post) override;
|
||||
void ReportFileError(std::string_view message) override;
|
||||
};
|
||||
|
||||
#endif /* INI_TYPE_H */
|
||||
|
|
|
@ -164,9 +164,9 @@ struct SettingsIniFile : IniLoadFile {
|
|||
return in;
|
||||
}
|
||||
|
||||
void ReportFileError(std::string_view pre, std::string_view buffer, std::string_view post) override
|
||||
void ReportFileError(std::string_view message) override
|
||||
{
|
||||
FatalError("{}{}{}", pre, buffer, post);
|
||||
FatalError("{}", message);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue