Files
pokeplatinum/tools/msgenc/Json.cpp
2026-06-15 17:20:26 -07:00

232 lines
7.1 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 "dataproc.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 << "#define " << guard.substr(4, guard.size() - 6) << "_ENTRY_COUNT" << " " << id_strings.size() << "\n";
hstrm << "\n";
hstrm << "#endif // MSGENC_" << guard << "\n";
hstrm.flush();
}
// Read messages from JSON into memory to be converted
int Json::FromFile(MessagesConverter &converter) {
int key = JSON_KEY_NOT_DEFINED;
if (dp_load(&this->doc, filename.c_str()) == 0) {
key = dp_u16(dp_get(&this->doc, ".key"));
key &= 0xFFFF;
key |= 0x10000;
datanode_t messages = dp_get(&this->doc, ".messages");
std::size_t numMessages = dp_arrlen(messages);
for (std::size_t i = 0; i < numMessages; i++) {
string message;
datanode_t elem = dp_arrelem(messages, i);
if (dp_hasmemb(elem, "en_US")) {
datanode_t content = dp_objmemb(elem, "en_US");
std::size_t numLines = 0;
switch (content.type) {
case DATAPROC_T_STRING:
message.append(dp_string(content));
break;
case DATAPROC_T_ARRAY:
numLines = dp_arrlen(content);
for (std::size_t j = 0; j < numLines; j++) {
const char *line = dp_string(dp_arrelem(content, j));
if (line) message.append(line);
}
break;
default:
dp_error(&content, "expected an array or string");
continue;
}
}
else if (dp_hasmemb(elem, "garbage")) {
message.resize(dp_int(dp_objmemb(elem, "garbage")), ' ');
}
else {
dp_error(&elem, "expected a definition for one of 'garbage' or 'en_US'");
continue;
}
converter.GetDecodedMessages().emplace_back(message); // emplace a copy
id_strings.emplace_back(dp_string(dp_objmemb(elem, "id")));
this->messages.push_back(message);
IncRowNoBuf();
}
}
if (dp_report(&this->doc) == DIAG_ERROR) {
throw std::runtime_error("JSON parse error");
}
if (!converter.GetHeaderFilename().empty()) {
WriteHeader(converter.GetHeaderFilename());
}
dp_free(&this->doc);
return key;
}
void Json::ToFile(MessagesConverter &converter) {
if (!converter.GetHeaderFilename().empty()) {
ReadHeader(converter.GetHeaderFilename());
}
auto it = id_strings.cbegin();
this->doc = dp_new();
datanode_t root = dp_set_obj(&this->doc);
dp_obj_putint(&root, "key", converter.GetKey());
datanode_t messages = dp_obj_putarray(&root, "messages");
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()) {
datanode_t entry = dp_arr_appobject(&messages);
if (it != id_strings.cend()) {
dp_obj_putstring(&entry, "id", it->c_str());
it++;
} else {
std::snprintf(keybuf, 256, "%s_%s", prefix.c_str(), row_no_buf);
dp_obj_putstring(&entry, "id", keybuf);
}
if (message.find_first_not_of(' ') == string::npos) {
dp_obj_putuint(&entry, "garbage", message.size());
} else {
vector<string> message_lines = SplitMessage(message, true);
if (message_lines.size() == 1) {
dp_obj_putstring(&entry, "en_US", message.c_str());
} else {
datanode_t lines = dp_obj_putarray(&entry, "en_US");
for (const auto& line : message_lines) {
dp_arr_appstring(&lines, line.c_str());
}
}
}
IncRowNoBuf();
}
const char *output = dp_dump(&this->doc);
ofstream jstrm(filename);
jstrm << output << 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;
}