1
0
Fork 0

Codechange: rewrite ini file parsing with the StringConsumer

pull/14197/head
Rubidium 2025-05-03 10:27:49 +02:00 committed by rubidium42
parent 6a9f694158
commit 85f1110569
4 changed files with 37 additions and 48 deletions

View File

@ -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);
}

View File

@ -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()));
}
}

View File

@ -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 */

View File

@ -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);
}
};