pokeplatinum/tools/msgenc/Json.cpp

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