mirror of
https://github.com/pret/pokeplatinum.git
synced 2026-03-21 17:55:13 -05:00
286 lines
9.2 KiB
C++
286 lines
9.2 KiB
C++
#include "Json.h"
|
|
|
|
#include <cctype>
|
|
#include <cstddef>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <fstream>
|
|
#include <ios>
|
|
#include <regex>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
|
|
#include "rapidjson/document.h"
|
|
#include "rapidjson/error/en.h"
|
|
#include "rapidjson/error/error.h"
|
|
#include "rapidjson/prettywriter.h"
|
|
#include "rapidjson/rapidjson.h"
|
|
#include "rapidjson/stringbuffer.h"
|
|
|
|
#include "MessagesConverter.h"
|
|
|
|
string ReadWholeFile(const string_view& fpath) {
|
|
constexpr size_t read_size = 4096;
|
|
|
|
ifstream stream(fpath.data());
|
|
stream.exceptions(ios_base::badbit);
|
|
if (!stream) {
|
|
throw ios_base::failure("file does not exist");
|
|
}
|
|
|
|
string out;
|
|
string buf(read_size, '\0');
|
|
while (stream.read(&buf[0], read_size)) {
|
|
out.append(buf, 0, stream.gcount());
|
|
}
|
|
|
|
out.append(buf, 0, stream.gcount());
|
|
return out;
|
|
}
|
|
|
|
// Read header constants from the supplied file.
|
|
// Constants are expected to be of the format `#define +{name} +{int value}`,
|
|
// and the integer value should be sequential starting from 0.
|
|
void Json::ReadHeader(const string &_filename) {
|
|
string hstring = ReadWholeFile(_filename);
|
|
regex pattern(R"(#define\s+(\w+)\s+([0-9]+))");
|
|
smatch results;
|
|
id_strings.clear();
|
|
while (regex_search(hstring, results, pattern)) {
|
|
id_strings.emplace_back(results[1]);
|
|
hstring = results.suffix().str();
|
|
}
|
|
}
|
|
|
|
// Write header constants to the supplied file.
|
|
// Print constants in the format `#define {name} {int value}` such that the
|
|
// integer values are sequentially ordered starting from 0.
|
|
void Json::WriteHeader(const string &_filename) {
|
|
ofstream hstrm(_filename);
|
|
string guard(_filename);
|
|
for (auto &c : guard) {
|
|
switch (c) {
|
|
case '/':
|
|
case '.':
|
|
case '-':
|
|
c = '_';
|
|
break;
|
|
|
|
default:
|
|
c = toupper(c);
|
|
break;
|
|
}
|
|
}
|
|
|
|
hstrm << "/***************************************************\n";
|
|
hstrm << " * WARNING: This file was autogenerated by msgenc. *\n";
|
|
hstrm << " * DO NOT MODIFY *\n";
|
|
hstrm << " ***************************************************/\n";
|
|
hstrm << "\n";
|
|
hstrm << "#ifndef MSGENC_" << guard << "\n";
|
|
hstrm << "#define MSGENC_" << guard << "\n";
|
|
hstrm << "\n";
|
|
for (size_t i = 0; i < id_strings.size(); i++) {
|
|
vector<string> message_lines = SplitMessage(messages[i]);
|
|
for (const auto& line : message_lines) {
|
|
hstrm << "// " << line << "\n";
|
|
}
|
|
hstrm << "#define " << id_strings[i] << " " << i << "\n";
|
|
}
|
|
|
|
hstrm << "\n";
|
|
hstrm << "#endif // MSGENC_" << guard << "\n";
|
|
hstrm.flush();
|
|
}
|
|
|
|
// Read messages from JSON into memory to be converted
|
|
int Json::FromFile(MessagesConverter &converter) {
|
|
static char errbuf[256] = {0};
|
|
|
|
string json = ReadWholeFile(filename);
|
|
rapidjson::ParseResult result = doc.Parse(json.c_str());
|
|
if (!result) {
|
|
throw runtime_error(rapidjson::GetParseError_En(result.Code()));
|
|
}
|
|
|
|
if (!doc.IsObject()) {
|
|
throw runtime_error("Input document is not an object.");
|
|
}
|
|
|
|
int i = 0;
|
|
int key = JSON_KEY_NOT_DEFINED;
|
|
|
|
if (auto membKey = doc.FindMember("key"); membKey != doc.MemberEnd()) {
|
|
if (!membKey->value.IsInt()) {
|
|
throw runtime_error("Non-numeric value for `key`.");
|
|
}
|
|
|
|
key = membKey->value.GetInt();
|
|
key &= 0xFFFF;
|
|
key |= 0x10000;
|
|
}
|
|
|
|
auto membMessages = doc.FindMember("messages");
|
|
if (membMessages == doc.MemberEnd()) {
|
|
throw runtime_error("Bank: missing required member `messages`.");
|
|
} else if (!membMessages->value.IsArray()) {
|
|
throw runtime_error("Bank.messages: expected an array.");
|
|
}
|
|
|
|
for (const auto &entry : membMessages->value.GetArray()) {
|
|
if (!entry.IsObject()) {
|
|
sprintf(errbuf, "Bank.messages[%d]: expected an object.", i);
|
|
throw runtime_error(errbuf);
|
|
}
|
|
|
|
string message;
|
|
auto membId = entry.FindMember("id");
|
|
auto membGarbage = entry.FindMember("garbage");
|
|
auto membText = entry.FindMember("en_US"); // TODO: Multi-language support
|
|
|
|
if (membId == entry.MemberEnd()) {
|
|
sprintf(errbuf, "Bank.messages[%d]: missing required member `id`.", i);
|
|
throw runtime_error(errbuf);
|
|
} else if (!membId->value.IsString()) {
|
|
sprintf(errbuf, "Bank.messages[%d].id: expected a string.", i);
|
|
throw runtime_error(errbuf);
|
|
}
|
|
|
|
if (membGarbage != entry.MemberEnd()) {
|
|
if (!membGarbage->value.IsInt()) {
|
|
sprintf(errbuf, "Bank.messages[%d].garbage: expected an integer.", i);
|
|
throw runtime_error(errbuf);
|
|
}
|
|
|
|
message.resize(membGarbage->value.GetInt(), ' ');
|
|
goto enqueue_message;
|
|
}
|
|
|
|
if (membText == entry.MemberEnd()) {
|
|
sprintf(errbuf, "Bank.messages[%d]: missing required member `en_US`.", i);
|
|
throw runtime_error(errbuf);
|
|
} else if (!membText->value.IsString() && !membText->value.IsArray()) {
|
|
sprintf(errbuf, "Bank.messages[%d].en_US: expected a string or multi-line array of strings.", i);
|
|
throw runtime_error(errbuf);
|
|
}
|
|
|
|
if (membText->value.IsString()) {
|
|
message.assign(membText->value.GetString());
|
|
} else if (membText->value.IsArray()) {
|
|
for (const auto &line : membText->value.GetArray()) {
|
|
if (!line.IsString()) {
|
|
goto content_error;
|
|
}
|
|
|
|
message.append(line.GetString());
|
|
}
|
|
} else {
|
|
content_error:
|
|
sprintf(errbuf, "Bank.messages[%d].en_US: expected a string or array of strings.", i);
|
|
throw runtime_error(errbuf);
|
|
}
|
|
|
|
enqueue_message:
|
|
converter.GetDecodedMessages().emplace_back(message); // emplace a copy
|
|
id_strings.emplace_back(membId->value.GetString());
|
|
messages.push_back(message);
|
|
i++;
|
|
IncRowNoBuf();
|
|
}
|
|
|
|
if (!converter.GetHeaderFilename().empty()) {
|
|
WriteHeader(converter.GetHeaderFilename());
|
|
}
|
|
|
|
return key;
|
|
}
|
|
|
|
void Json::ToFile(MessagesConverter &converter) {
|
|
if (!converter.GetHeaderFilename().empty()) {
|
|
ReadHeader(converter.GetHeaderFilename());
|
|
}
|
|
|
|
auto it = id_strings.cbegin();
|
|
doc.SetObject();
|
|
doc.AddMember("key", converter.GetKey(), doc.GetAllocator());
|
|
|
|
rapidjson::Value messages(rapidjson::kArrayType);
|
|
|
|
char keybuf[256];
|
|
string prefix = filename.substr(filename.find_last_of('/') + 1);
|
|
prefix = prefix.substr(0, prefix.find_first_of('.'));
|
|
for (const auto &message : converter.GetDecodedMessages()) {
|
|
rapidjson::Value entry_name(rapidjson::kStringType);
|
|
if (it != id_strings.cend()) {
|
|
entry_name.SetString(it->c_str(), doc.GetAllocator());
|
|
} else {
|
|
sprintf(keybuf, "%s_%s", prefix.c_str(), row_no_buf);
|
|
entry_name.SetString(keybuf, doc.GetAllocator());
|
|
}
|
|
|
|
rapidjson::Value entry(rapidjson::kObjectType);
|
|
entry.AddMember("id", entry_name, doc.GetAllocator());
|
|
|
|
if (message.find_first_not_of(' ') == string::npos) {
|
|
entry.AddMember("garbage", message.size(), doc.GetAllocator());
|
|
} else {
|
|
vector<string> message_lines = SplitMessage(message, true);
|
|
if (message_lines.size() == 1) {
|
|
rapidjson::Value entry_message(rapidjson::kStringType);
|
|
entry_message.SetString(message.c_str(), message.size(), doc.GetAllocator());
|
|
entry.AddMember("en_US", entry_message, doc.GetAllocator());
|
|
} else {
|
|
rapidjson::Value entry_lines(rapidjson::kArrayType);
|
|
for (const auto& line : message_lines) {
|
|
rapidjson::Value entry_line(rapidjson::kStringType);
|
|
entry_line.SetString(line.c_str(), line.size(), doc.GetAllocator());
|
|
entry_lines.PushBack(entry_line, doc.GetAllocator());
|
|
}
|
|
entry.AddMember("en_US", entry_lines, doc.GetAllocator());
|
|
}
|
|
}
|
|
|
|
messages.PushBack(entry, doc.GetAllocator());
|
|
IncRowNoBuf();
|
|
it++;
|
|
}
|
|
|
|
doc.AddMember("messages", messages, doc.GetAllocator());
|
|
|
|
rapidjson::StringBuffer buffer;
|
|
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
|
|
doc.Accept(writer);
|
|
|
|
ofstream jstrm(filename);
|
|
jstrm << buffer.GetString() << endl;
|
|
}
|
|
|
|
vector<string> Json::SplitMessage(const string &message, bool preserve) {
|
|
vector<string> v;
|
|
|
|
size_t start = 0, i = 0;
|
|
for (; i < message.size(); i++) {
|
|
if (message[i] == '\r' || message[i] == '\n' || message[i] == '\f') {
|
|
if (preserve) {
|
|
v.push_back(message.substr(start, i - start + 1));
|
|
} else {
|
|
v.push_back(message.substr(start, i - start));
|
|
}
|
|
|
|
start = i + 1;
|
|
}
|
|
}
|
|
|
|
if (start < message.size()) {
|
|
if (preserve) {
|
|
v.push_back(message.substr(start, i - start + 1));
|
|
} else {
|
|
v.push_back(message.substr(start, i - start));
|
|
}
|
|
}
|
|
|
|
return v;
|
|
}
|