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