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

View File

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

View File

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