Add to_json and from_json script functions

This commit is contained in:
GenevensiS
2025-08-07 18:45:12 +02:00
committed by GitHub
parent 12eb39b5e2
commit dc348b4812
18 changed files with 748 additions and 142 deletions
+1 -1
View File
@@ -7,7 +7,7 @@ Creates a new [[type:card]] object. The card is not automatically added to a set
The argument is a map from card field names to values, for example @new_card([name: "My Card"])@ creates a card with the name @"My Card"@, and all other fields at their default value.
The map can also contain the following built-in keys: notes, id, linked_card_1 to linked_card_4, linked_relation_1 to linked_relation_4, stylesheet, and styling_data. For styling_data, the value must itself be a map from styling field names to values. Be sure to define a stylesheet before styling_data.
The map can also contain the following built-in keys: notes, id, linked_card_1 to linked_card_4, linked_relation_1 to linked_relation_4, stylesheet, styling_data, and extra_data. For styling_data and extra_data, the value must itself be a map from field names to values. Be sure to define a stylesheet before these.
NOTE: you should use underscores instead of spaces in field names.
-1
View File
@@ -40,7 +40,6 @@ Field::~Field() {}
void Field::initDependencies(Context& ctx, const Dependency& dep) const {
sort_script.initDependencies(ctx, dep);
import_script.initDependencies(ctx, dep);
}
IMPLEMENT_REFLECTION(Field) {
+2 -3
View File
@@ -62,9 +62,9 @@ wxDataFormat CardsDataObject::format = _("application/x-mse-cards");
CardsDataObject::CardsDataObject(const SetP& set, const vector<CardP>& cards) {
// set the stylesheet, so when deserializing we know whos style options we are reading
bool* has_styling = new bool[cards.size()];
vector<bool> has_styling;
for (size_t i = 0 ; i < cards.size() ; ++i) {
has_styling[i] = cards[i]->has_styling && !cards[i]->stylesheet;
has_styling.push_back(cards[i]->has_styling && !cards[i]->stylesheet);
if (has_styling[i]) {
cards[i]->stylesheet = set->stylesheet;
}
@@ -78,7 +78,6 @@ CardsDataObject::CardsDataObject(const SetP& set, const vector<CardP>& cards) {
}
}
SetFormat(format);
delete [] has_styling;
}
CardsDataObject::CardsDataObject() {
+5 -5
View File
@@ -91,11 +91,11 @@ public:
Keyword() : fixed(false), valid(false) {}
String keyword; ///< The keyword, only for human use
String rules; ///< Rules/explanation
String match; ///< String to match, <atom-param> tags are used for parameters
vector<KeywordParamP> parameters; ///< The types of parameters
StringScript reminder; ///< Reminder text of the keyword
String mode; ///< Mode of use, can be used by scripts (only gives the name)
String rules; ///< Rules/explanation
String match; ///< String to match, <atom-param> tags are used for parameters
vector<KeywordParamP> parameters; ///< The types of parameters
StringScript reminder; ///< Reminder text of the keyword
String mode; ///< Mode of use, can be used by scripts (only gives the name)
/// Regular expression to match and split parameters, automatically generated.
/** The regex has exactly 2 * parameters.size() + 1 captures (excluding the entire match, caputure 0),
* captures 1,3,... capture the plain text of the match string
+1 -1
View File
@@ -94,7 +94,7 @@ bool PackType::update(Context& ctx) {
bool PackItem::update(Context& ctx) {
return amount.update(ctx)
| weight.update(ctx);
|| weight.update(ctx);
}
+1 -1
View File
@@ -50,7 +50,7 @@ public:
/// The values on the fields of the set
/** The indices should correspond to the set_fields in the Game */
IndexMap<FieldP, ValueP> data;
/// Extra values for specitic stylesheets, indexed by stylesheet name
/// Extra values for specific stylesheets, indexed by stylesheet name
DelayedIndexMaps<FieldP,ValueP> styling_data;
vector<CardP> cards; ///< The cards in the set
vector<KeywordP> keywords; ///< Additional keywords used in this set
+3 -50
View File
@@ -19,10 +19,11 @@
#include <data/stylesheet.hpp>
#include <data/action/set.hpp>
#include <gui/add_json_window.hpp>
#include <script/functions/json.hpp>
#include <script/functions/construction_helper.hpp>
#include <wx/statline.h>
#include <boost/json/src.hpp>
#include <boost/json.hpp>
#include <boost/json/src.hpp>
// ----------------------------------------------------------------------------- : AddJSON
@@ -63,54 +64,6 @@ AddJSONWindow::AddJSONWindow(Window* parent, const SetP& set, bool sizer)
}
}
static ScriptValueP json_to_script(boost::json::value& jv) {
if (jv == nullptr) return script_nil;
else if (jv.is_null()) return script_nil;
else if (jv.is_bool()) return to_script(jv.get_bool());
else if (jv.is_double()) return to_script(jv.get_double());
else if (jv.is_int64()) {
int integer = jv.get_int64();
return to_script(integer);
}
else if (jv.is_uint64()) {
int integer = jv.get_uint64();
return to_script(integer);
}
else if (jv.is_string()) {
std::string stdstring = boost::json::value_to<std::string>(jv);
String wxstring(stdstring.c_str(), wxConvUTF8);
return to_script(wxstring);
}
else if (jv.is_array()) {
boost::json::array array = jv.get_array();
ScriptCustomCollectionP result = make_intrusive<ScriptCustomCollection>();
for (int i = 0; i < array.size(); ++i) {
boost::json::value jvalue = array[i];
ScriptValueP value = json_to_script(jvalue);
result->value.push_back(value);
}
return result;
}
else if (jv.is_object()) {
boost::json::object object = jv.get_object();
ScriptCustomCollectionP result = make_intrusive<ScriptCustomCollection>();
for (auto it = object.begin(); it != object.end(); ++it) {
boost::json::string_view jview = it->key();
std::string_view stdview = std::string_view(jview.data(), jview.size());
std::string stdstring = { stdview.begin(), stdview.end() };
String key(stdstring.c_str(), wxConvUTF8);
boost::json::value jvalue = it->value();
ScriptValueP value = json_to_script(jvalue);
result->key_value[key] = value;
}
return result;
}
else {
queue_message(MESSAGE_ERROR, _ERROR_("add card json unknown type"));
return script_nil;
}
}
void AddJSONWindow::setJSONType() {
int sel = json_type->GetSelection();
if (sel == json_type->GetCount() - 1) { // Custom type
@@ -210,7 +163,7 @@ void AddJSONWindow::onBrowseFiles(wxCommandEvent&) {
auto& card = card_array[i].as_object();
for (int h = 0; h < headers_out.size(); ++h) {
auto& value = card[headers_out[h].ToStdString()];
row.push_back(json_to_script(value));
row.push_back(json_to_mse(value, set.get()));
}
table_out.push_back(row);
}
+23 -27
View File
@@ -102,36 +102,32 @@ CardsPanel::CardsPanel(Window* parent, int id)
}
void CardsPanel::updateCardCounts() {
if (counts) {
if (card_list && set) {
int selected = card_list->GetSelectedItemCount();
int filtered = card_list->GetItemCount();
int total = set->cards.size();
if (counts && card_list && set) {
int selected = card_list->GetSelectedItemCount();
int filtered = card_list->GetItemCount();
int total = set->cards.size();
if (
selected_cards_count == selected
&& filtered_cards_count == filtered
&& total_cards_count == total
) return;
if (
selected_cards_count == selected
&& filtered_cards_count == filtered
&& total_cards_count == total
&& !counts->GetLabel().empty()
) return;
selected_cards_count = selected;
filtered_cards_count = filtered;
total_cards_count = total;
selected_cards_count = selected;
filtered_cards_count = filtered;
total_cards_count = total;
if (filtered == total) {
counts->SetLabel(_TOOL_2_("card counts 2",
wxString::Format(wxT("%i"), selected),
wxString::Format(wxT("%i"), total)));
}
else {
counts->SetLabel(_TOOL_3_("card counts 3",
wxString::Format(wxT("%i"), selected),
wxString::Format(wxT("%i"), filtered),
wxString::Format(wxT("%i"), total)));
}
} else {
counts->SetLabel(_(""));
if (filtered == total) {
counts->SetLabel(_TOOL_2_("card counts 2",
wxString::Format(wxT("%i"), selected),
wxString::Format(wxT("%i"), total)));
}
else {
counts->SetLabel(_TOOL_3_("card counts 3",
wxString::Format(wxT("%i"), selected),
wxString::Format(wxT("%i"), filtered),
wxString::Format(wxT("%i"), total)));
}
}
}
+24
View File
@@ -11,6 +11,8 @@
#include <util/version.hpp>
#include <script/functions/functions.hpp>
#include <script/functions/util.hpp>
#include <script/functions/construction_helper.hpp>
#include <script/functions/json.hpp>
#include <util/tagged_string.hpp>
#include <util/spec_sort.hpp>
#include <util/error.hpp>
@@ -21,6 +23,8 @@
#include <random>
#include <wx/filename.h>
#include <wx/stdpaths.h>
#include <wx/wfstream.h>
#include <boost/json.hpp>
// ----------------------------------------------------------------------------- : Debugging
@@ -257,6 +261,24 @@ SCRIPT_FUNCTION(to_code) {
SCRIPT_RETURN(input->toCode());
}
SCRIPT_FUNCTION(to_json) {
SCRIPT_PARAM_C(ScriptValueP, input);
SCRIPT_PARAM_C(Set*, set);
SCRIPT_PARAM_DEFAULT(bool, pretty_print, true);
boost::json::value jv = mse_to_json(input, set);
queue_message(MESSAGE_ERROR, json_pretty_print(jv));
if (pretty_print) return to_script(json_pretty_print(jv));
else return to_script(json_ugly_print(jv));
}
SCRIPT_FUNCTION(from_json) {
SCRIPT_PARAM_C(ScriptValueP, input);
SCRIPT_PARAM_C(Set*, set);
return json_to_mse(input, set);
}
SCRIPT_FUNCTION(type_name) {
SCRIPT_PARAM_C(ScriptValueP, input);
SCRIPT_RETURN(input->typeName());
@@ -818,6 +840,8 @@ void init_script_basic_functions(Context& ctx) {
ctx.setVariable(_("to_color"), script_to_color);
ctx.setVariable(_("to_date"), script_to_date);
ctx.setVariable(_("to_code"), script_to_code);
ctx.setVariable(_("to_json"), script_to_json);
ctx.setVariable(_("from_json"), script_from_json);
ctx.setVariable(_("type_name"), script_type_name);
ctx.setVariable(_("make_map"), script_make_map);
ctx.setVariable(_("get_card_styling"), script_get_card_styling);
+6 -6
View File
@@ -37,9 +37,9 @@ SCRIPT_FUNCTION(new_card) {
if (key == script_nil) continue;
String key_name = key->toString();
// check if the given value is for a built-in field
if (set_builtin_container(game, new_card, value, key_name, ignore_field_not_found)) continue;
if (set_builtin_container(*game, new_card, value, key_name, ignore_field_not_found)) continue;
// find the field value (container) that corresponds to the given value
Value* container = get_container(game, new_card, key_name, ignore_field_not_found);
Value* container = get_card_field_container(*game, new_card->data, key_name, ignore_field_not_found);
if (container == nullptr) continue;
FieldP field = container->fieldP;
// if the field has a construction script, set the value and card context variables to be the given value and this card, run script
@@ -59,9 +59,9 @@ SCRIPT_FUNCTION(new_card) {
if (script_key == script_nil) continue;
String script_key_name = script_key->toString();
// check if the script value is for a built-in field
if (set_builtin_container(game, new_card, script_value, script_key_name, ignore_field_not_found)) continue;
if (set_builtin_container(*game, new_card, script_value, script_key_name, ignore_field_not_found)) continue;
// find the field value that corresponds to the script value
Value* script_container = get_container(game, new_card, script_key_name, ignore_field_not_found);
Value* script_container = get_card_field_container(*game, new_card->data, script_key_name, ignore_field_not_found);
if (script_container == nullptr) continue;
// set the field value to the script value
set_container(script_container, script_value, script_key_name);
@@ -94,9 +94,9 @@ SCRIPT_FUNCTION(new_card) {
if (script_key == script_nil) continue;
String script_key_name = script_key->toString();
// check if the script value is for a built-in field
if (set_builtin_container(game, new_card, script_value, script_key_name, ignore_field_not_found)) continue;
if (set_builtin_container(*game, new_card, script_value, script_key_name, ignore_field_not_found)) continue;
// find the field value that corresponds to the script value
Value* script_container = get_container(game, new_card, script_key_name, ignore_field_not_found);
Value* script_container = get_card_field_container(*game, new_card->data, script_key_name, ignore_field_not_found);
if (script_container == nullptr) continue;
// set the field value to the script value
set_container(script_container, script_value, script_key_name);
+66 -41
View File
@@ -4,6 +4,8 @@
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
#pragma once
// ----------------------------------------------------------------------------- : Includes
#include <util/prec.hpp>
@@ -12,33 +14,46 @@
#include <data/field/package_choice.hpp>
#include <data/field/color.hpp>
#include <data/field/image.hpp>
#include <data/field/symbol.hpp>
#include <data/action/set.hpp>
#include <data/game.hpp>
#include <data/set.hpp>
#include <data/stylesheet.hpp>
#include <data/card.hpp>
#include <util/error.hpp>
// ----------------------------------------------------------------------------- : Helper functions
static Value* get_container(GameP& game, CardP& card, String key_name, bool ignore_field_not_found) {
inline static Value* get_card_field_container(Game& game, IndexMap<FieldP, ValueP>& map, String& key_name, bool ignore_field_not_found) {
// find value container to update
IndexMap<FieldP, ValueP>::const_iterator value_it = card->data.find(key_name);
if (value_it == card->data.end()) {
IndexMap<FieldP, ValueP>::const_iterator it = map.find(key_name);
if (it == map.end()) {
// look among alternate names
map<String, String>::iterator alt_name_it = game->card_fields_alt_names.find(unified_form(key_name));
if (alt_name_it != game->card_fields_alt_names.end()) {
value_it = card->data.find(alt_name_it->second);
std::map<String, String>::iterator alt_name_it = game.card_fields_alt_names.find(unified_form(key_name));
if (alt_name_it != game.card_fields_alt_names.end()) {
it = map.find(alt_name_it->second);
}
}
if (value_it == card->data.end()) {
if (it == map.end()) {
if (ignore_field_not_found) return nullptr;
throw ScriptError(_ERROR_1_("no field with name", key_name));
throw ScriptError(_ERROR_2_("no field with name", _TYPE_("card"), key_name));
}
return value_it->get();
return it->get();
}
static void set_container(Value* container, ScriptValueP& value, String key_name) {
inline static Value* get_container(IndexMap<FieldP, ValueP>& map, String& type, String& key_name, bool ignore_field_not_found) {
// find value container to update
IndexMap<FieldP, ValueP>::const_iterator it = map.find(key_name);
if (it == map.end()) {
it = map.find(key_name.Lower());
if (it == map.end()) {
if (ignore_field_not_found) return nullptr;
throw ScriptError(_ERROR_2_("no field with name", _TYPE_V_(type), key_name));
}
}
return it->get();
}
inline static void set_container(Value* container, ScriptValueP& value, String key_name) {
// set the given value into the container
if (TextValue* tvalue = dynamic_cast<TextValue*>(container)) {
tvalue->value = value->toString();
@@ -53,23 +68,36 @@ static void set_container(Value* container, ScriptValueP& value, String key_name
cvalue->value = value->toColor();
}
else if (ImageValue* ivalue = dynamic_cast<ImageValue*>(container)) {
wxFileName fname(static_cast<ExternalImage*>(value.get())->toString());
ivalue->filename = LocalFileName::fromReadString(fname.GetName(), "");
if (ExternalImage* image = dynamic_cast<ExternalImage*>(value.get())) {
wxFileName fname(image->toString());
ivalue->filename = LocalFileName::fromReadString(fname.GetName(), "");
} else if (value->type() == SCRIPT_STRING) {
ivalue->filename = LocalFileName::fromReadString(value->toString(), "");
} else {
throw ScriptError(_ERROR_1_("cant set image value", key_name));
}
}
else if (SymbolValue* svalue = dynamic_cast<SymbolValue*>(container)) {
if (value->type() == SCRIPT_STRING) {
svalue->filename = LocalFileName::fromReadString(value->toString(), "");
} else {
throw ScriptError(_ERROR_1_("cant set symbol value", key_name));
}
}
else {
throw ScriptError(_ERROR_1_("can't set value", key_name));
throw ScriptError(_ERROR_1_("cant set value", key_name));
}
}
static bool set_builtin_container(GameP& game, CardP& card, ScriptValueP& value, String key_name, bool ignore_field_not_found) {
inline static bool set_builtin_container(const Game& game, CardP& card, ScriptValueP& value, String key_name, bool ignore_field_not_found) {
// check if the given value is for a built-in field, if found set it and return true
key_name = unified_form(key_name);
if (key_name == _("notes") || key_name == _("note")) {
card->notes = value->toString();
return true;
} else if (key_name == _("style") || key_name == _("stylesheet") || key_name == _("template")) {
if (trim(value->toString()) != wxEmptyString) {
card->stylesheet = StyleSheet::byGameAndName(*game, value->toString());
if (!trim(value->toString()).empty()) {
card->stylesheet = StyleSheet::byGameAndName(game, value->toString());
if (card->stylesheet) card->styling_data.init(card->stylesheet->styling_fields);
}
return true;
@@ -110,35 +138,34 @@ static bool set_builtin_container(GameP& game, CardP& card, ScriptValueP& value,
// card->linked_relation_4 = value->toString();
// return true;
//}
else if (key_name == _("styling_data") || key_name == _("style_data") || key_name == _("stylesheet_data") || key_name == _("template_data") || key_name == _("styling")
|| key_name == _("styling_fields") || key_name == _("style_fields") || key_name == _("stylesheet_fields") || key_name == _("template_fields")) {
else if (key_name == _("styling_data") || key_name == _("style_data") || key_name == _("stylesheet_data") || key_name == _("template_data") || key_name == _("styling")
|| key_name == _("styling_fields") || key_name == _("style_fields") || key_name == _("stylesheet_fields") || key_name == _("template_fields")
|| key_name == _("extra_data") || key_name == _("extra_fields") || key_name == _("extra_card_data") || key_name == _("extra_card_fields")) {
bool is_extra = key_name == _("extra_data") || key_name == _("extra_fields") || key_name == _("extra_card_data") || key_name == _("extra_card_fields");
String type = is_extra ? _("extra") : _("styling");
if (value->type() != SCRIPT_COLLECTION) {
throw ScriptError(_ERROR_("styling data not map"));
throw ScriptError(_ERROR_1_("styling data not map", type));
}
ScriptValueP value_it = value->makeIterator();
ScriptValueP value_key;
while (ScriptValueP value_value = value_it->next(&value_key)) {
assert(value_key);
if (value_key == script_nil) continue;
String value_key_name = value_key->toString();
IndexMap<FieldP, ValueP>::const_iterator style_it = card->styling_data.find(value_key_name);
if (style_it == card->styling_data.end()) {
style_it = card->styling_data.find(value_key_name.Lower());
if (style_it == card->styling_data.end()) {
if (!ignore_field_not_found) throw ScriptError(_ERROR_1_("no style field with name", value_key_name));
continue;
}
}
Value* value_container = style_it->get();
set_container(value_container, value_value, value_key_name);
card->has_styling = true;
if (!card->stylesheet) {
throw ScriptError(_ERROR_1_("styling data without stylesheet", type));
}
IndexMap<FieldP, ValueP>& data = is_extra ? card->extraDataFor(*card->stylesheet) : card->styling_data;
ScriptValueP it = value->makeIterator();
ScriptValueP key;
while (ScriptValueP value = it->next(&key)) {
assert(key);
if (key == script_nil) continue;
String key_name = key->toString();
Value* container = get_container(data, type, key_name, ignore_field_not_found);
set_container(container, value, key_name);
if (!is_extra) card->has_styling = true;
}
return true;
}
return false;
}
static bool check_table_headers(GameP& game, std::vector<String>& headers, const String& file_extension, String& missing_fields_out) {
inline static bool check_table_headers(GameP& game, std::vector<String>& headers, const String& file_extension, String& missing_fields_out) {
if (headers.empty()) {
queue_message(MESSAGE_ERROR, _("Empty headers given"));
return false;
@@ -176,7 +203,7 @@ static bool check_table_headers(GameP& game, std::vector<String>& headers, const
return true;
}
static bool cards_from_table(SetP& set, vector<String>& headers, std::vector<std::vector<ScriptValueP>>& table, bool ignore_field_not_found, const String& file_extension, vector<CardP>& cards_out) {
inline static bool cards_from_table(SetP& set, vector<String>& headers, std::vector<std::vector<ScriptValueP>>& table, bool ignore_field_not_found, const String& file_extension, vector<CardP>& cards_out) {
// ensure table is square
int count = headers.size();
for (int y = 0; y < table.size(); ++y) {
@@ -212,5 +239,3 @@ static bool cards_from_table(SetP& set, vector<String>& headers, std::vector<std
if (ctx_ignore) ctx.setVariable("ignore_field_not_found", ctx_ignore);
return true;
}
+596
View File
@@ -0,0 +1,596 @@
//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
//| Copyright: (C) Twan van Laarhoven and the other MSE developers |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
#pragma once
// ----------------------------------------------------------------------------- : Includes
#include <util/prec.hpp>
#include <data/set.hpp>
#include <data/card.hpp>
#include <data/pack.hpp>
#include <data/format/clipboard.hpp>
#include <script/functions/construction_helper.hpp>
#include <boost/json.hpp>
#include <sstream>
#include <wx/sstream.h>
// All this isn't great, but it will do for now. Idealy you would create JsonWriter and JsonReader classes
// that inherit from Writer and Reader, and just have a switch to go from normal mode to JSON mode...
// ----------------------------------------------------------------------------- : JSON to String
inline static void pretty_print(std::ostream& os, boost::json::value const& jv, std::string* indent = nullptr)
{
std::string indent_;
if(! indent)
indent = &indent_;
switch(jv.kind())
{
case boost::json::kind::object:
{
os << "{\n";
indent->append(4, ' ');
auto const& obj = jv.get_object();
if(! obj.empty())
{
auto it = obj.begin();
for(;;)
{
os << *indent << boost::json::serialize(it->key()) << " : ";
pretty_print(os, it->value(), indent);
if(++it == obj.end())
break;
os << ",\n";
}
}
os << "\n";
indent->resize(indent->size() - 4);
os << *indent << "}";
break;
}
case boost::json::kind::array:
{
os << "[\n";
indent->append(4, ' ');
auto const& arr = jv.get_array();
if(! arr.empty())
{
auto it = arr.begin();
for(;;)
{
os << *indent;
pretty_print( os, *it, indent);
if(++it == arr.end())
break;
os << ",\n";
}
}
os << "\n";
indent->resize(indent->size() - 4);
os << *indent << "]";
break;
}
case boost::json::kind::string:
{
os << boost::json::serialize(jv.get_string());
break;
}
case boost::json::kind::uint64:
case boost::json::kind::int64:
case boost::json::kind::double_:
os << jv;
break;
case boost::json::kind::bool_:
if(jv.get_bool())
os << "true";
else
os << "false";
break;
case boost::json::kind::null:
os << "null";
break;
}
if(indent->empty())
os << "\n";
}
inline static String json_pretty_print(boost::json::value const& jv, std::string* indent = nullptr) {
std::ostringstream stream;
pretty_print(stream, jv, indent);
String string = wxString(stream.str().c_str());
return string;
}
inline static String json_ugly_print(boost::json::value const& jv) {
String string = wxString(boost::json::serialize(jv).c_str());
return string;
}
// ----------------------------------------------------------------------------- : JSON to MSE
inline static ScriptValueP json_to_mse(boost::json::value& jv, Set* set);
template <typename T>
static void read(T& out, boost::json::object& jv, const char value_name[]) {
if (!jv.contains(value_name)) return;
else {
wxStringInputStream stream = {_("")};
Reader reader(stream, nullptr, _(""));
reader.setValue(wxString(jv[value_name].as_string().c_str()));
reader.handle(out);
}
}
// templates don't work with enums? are you kidding me with this language?
static void read(PackSelectType& out, boost::json::object& jv, const char value_name[]) {
if (!jv.contains(value_name)) return;
else {
wxStringInputStream stream = {_("")};
Reader reader(stream, nullptr, _(""));
reader.setValue(wxString(jv[value_name].as_string().c_str()));
reader.handle(out);
}
}
inline static PackItemP json_to_mse_pack_item(boost::json::object& jv) {
PackItemP pack_item = make_intrusive<PackItem>();
read(pack_item->name, jv, "name");
read(pack_item->amount, jv, "amount");
read(pack_item->weight, jv, "weight");
return pack_item;
}
inline static PackTypeP json_to_mse_pack_type(boost::json::object& jv) {
PackTypeP pack_type = make_intrusive<PackType>();
read(pack_type->name, jv, "name");
read(pack_type->enabled, jv, "enabled");
read(pack_type->selectable, jv, "selectable");
read(pack_type->summary, jv, "summary");
read(pack_type->select, jv, "select");
if (jv.contains("items") && jv["items"].is_array()) {
boost::json::array pack_itemsv = jv["items"].as_array();
for (int i = 0; i < pack_itemsv.size(); i++) {
boost::json::object pack_itemv = pack_itemsv[i].as_object();
pack_type->items.emplace_back(json_to_mse_pack_item(pack_itemv));
}
}
return pack_type;
}
inline static KeywordP json_to_mse_keyword(boost::json::object& jv) {
KeywordP keyword = make_intrusive<Keyword>();
read(keyword->keyword, jv, "keyword");
read(keyword->match, jv, "match");
read(keyword->reminder, jv, "reminder");
read(keyword->rules, jv, "rules");
read(keyword->mode, jv, "mode");
return keyword;
}
inline static CardP json_to_mse_card(boost::json::object& jv, Set* set) {
CardP card = make_intrusive<Card>(*set->game);
read(card->time_created, jv, "time_created");
read(card->time_modified, jv, "time_modified");
read(card->notes, jv, "notes");
//read(card->uid, jv, "uid");
//read(card->linked_card_1, jv, "linked_card_1");
//read(card->linked_card_2, jv, "linked_card_2");
//read(card->linked_card_3, jv, "linked_card_3");
//read(card->linked_card_4, jv, "linked_card_4");
//read(card->linked_relation_1, jv, "linked_relation_1");
//read(card->linked_relation_2, jv, "linked_relation_2");
//read(card->linked_relation_3, jv, "linked_relation_3");
//read(card->linked_relation_4, jv, "linked_relation_4");
// card fields
if (jv.contains("data") && jv["data"].is_object()) {
boost::json::object datav = jv["data"].as_object();
for (auto it = datav.begin(); it != datav.end(); ++it) {
String key_name = wxString(it->key_c_str());
Value* container = get_card_field_container(*set->game, card->data, key_name, false);
ScriptValueP value = json_to_mse(it->value(), set);
set_container(container, value, key_name);
}
}
// stylesheet
if (jv.contains("stylesheet")) card->stylesheet = StyleSheet::byGameAndName(*set->game, wxString(jv["stylesheet"].as_string().c_str()));
if (card->stylesheet) {
// styling fields
card->styling_data.init(card->stylesheet->styling_fields);
if (jv.contains("styling_data") && jv["styling_data"].is_object()) {
boost::json::object datav = jv["styling_data"].as_object();
for (auto it = datav.begin(); it != datav.end(); ++it) {
String key_name = wxString(it->key_c_str());
Value* container = get_container(card->styling_data, wxString("styling"), key_name, false);
ScriptValueP value = json_to_mse(it->value(), set);
set_container(container, value, key_name);
card->has_styling = true;
}
}
// extra card fields
if (jv.contains("extra_data") && jv["extra_data"].is_object()) {
boost::json::object datav = jv["extra_data"].as_object();
for (auto it = datav.begin(); it != datav.end(); ++it) {
StyleSheetP& stylesheet = StyleSheet::byGameAndName(*set->game, it->key_c_str());
if (!stylesheet) continue;
IndexMap<FieldP, ValueP>& stylesheet_data = card->extraDataFor(*stylesheet);
boost::json::object stylesheet_datav = it->value().as_object();
for (auto stylesheet_it = stylesheet_datav.begin(); stylesheet_it != stylesheet_datav.end(); ++stylesheet_it) {
String key_name = wxString(stylesheet_it->key_c_str());
Value* container = get_container(stylesheet_data, wxString("extra card"), key_name, false);
ScriptValueP value = json_to_mse(stylesheet_it->value(), set);
set_container(container, value, key_name);
}
}
}
}
return card;
}
inline static SetP json_to_mse_set(boost::json::object& jv) {
if (!jv.contains("game")) {
throw ScriptError(_ERROR_("json set without game"));
}
if (!jv.contains("stylesheet")) {
throw ScriptError(_ERROR_("json set without stylesheet"));
}
GameP& game = Game::byName(wxString(jv["game"].as_string().c_str()));
StyleSheetP& stylesheet = StyleSheet::byGameAndName(*game, wxString(jv["stylesheet"].as_string().c_str()));
SetP& set = make_intrusive<Set>(stylesheet);
// set fields
if (jv.contains("set_info") && jv["set_info"].is_object()) {
boost::json::object datav = jv["set_info"].as_object();
for (auto it = datav.begin(); it != datav.end(); ++it) {
String key_name = wxString(it->key_c_str());
Value* container = get_container(set->data, wxString("set"), key_name, false);
ScriptValueP value = json_to_mse(it->value(), set.get());
set_container(container, value, key_name);
}
}
// styling
if (jv.contains("styling") && jv["styling"].is_object()) {
boost::json::object datav = jv["styling"].as_object();
for (auto it = datav.begin(); it != datav.end(); ++it) {
StyleSheetP& stylesheet = StyleSheet::byGameAndName(*set->game, it->key_c_str());
if (!stylesheet) continue;
IndexMap<FieldP, ValueP>& stylesheet_data = set->stylingDataFor(*stylesheet);
boost::json::object stylesheet_datav = it->value().as_object();
for (auto stylesheet_it = stylesheet_datav.begin(); stylesheet_it != stylesheet_datav.end(); ++stylesheet_it) {
String key_name = wxString(stylesheet_it->key_c_str());
Value* container = get_container(stylesheet_data, wxString("styling"), key_name, false);
ScriptValueP value = json_to_mse(stylesheet_it->value(), set.get());
set_container(container, value, key_name);
}
}
}
// cards
if (jv.contains("cards") && jv["cards"].is_array()) {
boost::json::array cardsv = jv["cards"].as_array();
for (int i = 0; i < cardsv.size(); i++) {
boost::json::object cardv = cardsv[i].as_object();
set->cards.emplace_back(json_to_mse_card(cardv, set.get()));
}
}
// keywords
if (jv.contains("keywords") && jv["keywords"].is_array()) {
boost::json::array keywordsv = jv["keywords"].as_array();
for (int i = 0; i < keywordsv.size(); i++) {
boost::json::object keywordv = keywordsv[i].as_object();
set->keywords.emplace_back(json_to_mse_keyword(keywordv));
}
}
// pack types
if (jv.contains("pack_types") && jv["pack_types"].is_array()) {
boost::json::array pack_typesv = jv["pack_types"].as_array();
for (int i = 0; i < pack_typesv.size(); i++) {
boost::json::object pack_typev = pack_typesv[i].as_object();
set->pack_types.emplace_back(json_to_mse_pack_type(pack_typev));
}
}
return set;
}
inline static ScriptValueP json_to_mse(boost::json::value& jv, Set* set) {
if (jv == nullptr) return script_nil;
else if (jv.is_null()) return script_nil;
else if (jv.is_bool()) return to_script(jv.get_bool());
else if (jv.is_double()) return to_script(jv.get_double());
else if (jv.is_int64()) {
int integer = jv.get_int64();
return to_script(integer);
}
else if (jv.is_uint64()) {
int integer = jv.get_uint64();
return to_script(integer);
}
else if (jv.is_string()) {
std::string stdstring = boost::json::value_to<std::string>(jv);
String wxstring(stdstring.c_str(), wxConvUTF8);
return to_script(wxstring);
}
else if (jv.is_array()) {
boost::json::array array = jv.get_array();
ScriptCustomCollectionP result = make_intrusive<ScriptCustomCollection>();
for (int i = 0; i < array.size(); ++i) {
boost::json::value jvalue = array[i];
ScriptValueP value = json_to_mse(jvalue, set);
result->value.push_back(value);
}
return result;
}
else if (jv.is_object()) {
boost::json::object object = jv.get_object();
if (object.contains("mse_object_type")) {
boost::json::string mse_object_type = object["mse_object_type"].as_string();
if (mse_object_type == "set") return make_intrusive<ScriptObject<SetP>> (json_to_mse_set(object));
if (mse_object_type == "card") return make_intrusive<ScriptObject<CardP>> (json_to_mse_card(object, set));
if (mse_object_type == "keyword") return make_intrusive<ScriptObject<KeywordP>> (json_to_mse_keyword(object));
if (mse_object_type == "pack_type") return make_intrusive<ScriptObject<PackTypeP>>(json_to_mse_pack_type(object));
if (mse_object_type == "pack_item") return make_intrusive<ScriptObject<PackItemP>>(json_to_mse_pack_item(object));
queue_message(MESSAGE_ERROR, _ERROR_("json unknown type") + _("(") + wxString(mse_object_type.c_str()) + _(")"));
return script_nil;
}
ScriptCustomCollectionP result = make_intrusive<ScriptCustomCollection>();
for (auto it = object.begin(); it != object.end(); ++it) {
boost::json::string_view jview = it->key();
std::string_view stdview = std::string_view(jview.data(), jview.size());
std::string stdstring = { stdview.begin(), stdview.end() };
String key(stdstring.c_str(), wxConvUTF8);
boost::json::value jvalue = it->value();
ScriptValueP value = json_to_mse(jvalue, set);
result->key_value[key] = value;
}
return result;
}
else {
queue_message(MESSAGE_ERROR, _ERROR_("json unknown type"));
return script_nil;
}
}
inline static ScriptValueP json_to_mse(String& string, Set* set) {
try {
boost::system::error_code ec;
boost::json::parse_options options;
options.allow_invalid_utf8 = true;
boost::json::value jv = boost::json::parse(string.ToStdString(), ec, {}, options);
if(ec) queue_message(MESSAGE_ERROR, _ERROR_("json cant parse") + _("\n\n") + ec.message());
return json_to_mse(jv, set);
}
catch (...) {
queue_message(MESSAGE_ERROR, _ERROR_("json cant parse"));
return script_nil;
}
}
inline static ScriptValueP json_to_mse(ScriptValueP& sv, Set* set) {
try {
String string = sv->toString();
return json_to_mse(string, set);
}
catch (...) {
queue_message(MESSAGE_ERROR, _ERROR_("json cant convert"));
return script_nil;
}
}
// ----------------------------------------------------------------------------- : MSE to JSON
template <typename T>
static void write(boost::json::object& out, const String& name, T& value) {
wxStringOutputStream stream;
Writer writer(stream);
writer.indentation = -1000;
writer.handle(name, value);
String string = stream.GetString();
if (!string.empty()) {
if (string.StartsWith(name + ":")) string = string.substr(name.length() + 1).Trim(false);
if (string.EndsWith("\n")) string = string.substr(0, string.length() - 1);
out.emplace(name.ToStdString(), string);
}
}
static void write(boost::json::object& out, const String& name, IndexMap<FieldP, ValueP>& map) {
boost::json::object indexmapv;
for (IndexMap<FieldP, ValueP>::iterator it = map.begin(); it != map.end(); ++it) {
write(indexmapv, (*it)->fieldP->name, *it);
}
if (!indexmapv.empty()) out.emplace(name.ToStdString(), indexmapv);
}
static void write(boost::json::object& out, const String& name, DelayedIndexMaps<FieldP,ValueP>& map) {
boost::json::object delayedindexmapv;
for (auto it = map.data.begin() ; it != map.data.end() ; ++it) {
write(delayedindexmapv, it->first, it->second->read_data);
}
if (!delayedindexmapv.empty()) out.emplace(name.ToStdString(), delayedindexmapv);
}
inline static boost::json::object mse_to_json(PackItemP& item) {
boost::json::object itemv;
itemv.emplace("mse_object_type", "pack_item");
write(itemv, "name", item->name);
write(itemv, "amount", item->amount);
write(itemv, "weight", item->weight);
return itemv;
}
inline static boost::json::object mse_to_json(PackTypeP& pack) {
boost::json::object packv;
packv.emplace("mse_object_type", "pack_type");
write(packv, "name", pack->name);
write(packv, "enabled", pack->enabled);
write(packv, "selectable", pack->selectable);
write(packv, "summary", pack->summary);
write(packv, "select", pack->select);
write(packv, "filter", pack->filter);
boost::json::array itemsv;
for (auto item : pack->items) {
itemsv.emplace_back(mse_to_json(item));
}
packv.emplace("items", itemsv);
return packv;
}
inline static boost::json::object mse_to_json(KeywordP& keyword) {
boost::json::object keywordv;
keywordv.emplace("mse_object_type", "keyword");
write(keywordv, "keyword", keyword->keyword);
write(keywordv, "match", keyword->match);
write(keywordv, "reminder", keyword->reminder);
write(keywordv, "rules", keyword->rules);
write(keywordv, "mode", keyword->mode);
return keywordv;
}
inline static boost::json::object mse_to_json(CardP& card, Set* set) {
boost::json::object cardv;
cardv.emplace("mse_object_type", "card");
// built-in values
write(cardv, "time_created", card->time_created);
write(cardv, "time_modified", card->time_modified);
write(cardv, "notes", card->notes);
//write(cardv, "uid", card->uid);
//write(cardv, "linked_card_1", card->linked_card_1);
//write(cardv, "linked_card_2", card->linked_card_2);
//write(cardv, "linked_card_3", card->linked_card_3);
//write(cardv, "linked_card_4", card->linked_card_4);
//write(cardv, "linked_relation_1", card->linked_relation_1);
//write(cardv, "linked_relation_2", card->linked_relation_2);
//write(cardv, "linked_relation_3", card->linked_relation_3);
//write(cardv, "linked_relation_4", card->linked_relation_4);
// card fields
write(cardv, "data", card->data);
// stylesheet
bool change_stylesheet = set && !card->stylesheet;
if (change_stylesheet) {
card->stylesheet = set->stylesheet;
}
if (card->stylesheet) {
write(cardv, "stylesheet", card->stylesheet);
write(cardv, "stylesheet_version", card->stylesheet->version);
// extra card fields
write(cardv, "extra_data", card->extra_data);
}
// style
write(cardv, "has_styling", card->has_styling);
if (card->has_styling) {
write(cardv, "styling_data", card->styling_data);
}
// restore stylesheet
if (change_stylesheet) {
card->stylesheet = StyleSheetP();
}
// done
return cardv;
}
inline static boost::json::object mse_to_json(Set* set) {
boost::json::object setv;
setv.emplace("mse_object_type", "set");
// built-in values
write(setv, "mse_version", set->fileVersion());
write(setv, "game", set->game);
write(setv, "game_version", set->game->version);
write(setv, "stylesheet", set->stylesheet);
write(setv, "stylesheet_version", set->stylesheet->version);
// set fields
write(setv, "set_info", set->data);
// styling
write(setv, "styling", set->styling_data);
// cards
boost::json::array cardsv;
for (auto card : set->cards) {
cardsv.emplace_back(mse_to_json(card, set));
}
setv.emplace("cards", cardsv);
// keywords
boost::json::array keywordsv;
for (auto keyword : set->keywords) {
keywordsv.emplace_back(mse_to_json(keyword));
}
if (!keywordsv.empty()) setv.emplace("keywords", keywordsv);
// pack types
boost::json::array pack_typesv;
for (auto pack_type : set->pack_types) {
pack_typesv.emplace_back(mse_to_json(pack_type));
}
if (!pack_typesv.empty()) setv.emplace("pack_types", pack_typesv);
// done
return setv;
}
inline static boost::json::value mse_to_json(ScriptValueP& sv, Set* set) {
ScriptType type = sv->type();
// special types
if (ScriptObject<PackItemP>* i = dynamic_cast<ScriptObject<PackItemP>*>(sv.get())) return mse_to_json(i->getValue());
if (ScriptObject<PackTypeP>* t = dynamic_cast<ScriptObject<PackTypeP>*>(sv.get())) return mse_to_json(t->getValue());
if (ScriptObject<KeywordP>* k = dynamic_cast<ScriptObject<KeywordP>*> (sv.get())) return mse_to_json(k->getValue());
if (ScriptObject<CardP>* c = dynamic_cast<ScriptObject<CardP>*> (sv.get())) return mse_to_json(c->getValue(), set);
if (ScriptObject<SetP>* z = dynamic_cast<ScriptObject<SetP>*> (sv.get())) return mse_to_json(z->getValue().get());
if (ScriptObject<Set*>* s = dynamic_cast<ScriptObject<Set*>*> (sv.get())) return mse_to_json(s->getValue());
// primitive types
if (type == SCRIPT_NIL) return boost::json::value(nullptr);
if (type == SCRIPT_INT) return boost::json::value(sv->toInt());
if (type == SCRIPT_DOUBLE) return boost::json::value(sv->toDouble());
if (type == SCRIPT_BOOL) return boost::json::value(sv->toBool());
if (type == SCRIPT_STRING) return boost::json::value(sv->toString());
if (type == SCRIPT_REGEX) return boost::json::value(sv->toString());
if (type == SCRIPT_COLOR) return boost::json::value(format_color(sv->toColor()));
if (type == SCRIPT_DATETIME) return boost::json::value(sv->toDateTime().FormatISOCombined(' '));
if (type == SCRIPT_COLLECTION) {
ScriptCustomCollection* custom = dynamic_cast<ScriptCustomCollection*>(sv.get());
if (custom) {
if (custom->value.size() > 0) {
boost::json::array array;
for (int i = 0; i < custom->value.size(); i++) {
array.emplace_back(mse_to_json(custom->value[i], set));
}
return array;
} else if (custom->key_value.size() > 0) {
boost::json::object object;
map<String, ScriptValueP>::iterator it;
for (it = custom->key_value.begin(); it != custom->key_value.end(); it++) {
object.emplace(it->first.ToStdString(), mse_to_json(it->second, set));
}
return object;
}
} else {
ScriptConcatCollection* concat = dynamic_cast<ScriptConcatCollection*>(sv.get());
if (concat) {
boost::json::value a = mse_to_json(concat->getA(), set);
boost::json::value b = mse_to_json(concat->getB(), set);
if (a.is_array() && b.is_array()) {
boost::json::array array_a = a.get_array();
boost::json::array array_b = b.get_array();
for (int i = 0; i < array_b.size(); i++) {
array_a.emplace_back(array_b[i]);
}
return array_a;
} else if (a.is_object() && b.is_object()) {
boost::json::object object_a = a.get_object();
boost::json::object object_b = b.get_object();
for (auto it = object_b.begin(); it != object_b.end(); ++it) {
object_a.emplace(it->key(), it->value());
}
return object_a;
} else {
queue_message(MESSAGE_ERROR, _ERROR_("json cant concat"));
return boost::json::value(nullptr);
}
}
}
}
queue_message(MESSAGE_ERROR, _ERROR_1_("json unknown script type", sv->typeName()));
return boost::json::value(nullptr);
}
+4 -1
View File
@@ -223,7 +223,10 @@ public:
inline ScriptConcatCollection(ScriptValueP a, ScriptValueP b) : a(a), b(b) {}
ScriptValueP getMember(const String& name) const override;
ScriptValueP getIndex(int index) const override;
ScriptValueP makeIterator() const override;
ScriptValueP makeIterator() const override;
ScriptValueP getA() { return a; }
ScriptValueP getB() { return b; }
int itemCount() const override { return a->itemCount() + b->itemCount(); }
/// Collections can be compared by comparing pointers
CompareWhat compareAs(String&, void const*& compare_ptr) const override {
+1 -1
View File
@@ -149,8 +149,8 @@ public:
IndexMap<Key,Value>& get(const String& name, const vector<Key>& init_with);
/// Clear the delayed index map
void clear();
private:
map<String, intrusive_ptr<DelayedIndexMapsData<Key,Value>>> data;
private:
friend class Reader;
friend class Writer;
friend class GetDefaultMember;
+3
View File
@@ -118,6 +118,9 @@ public:
inline Packaged* getPackage() const { return package; }
String addLocale(String);
/// Set the value that will be returned by the next getValue() call (may mess up the state of the reader)
inline void setValue(const String& value) { state = UNHANDLED; previous_value = value; };
private:
// --------------------------------------------------- : Data
+6
View File
@@ -16,6 +16,12 @@
using boost::tribool;
// ----------------------------------------------------------------------------- : Writer
Writer::Writer(OutputStream& output)
: indentation(0)
, output(output)
, stream(output, wxEOL_UNIX, wxMBConvUTF8())
{}
Writer::Writer(OutputStream& output, Version file_app_version)
: indentation(0)
+5 -4
View File
@@ -25,8 +25,9 @@ DECLARE_POINTER_TYPE(StyleSheet);
class Writer {
public:
/// Construct a writer that writes to the given output stream
Writer(OutputStream& output);
Writer(OutputStream& output, Version file_app_version);
/// Tell the reflection code we are not reading
static constexpr bool isReading = false;
static constexpr bool isWriting = true;
@@ -72,11 +73,11 @@ public:
// special behaviour
void handle(const GameP&);
void handle(const StyleSheetP&);
private:
// --------------------------------------------------- : Data
/// Indentation of the current block
int indentation;
private:
// --------------------------------------------------- : Data
/// Blocks opened to which nothing has been written
vector<const Char*> pending_opened;
+1
View File
@@ -76,6 +76,7 @@ String tr(const String&, const String& subcat, const String& key, DefaultLocaleF
#define _TITLE_(s) tr(LOCALE_CAT_TITLE, _(s))
/// A localized string for type names in scripts
#define _TYPE_(s) tr(LOCALE_CAT_TYPE, _(s))
#define _TYPE_V_(s) tr(LOCALE_CAT_TYPE, s )
/// A localized string for action names
#define _ACTION_(s) tr(LOCALE_CAT_ACTION, _(s))
/// A localized string for error messages