#include "Json.h" #include #include #include #include #include #include #include #include #include #include #include #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 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 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 writer(buffer); doc.Accept(writer); ofstream jstrm(filename); jstrm << buffer.GetString() << endl; } vector Json::SplitMessage(const string &message, bool preserve) { vector 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; }