From 87fbc0e80e01c7f4e148fed2abcc41f0ab5d9cf8 Mon Sep 17 00:00:00 2001
From: GenevensiS <66968533+G-e-n-e-v-e-n-s-i-S@users.noreply.github.com>
Date: Mon, 9 Jun 2025 04:53:33 +0200
Subject: [PATCH] Implement CSV / TSV import (#45)
- add csv/tsv importer
- add `make_map` script function
- add `alt name` field property
- add `construction script` field property
- add `construction script` game property
---
data/en.mse-locale/locale | 10 +
doc/function/index.txt | 1 +
doc/function/make_map.txt | 17 ++
doc/type/field.txt | 5 +
doc/type/game.txt | 1 +
src/data/field.cpp | 3 +
src/data/field.hpp | 40 ++--
src/data/game.cpp | 25 ++-
src/data/game.hpp | 22 +-
src/data/stylesheet.cpp | 4 +-
src/gui/add_csv_window.cpp | 189 ++++++++++++++++++
src/gui/add_csv_window.hpp | 50 +++++
src/gui/control/card_list.cpp | 12 +-
src/gui/control/card_list.hpp | 3 +-
src/gui/set/cards_panel.cpp | 4 +
src/script/functions/basic.cpp | 27 ++-
src/script/functions/construction.cpp | 153 +++++++++++---
src/util/string.cpp | 9 +
src/util/string.hpp | 5 +-
src/util/window_id.hpp | 3 +
.../drupal/mse-drupal-modules/highlight.inc | 1 +
21 files changed, 521 insertions(+), 63 deletions(-)
create mode 100644 doc/function/make_map.txt
create mode 100644 src/gui/add_csv_window.cpp
create mode 100644 src/gui/add_csv_window.hpp
diff --git a/data/en.mse-locale/locale b/data/en.mse-locale/locale
index e98ccb86..1964ccbb 100644
--- a/data/en.mse-locale/locale
+++ b/data/en.mse-locale/locale
@@ -52,6 +52,7 @@ menu:
add card: &Add Card Ctrl+Enter
add cards: Add &Multiple Cards...
remove card: &Delete Selected Card
+ add card csv: Add Cards from CSV or TSV...
orientation: &Orientation
rotate 0: &Normal
rotate 270: Rotated 90° &Clockwise
@@ -536,6 +537,12 @@ label:
html template: Template:
html export options: Export options
+ # CSV import
+ add card csv sep: Separator:
+ add card csv tab: Tab
+ add card csv comma: Comma
+ add card csv file: CSV or TSV file path:
+
# Image slicer
original: Original:
result: Result:
@@ -723,6 +730,9 @@ title:
export cancelled: Export Cancelled
export html: Export to HTML
save html: Export to HTML
+ # import
+ add card csv: Add Cards from CSV or TSV file
+ add card csv file: Open CSV or TSV file
# auto replace
auto replaces: Auto Replace
diff --git a/doc/function/index.txt b/doc/function/index.txt
index 875b83c4..b4fa5497 100644
--- a/doc/function/index.txt
+++ b/doc/function/index.txt
@@ -11,6 +11,7 @@ These functions are built into the program, other [[type:function]]s can be defi
| [[fun:to_color]] Convert any value to a [[type:color]]
| [[fun:to_image]] Convert any value to an [[type:image]]
| [[fun:to_date]] Convert any value to a [[type:date]]
+| [[fun:make_map]] Create a [[type:map]] from two [[type:list]]s
| [[fun:type_name]] Get the type of a value
! Numbers <<<
diff --git a/doc/function/make_map.txt b/doc/function/make_map.txt
new file mode 100644
index 00000000..57b6e624
--- /dev/null
+++ b/doc/function/make_map.txt
@@ -0,0 +1,17 @@
+Function: make_map
+
+--Usage--
+> make_map(keys: some_list, values: some_list)
+
+Creates a new map. Converts the elements in keys to strings, and uses them as keys for the elements in values.
+
+Produces a warning if keys and values are not of the same size.
+nil keys will be ignored.
+
+--Parameters--
+! Parameter Type Description
+| @keys@ [[type:list]] List of keys.
+| @values@ [[type:list]] List of values.
+
+--Examples--
+> make_map(keys:["apple", "durian", 3], values:["good", "bad", "not edible"]) == ["apple":"good", "durian":"bad", "3":"not edible"]
diff --git a/doc/type/field.txt b/doc/type/field.txt
index eefa4a78..cb88089c 100644
--- a/doc/type/field.txt
+++ b/doc/type/field.txt
@@ -28,6 +28,7 @@ Fields are part of the [[file:style triangle]]:
* @color@
* @info@
| @name@ [[type:string]] ''required'' Name of the field.
+| @alt names@ [[type:list]] of [[type:string]] Possible alternate names for this field, mainly to look for it when importing CSV files. They are trimmed, made case insensitive, and don't distinguish between spaces and underscores.
| @description@ [[type:localized string]] @""@ Description of the field, shown in the status bar when the mouse is over the field.
| @icon@ [[type:filename]] Filename of an icon for this field, used for automatically generated [[type:statistics category]]s.
| @editable@ [[type:boolean]] @true@ Can values of this field be edited?
@@ -42,6 +43,10 @@ Fields are part of the [[file:style triangle]]:
| @card list name@ [[type:localized string]] field name Alternate name to use for the card list, for example an abbreviation.
| @card list alignment@ [[type:alignment]] @left@ Alignment of the card list column.
| @sort script@ [[type:script]] Alternate way to sort the card list when using this column to sort the list.
+| @construction script@ [[type:script]] Script applied to the value given when creating a card with the new_card function.
+ For example, the pt field should not be initialized directly, since it is a combination of the power field and toughness field.
+ So if a value is given for pt, it must be redirected to power and toughness like so: {split := split_text(value, match:"/"); [power:split[0], toughness:split[1]]}.
+ The script must return a map from field names to values. Use the make_map function to dynamically create maps.
The @type@ determines what values of this field contain:
! Type Values contain Displayed as
diff --git a/doc/type/game.txt b/doc/type/game.txt
index ce2d5088..4aaf4bd4 100644
--- a/doc/type/game.txt
+++ b/doc/type/game.txt
@@ -33,6 +33,7 @@ Such a package contains a [[file:format|data file]] called game that ha
| @default set style@ [[type:indexmap]] of [[type:style]]s Default style for the set fields, can be overridden by the stylesheet.
| @card fields@ [[type:list]] of [[type:field]]s Fields for each card.
| @card list color script@ [[type:script]] from fields Script that determines the color of an item in the card list.
If not set uses the @card list colors@ property of the first card field that has it.
+| @construction script@ [[type:script]] Script that is applied as the last step in the creation of a card in the new_card function. Must return a map from field names to values. Use the make_map function to dynamically create maps.
| @statistics dimensions@ [[type:list]] of [[type:statistics dimension]]s from fields Dimensions for statistics, a dimension is roughly the same as an axis.
By default all card fields with 'show statistics' set to true are used.
| @statistics categories@ [[type:list]] of [[type:statistics category]]s from dimensions DOC_MSE_VERSION: not used since 0.3.6
Choices shown on the statistics panel.
By default all statistics dimensions are used.
diff --git a/src/data/field.cpp b/src/data/field.cpp
index 36536291..4b1430ab 100644
--- a/src/data/field.cpp
+++ b/src/data/field.cpp
@@ -40,6 +40,7 @@ Field::~Field() {}
void Field::initDependencies(Context& ctx, const Dependency& dep) const {
sort_script.initDependencies(ctx, dep);
+ construction_script.initDependencies(ctx, dep);
}
IMPLEMENT_REFLECTION(Field) {
@@ -48,6 +49,7 @@ IMPLEMENT_REFLECTION(Field) {
REFLECT(type);
}
REFLECT(name);
+ REFLECT(alt_names);
REFLECT_LOCALIZED(caption);
REFLECT_LOCALIZED(description); // FIXME: This field is both unused and uninitialized.
REFLECT_N("icon", icon_filename);
@@ -62,6 +64,7 @@ IMPLEMENT_REFLECTION(Field) {
REFLECT(card_list_allow);
REFLECT_LOCALIZED(card_list_name);
REFLECT(sort_script);
+ REFLECT(construction_script);
REFLECT_N("card_list_alignment", card_list_align);
}
diff --git a/src/data/field.hpp b/src/data/field.hpp
index ba94022f..e11e1b1e 100644
--- a/src/data/field.hpp
+++ b/src/data/field.hpp
@@ -42,25 +42,27 @@ public:
Field();
virtual ~Field();
- size_t index; ///< Used by IndexMap
- String name; ///< Name of the field, for refering to it from scripts and files
- LocalizedString caption; ///< Caption for NativeLookEditor
- LocalizedString description;///< Description, used in status bar
- String icon_filename; ///< Filename for an icon (for list of fields)
- bool editable; ///< Can values of this field be edited?
- bool save_value; ///< Should values of this field be written to files? Can be false for script generated fields.
- bool show_statistics; ///< Should this field appear as a group by choice in the statistics panel?
- int position_hint; ///< Position in the statistics list
- bool identifying; ///< Does this field give Card::identification()?
- int card_list_column; ///< What column to use in the card list?
- UInt card_list_width; ///< Width of the card list column (pixels).
- bool card_list_visible;///< Is this field shown in the card list?
- bool card_list_allow; ///< Is this field allowed to appear in the card list?
- LocalizedString card_list_name; ///< Name to use in card list.
- Alignment card_list_align; ///< Alignment of the card list colummn.
- OptionalScript sort_script; ///< The script to use when sorting this, if not the value.
- Dependencies dependent_scripts; ///< Scripts that depend on values of this field
- String package_relative_filename;
+ size_t index; ///< Used by IndexMap
+ String name; ///< Name of the field, for refering to it from scripts and files
+ vector alt_names; ///< Other names this field might go by, mainly in CSV files
+ LocalizedString caption; ///< Caption for NativeLookEditor
+ LocalizedString description; ///< Description, used in status bar
+ String icon_filename; ///< Filename for an icon (for list of fields)
+ bool editable; ///< Can values of this field be edited?
+ bool save_value; ///< Should values of this field be written to files? Can be false for script generated fields.
+ bool show_statistics; ///< Should this field appear as a group by choice in the statistics panel?
+ int position_hint; ///< Position in the statistics list
+ bool identifying; ///< Does this field give Card::identification()?
+ int card_list_column; ///< What column to use in the card list?
+ UInt card_list_width; ///< Width of the card list column (pixels).
+ bool card_list_visible; ///< Is this field shown in the card list?
+ bool card_list_allow; ///< Is this field allowed to appear in the card list?
+ LocalizedString card_list_name; ///< Name to use in card list.
+ Alignment card_list_align; ///< Alignment of the card list colummn.
+ OptionalScript sort_script; ///< The script to use when sorting this, if not the value.
+ OptionalScript construction_script; ///< The script to apply to the supplied value, when creating a new card.
+ Dependencies dependent_scripts; ///< Scripts that depend on values of this field
+ String package_relative_filename;
/// Creates a new Value corresponding to this Field
virtual ValueP newValue() = 0;
diff --git a/src/data/game.cpp b/src/data/game.cpp
index 18c7882b..09edff55 100644
--- a/src/data/game.cpp
+++ b/src/data/game.cpp
@@ -49,6 +49,7 @@ IMPLEMENT_REFLECTION(Game) {
REFLECT_NO_SCRIPT(default_set_style);
REFLECT_NO_SCRIPT(card_fields);
REFLECT_NO_SCRIPT(card_list_color_script);
+ REFLECT_NO_SCRIPT(construction_script);
REFLECT_NO_SCRIPT(statistics_dimensions);
REFLECT_NO_SCRIPT(statistics_categories);
REFLECT_COMPAT(<308, "pack_item", pack_types);
@@ -93,6 +94,28 @@ void Game::validate(Version v) {
pack->filter = OptionalScript(_("true"));
pack->select = SELECT_NO_REPLACE;
pack_types.push_back(pack);
+ }
+ // alternate card field names map
+ for (auto it = card_fields.begin(); it != card_fields.end(); ++it) {
+ FieldP field = *it;
+ String unified_name = unified_form(field->name);
+ if (card_fields_alt_names.count(unified_name)) {
+ queue_message(MESSAGE_WARNING, _("Duplicate alternate card field name: ") + unified_name);
+ }
+ else {
+ card_fields_alt_names.emplace(unified_name, field->name);
+ }
+ //String column_name = field->card_list_name.get();
+ //card_fields_alt_names.emplace(unified_form(column_name), field->name);
+ for (auto it2 = field->alt_names.begin(); it2 != field->alt_names.end(); ++it2) {
+ unified_name = unified_form(*it2);
+ if (card_fields_alt_names.count(unified_name)) {
+ queue_message(MESSAGE_WARNING, _("Duplicate alternate card field name: ") + unified_name);
+ }
+ else {
+ card_fields_alt_names.emplace(unified_name, field->name);
+ }
+ }
}
}
@@ -114,7 +137,7 @@ void Game::initCardListColorScript() {
return;
}
}
-}
+}
// special behaviour of reading/writing GamePs: only read/write the name
diff --git a/src/data/game.hpp b/src/data/game.hpp
index c34c3ede..07b6dff7 100644
--- a/src/data/game.hpp
+++ b/src/data/game.hpp
@@ -12,7 +12,7 @@
#include
#include