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