mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-10 04:57:00 -04:00
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
This commit is contained in:
@@ -52,6 +52,7 @@ menu:
|
|||||||
add card: &Add Card Ctrl+Enter
|
add card: &Add Card Ctrl+Enter
|
||||||
add cards: Add &Multiple Cards...
|
add cards: Add &Multiple Cards...
|
||||||
remove card: &Delete Selected Card
|
remove card: &Delete Selected Card
|
||||||
|
add card csv: Add Cards from CSV or TSV...
|
||||||
orientation: &Orientation
|
orientation: &Orientation
|
||||||
rotate 0: &Normal
|
rotate 0: &Normal
|
||||||
rotate 270: Rotated 90° &Clockwise
|
rotate 270: Rotated 90° &Clockwise
|
||||||
@@ -536,6 +537,12 @@ label:
|
|||||||
html template: Template:
|
html template: Template:
|
||||||
html export options: Export options
|
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
|
# Image slicer
|
||||||
original: Original:
|
original: Original:
|
||||||
result: Result:
|
result: Result:
|
||||||
@@ -723,6 +730,9 @@ title:
|
|||||||
export cancelled: Export Cancelled
|
export cancelled: Export Cancelled
|
||||||
export html: Export to HTML
|
export html: Export to HTML
|
||||||
save 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 replace
|
||||||
auto replaces: Auto Replace
|
auto replaces: Auto Replace
|
||||||
|
|
||||||
|
|||||||
@@ -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_color]] Convert any value to a [[type:color]]
|
||||||
| [[fun:to_image]] Convert any value to an [[type:image]]
|
| [[fun:to_image]] Convert any value to an [[type:image]]
|
||||||
| [[fun:to_date]] Convert any value to a [[type:date]]
|
| [[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
|
| [[fun:type_name]] Get the type of a value
|
||||||
|
|
||||||
! Numbers <<<
|
! Numbers <<<
|
||||||
|
|||||||
@@ -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"]
|
||||||
@@ -28,6 +28,7 @@ Fields are part of the [[file:style triangle]]:
|
|||||||
* @color@
|
* @color@
|
||||||
* @info@
|
* @info@
|
||||||
| @name@ [[type:string]] ''required'' Name of the field.
|
| @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.
|
| @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.
|
| @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?
|
| @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 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.
|
| @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.
|
| @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:
|
The @type@ determines what values of this field contain:
|
||||||
! Type Values contain Displayed as
|
! Type Values contain Displayed as
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ Such a package contains a [[file:format|data file]] called <tt>game</tt> that ha
|
|||||||
| @default set style@ [[type:indexmap]] of [[type:style]]s Default style for the set fields, can be overridden by the stylesheet.
|
| @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 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. <br/>If not set uses the @card list colors@ property of the first card field that has it.
|
| @card list color script@ [[type:script]] from fields Script that determines the color of an item in the card list. <br/>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. <br/>By default all card fields with 'show statistics' set to true are used.
|
| @statistics dimensions@ [[type:list]] of [[type:statistics dimension]]s from fields Dimensions for statistics, a dimension is roughly the same as an axis. <br/>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
|
| @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. <br/>By default all statistics dimensions are used.
|
Choices shown on the statistics panel. <br/>By default all statistics dimensions are used.
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ Field::~Field() {}
|
|||||||
|
|
||||||
void Field::initDependencies(Context& ctx, const Dependency& dep) const {
|
void Field::initDependencies(Context& ctx, const Dependency& dep) const {
|
||||||
sort_script.initDependencies(ctx, dep);
|
sort_script.initDependencies(ctx, dep);
|
||||||
|
construction_script.initDependencies(ctx, dep);
|
||||||
}
|
}
|
||||||
|
|
||||||
IMPLEMENT_REFLECTION(Field) {
|
IMPLEMENT_REFLECTION(Field) {
|
||||||
@@ -48,6 +49,7 @@ IMPLEMENT_REFLECTION(Field) {
|
|||||||
REFLECT(type);
|
REFLECT(type);
|
||||||
}
|
}
|
||||||
REFLECT(name);
|
REFLECT(name);
|
||||||
|
REFLECT(alt_names);
|
||||||
REFLECT_LOCALIZED(caption);
|
REFLECT_LOCALIZED(caption);
|
||||||
REFLECT_LOCALIZED(description); // FIXME: This field is both unused and uninitialized.
|
REFLECT_LOCALIZED(description); // FIXME: This field is both unused and uninitialized.
|
||||||
REFLECT_N("icon", icon_filename);
|
REFLECT_N("icon", icon_filename);
|
||||||
@@ -62,6 +64,7 @@ IMPLEMENT_REFLECTION(Field) {
|
|||||||
REFLECT(card_list_allow);
|
REFLECT(card_list_allow);
|
||||||
REFLECT_LOCALIZED(card_list_name);
|
REFLECT_LOCALIZED(card_list_name);
|
||||||
REFLECT(sort_script);
|
REFLECT(sort_script);
|
||||||
|
REFLECT(construction_script);
|
||||||
REFLECT_N("card_list_alignment", card_list_align);
|
REFLECT_N("card_list_alignment", card_list_align);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -44,8 +44,9 @@ public:
|
|||||||
|
|
||||||
size_t index; ///< Used by IndexMap
|
size_t index; ///< Used by IndexMap
|
||||||
String name; ///< Name of the field, for refering to it from scripts and files
|
String name; ///< Name of the field, for refering to it from scripts and files
|
||||||
|
vector<String> alt_names; ///< Other names this field might go by, mainly in CSV files
|
||||||
LocalizedString caption; ///< Caption for NativeLookEditor
|
LocalizedString caption; ///< Caption for NativeLookEditor
|
||||||
LocalizedString description;///< Description, used in status bar
|
LocalizedString description; ///< Description, used in status bar
|
||||||
String icon_filename; ///< Filename for an icon (for list of fields)
|
String icon_filename; ///< Filename for an icon (for list of fields)
|
||||||
bool editable; ///< Can values of this field be edited?
|
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 save_value; ///< Should values of this field be written to files? Can be false for script generated fields.
|
||||||
@@ -54,11 +55,12 @@ public:
|
|||||||
bool identifying; ///< Does this field give Card::identification()?
|
bool identifying; ///< Does this field give Card::identification()?
|
||||||
int card_list_column; ///< What column to use in the card list?
|
int card_list_column; ///< What column to use in the card list?
|
||||||
UInt card_list_width; ///< Width of the card list column (pixels).
|
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_visible; ///< Is this field shown in the card list?
|
||||||
bool card_list_allow; ///< Is this field allowed to appear 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.
|
LocalizedString card_list_name; ///< Name to use in card list.
|
||||||
Alignment card_list_align; ///< Alignment of the card list colummn.
|
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 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
|
Dependencies dependent_scripts; ///< Scripts that depend on values of this field
|
||||||
String package_relative_filename;
|
String package_relative_filename;
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ IMPLEMENT_REFLECTION(Game) {
|
|||||||
REFLECT_NO_SCRIPT(default_set_style);
|
REFLECT_NO_SCRIPT(default_set_style);
|
||||||
REFLECT_NO_SCRIPT(card_fields);
|
REFLECT_NO_SCRIPT(card_fields);
|
||||||
REFLECT_NO_SCRIPT(card_list_color_script);
|
REFLECT_NO_SCRIPT(card_list_color_script);
|
||||||
|
REFLECT_NO_SCRIPT(construction_script);
|
||||||
REFLECT_NO_SCRIPT(statistics_dimensions);
|
REFLECT_NO_SCRIPT(statistics_dimensions);
|
||||||
REFLECT_NO_SCRIPT(statistics_categories);
|
REFLECT_NO_SCRIPT(statistics_categories);
|
||||||
REFLECT_COMPAT(<308, "pack_item", pack_types);
|
REFLECT_COMPAT(<308, "pack_item", pack_types);
|
||||||
@@ -94,6 +95,28 @@ void Game::validate(Version v) {
|
|||||||
pack->select = SELECT_NO_REPLACE;
|
pack->select = SELECT_NO_REPLACE;
|
||||||
pack_types.push_back(pack);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Game::initCardListColorScript() {
|
void Game::initCardListColorScript() {
|
||||||
|
|||||||
@@ -42,12 +42,14 @@ public:
|
|||||||
IndexMap<FieldP,StyleP> default_set_style; ///< Default style for the set fields, because it is often the same
|
IndexMap<FieldP,StyleP> default_set_style; ///< Default style for the set fields, because it is often the same
|
||||||
vector<FieldP> card_fields; ///< Fields on each card
|
vector<FieldP> card_fields; ///< Fields on each card
|
||||||
OptionalScript card_list_color_script; ///< Script that determines the color of items in the card list
|
OptionalScript card_list_color_script; ///< Script that determines the color of items in the card list
|
||||||
|
OptionalScript construction_script; ///< Script applied as the last step of the new_card function
|
||||||
vector<StatsDimensionP> statistics_dimensions; ///< (Additional) statistics dimensions
|
vector<StatsDimensionP> statistics_dimensions; ///< (Additional) statistics dimensions
|
||||||
vector<StatsCategoryP> statistics_categories; ///< (Additional) statistics categories
|
vector<StatsCategoryP> statistics_categories; ///< (Additional) statistics categories
|
||||||
vector<PackTypeP> pack_types; ///< Types of random card packs to generate
|
vector<PackTypeP> pack_types; ///< Types of random card packs to generate
|
||||||
vector<WordListP> word_lists; ///< Word lists for editing with a drop down list
|
vector<WordListP> word_lists; ///< Word lists for editing with a drop down list
|
||||||
vector<AddCardsScriptP> add_cards_scripts; ///< Scripts for adding multiple cards to the set
|
vector<AddCardsScriptP> add_cards_scripts; ///< Scripts for adding multiple cards to the set
|
||||||
vector<AutoReplaceP> auto_replaces; ///< Things to autoreplace in textboxes
|
vector<AutoReplaceP> auto_replaces; ///< Things to autoreplace in textboxes
|
||||||
|
map<String,String> card_fields_alt_names; ///< Other names that fields might go by, for example in CSV files
|
||||||
|
|
||||||
bool has_keywords; ///< Does this game use keywords?
|
bool has_keywords; ///< Does this game use keywords?
|
||||||
OptionalScript keyword_match_script; ///< For the keyword editor
|
OptionalScript keyword_match_script; ///< For the keyword editor
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ StyleSheet::StyleSheet()
|
|||||||
StyleSheetP StyleSheet::byGameAndName(const Game& game, const String& name) {
|
StyleSheetP StyleSheet::byGameAndName(const Game& game, const String& name) {
|
||||||
/// Alternative stylesheets for game
|
/// Alternative stylesheets for game
|
||||||
static map<String, String> stylesheet_alternatives;
|
static map<String, String> stylesheet_alternatives;
|
||||||
String full_name = game.name() + _("-") + name + _(".mse-style");
|
String full_name = name;
|
||||||
|
if (!full_name.EndsWith(_(".mse-style"))) full_name = full_name + _(".mse-style");
|
||||||
|
if (!full_name.StartsWith(game.name() + _("-"))) full_name = game.name() + _("-") + full_name;
|
||||||
try {
|
try {
|
||||||
map<String, String>::const_iterator it = stylesheet_alternatives.find(full_name);
|
map<String, String>::const_iterator it = stylesheet_alternatives.find(full_name);
|
||||||
if (it != stylesheet_alternatives.end()) {
|
if (it != stylesheet_alternatives.end()) {
|
||||||
|
|||||||
@@ -0,0 +1,189 @@
|
|||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
//| 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) |
|
||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Includes
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <regex>
|
||||||
|
#include <util/prec.hpp>
|
||||||
|
#include <data/game.hpp>
|
||||||
|
#include <data/set.hpp>
|
||||||
|
#include <data/card.hpp>
|
||||||
|
#include <data/stylesheet.hpp>
|
||||||
|
#include <gui/add_csv_window.hpp>
|
||||||
|
#include <util/window_id.hpp>
|
||||||
|
#include <data/action/set.hpp>
|
||||||
|
#include <wx/statline.h>
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : AddCSV
|
||||||
|
|
||||||
|
AddCSVWindow::AddCSVWindow(Window* parent, const SetP& set, bool sizer)
|
||||||
|
: wxDialog(parent, wxID_ANY, _TITLE_("add card csv"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
|
||||||
|
, set(set)
|
||||||
|
{
|
||||||
|
// init controls
|
||||||
|
file_path = new wxTextCtrl(this, wxID_ANY, wxEmptyString);
|
||||||
|
file_browse = new wxButton(this, ID_CARD_ADD_CSV_BROWSE, _BUTTON_("browse"));
|
||||||
|
separator_type = new wxChoice(this, ID_CARD_ADD_CSV_SEP, wxDefaultPosition, wxDefaultSize, 0, nullptr);
|
||||||
|
separator_type->Clear();
|
||||||
|
separator_type->Append(_LABEL_("add card csv tab"));
|
||||||
|
separator_type->Append(_LABEL_("add card csv comma"));
|
||||||
|
separator_type->SetSelection(0);
|
||||||
|
setSeparatorType();
|
||||||
|
// init sizers
|
||||||
|
if (sizer) {
|
||||||
|
wxSizer* s = new wxBoxSizer(wxVERTICAL);
|
||||||
|
s->Add(new wxStaticText(this, -1, _LABEL_("add card csv sep")), 0, wxALL, 8);
|
||||||
|
s->Add(separator_type, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
|
||||||
|
s->Add(new wxStaticText(this, -1, _(" ") + _LABEL_("add card csv file")), 0, wxALL | (wxALL & ~wxTOP), 8);
|
||||||
|
s->Add(file_path, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
|
||||||
|
wxSizer* s2 = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
s2->Add(file_browse, 0, wxEXPAND | wxRIGHT, 8);
|
||||||
|
s2->Add(CreateButtonSizer(wxOK | wxCANCEL), 1, wxEXPAND, 8);
|
||||||
|
s->Add(s2, 0, wxEXPAND | wxALL, 12);
|
||||||
|
file_browse->SetFocus();
|
||||||
|
s->SetSizeHints(this);
|
||||||
|
SetSizer(s);
|
||||||
|
SetSize(500, 110);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddCSVWindow::setSeparatorType() {
|
||||||
|
int sel = separator_type->GetSelection();
|
||||||
|
if (sel == 0) separator = ' ';
|
||||||
|
else separator = ',';
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddCSVWindow::onSeparatorTypeChange(wxCommandEvent&) {
|
||||||
|
setSeparatorType();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddCSVWindow::onBrowseFiles(wxCommandEvent&) {
|
||||||
|
wxFileDialog* dlg = new wxFileDialog(this, _TITLE_("add card csv file"), settings.default_set_dir, wxEmptyString, _("CSV files|*.csv;*.tsv|All files (*.*)|*"), wxFD_OPEN);
|
||||||
|
if (dlg->ShowModal() == wxID_OK) {
|
||||||
|
file_path->SetValue(dlg->GetPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> AddCSVWindow::readCSVRow(const std::string& row) {
|
||||||
|
CSVState state = CSVState::UnquotedField;
|
||||||
|
std::vector<std::string> fields{ "" };
|
||||||
|
size_t f = 0; // index of the current field
|
||||||
|
for (char c : row) {
|
||||||
|
switch (state) {
|
||||||
|
case CSVState::UnquotedField:
|
||||||
|
if (c == separator) { // end of field
|
||||||
|
fields.push_back(""); f++;
|
||||||
|
}
|
||||||
|
else if (c == '"') {
|
||||||
|
state = CSVState::QuotedField;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fields[f].push_back(c);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CSVState::QuotedField:
|
||||||
|
switch (c) {
|
||||||
|
case '"': state = CSVState::QuotedQuote;
|
||||||
|
break;
|
||||||
|
default: fields[f].push_back(c);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CSVState::QuotedQuote:
|
||||||
|
if (c == separator) { // separator after closing quote
|
||||||
|
fields.push_back(""); f++;
|
||||||
|
state = CSVState::UnquotedField;
|
||||||
|
}
|
||||||
|
else if (c == '"') { // "" -> "
|
||||||
|
fields[f].push_back('"');
|
||||||
|
state = CSVState::QuotedField;
|
||||||
|
}
|
||||||
|
else { // end of quote
|
||||||
|
state = CSVState::UnquotedField;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// escape " { }
|
||||||
|
for (f = 0; f < fields.size(); ++f) {
|
||||||
|
fields[f] = std::regex_replace(fields[f], std::regex("\""), "\\\"");
|
||||||
|
fields[f] = std::regex_replace(fields[f], std::regex("\\{"), "\\{");
|
||||||
|
fields[f] = std::regex_replace(fields[f], std::regex("\\}"), "\\}");
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddCSVWindow::onOk(wxCommandEvent&) {
|
||||||
|
/// Perform the import
|
||||||
|
// Read the file, put it into a table
|
||||||
|
auto in = std::ifstream(file_path->GetValue().ToStdString());
|
||||||
|
if (in.fail()) {
|
||||||
|
queue_message(MESSAGE_ERROR, _ERROR_("add card csv file not found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::vector<std::vector<std::string>> table;
|
||||||
|
std::string row;
|
||||||
|
while (!in.eof()) {
|
||||||
|
std::getline(in, row);
|
||||||
|
if (in.bad() || in.fail()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
auto fields = readCSVRow(row);
|
||||||
|
table.push_back(fields);
|
||||||
|
}
|
||||||
|
// ensure table is square
|
||||||
|
int count = table[0].size();
|
||||||
|
for (int y = 1; y < table.size(); ++y) {
|
||||||
|
if (table[y].size() != count) {
|
||||||
|
queue_message(MESSAGE_ERROR, _ERROR_1_("add card csv file malformed", wxString::Format(wxT("%i"), y+1)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// produce script from table
|
||||||
|
std::ostringstream stream;
|
||||||
|
stream << "[";
|
||||||
|
for (int y = 1; y < table.size(); ++y) {
|
||||||
|
stream << "new_card([";
|
||||||
|
for (int x = 0; x < count; ++x) {
|
||||||
|
stream << "\"" << table[0][x] << "\": \"" << table[y][x] << "\"";
|
||||||
|
if (x < count - 1) stream << ",";
|
||||||
|
}
|
||||||
|
stream << "])";
|
||||||
|
if (y < table.size() - 1) stream << ",";
|
||||||
|
}
|
||||||
|
stream << "]";
|
||||||
|
String string(stream.str().c_str(), wxConvUTF8);
|
||||||
|
ScriptP script = parse(string, nullptr, false);
|
||||||
|
Context& ctx = set->getContext();
|
||||||
|
ScriptValueP result = script->eval(ctx, false);
|
||||||
|
// create cards
|
||||||
|
vector<CardP> cards;
|
||||||
|
ScriptValueP it = result->makeIterator();
|
||||||
|
while (ScriptValueP item = it->next()) {
|
||||||
|
CardP card = from_script<CardP>(item);
|
||||||
|
// is this a new card?
|
||||||
|
if (contains(set->cards, card) || contains(cards, card)) {
|
||||||
|
// make copy
|
||||||
|
card = make_intrusive<Card>(*card);
|
||||||
|
}
|
||||||
|
cards.push_back(card);
|
||||||
|
}
|
||||||
|
// add to set
|
||||||
|
if (!cards.empty()) {
|
||||||
|
// TODO: change the name of the action somehow
|
||||||
|
set->actions.addAction(make_unique<AddCardAction>(ADD, *set, cards));
|
||||||
|
}
|
||||||
|
// Done
|
||||||
|
EndModal(wxID_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
BEGIN_EVENT_TABLE(AddCSVWindow, wxDialog)
|
||||||
|
EVT_BUTTON(wxID_OK, AddCSVWindow::onOk)
|
||||||
|
EVT_BUTTON(ID_CARD_ADD_CSV_BROWSE, AddCSVWindow::onBrowseFiles)
|
||||||
|
EVT_CHOICE(ID_CARD_ADD_CSV_SEP, AddCSVWindow::onSeparatorTypeChange)
|
||||||
|
END_EVENT_TABLE()
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
//| 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>
|
||||||
|
|
||||||
|
DECLARE_POINTER_TYPE(Set);
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : AddCSVWindow
|
||||||
|
|
||||||
|
/// A window for selecting a subset of the cards from a set,
|
||||||
|
/** and selecting a link relation type.
|
||||||
|
/** this is used when linking cards
|
||||||
|
*/
|
||||||
|
class AddCSVWindow : public wxDialog {
|
||||||
|
public:
|
||||||
|
AddCSVWindow(Window* parent, const SetP& set, bool sizer);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
DECLARE_EVENT_TABLE();
|
||||||
|
|
||||||
|
wxChoice* separator_type;
|
||||||
|
wxTextCtrl* file_path;
|
||||||
|
wxButton* file_browse;
|
||||||
|
SetP set;
|
||||||
|
char separator;
|
||||||
|
|
||||||
|
std::vector<std::string> readCSVRow(const std::string& row);
|
||||||
|
|
||||||
|
void setSeparatorType();
|
||||||
|
|
||||||
|
void onSeparatorTypeChange(wxCommandEvent&);
|
||||||
|
|
||||||
|
void onBrowseFiles(wxCommandEvent&);
|
||||||
|
|
||||||
|
void onOk(wxCommandEvent&);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class CSVState {
|
||||||
|
UnquotedField,
|
||||||
|
QuotedField,
|
||||||
|
QuotedQuote
|
||||||
|
};
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <gui/control/card_list_column_select.hpp>
|
#include <gui/control/card_list_column_select.hpp>
|
||||||
#include <gui/set/window.hpp> // for sorting all cardlists in a window
|
#include <gui/set/window.hpp> // for sorting all cardlists in a window
|
||||||
#include <gui/util.hpp>
|
#include <gui/util.hpp>
|
||||||
|
#include <gui/add_csv_window.hpp>
|
||||||
#include <data/game.hpp>
|
#include <data/game.hpp>
|
||||||
#include <data/field.hpp>
|
#include <data/field.hpp>
|
||||||
#include <data/field/choice.hpp>
|
#include <data/field/choice.hpp>
|
||||||
@@ -181,6 +182,15 @@ bool CardListBase::doDelete() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CardListBase::doAddCSV() {
|
||||||
|
AddCSVWindow wnd(this, set, true);
|
||||||
|
if (wnd.ShowModal() == wxID_OK) {
|
||||||
|
// The actual adding is done in this window's onOk function
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------- : CardListBase : Building the list
|
// ----------------------------------------------------------------------------- : CardListBase : Building the list
|
||||||
|
|
||||||
// Comparison object for comparing cards
|
// Comparison object for comparing cards
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ public:
|
|||||||
bool doCopy() override;
|
bool doCopy() override;
|
||||||
bool doPaste() override;
|
bool doPaste() override;
|
||||||
bool doDelete() override;
|
bool doDelete() override;
|
||||||
|
bool doAddCSV();
|
||||||
|
|
||||||
// --------------------------------------------------- : Set actions
|
// --------------------------------------------------- : Set actions
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ CardsPanel::CardsPanel(Window* parent, int id)
|
|||||||
// NOTE: space after "Del" prevents wx from making del an accellerator
|
// NOTE: space after "Del" prevents wx from making del an accellerator
|
||||||
// otherwise we delete a card when delete is pressed inside the editor
|
// otherwise we delete a card when delete is pressed inside the editor
|
||||||
// Adding a space never hurts, please keep it just to be safe.
|
// Adding a space never hurts, please keep it just to be safe.
|
||||||
|
add_menu_item(menuCard, ID_CARD_ADD_CSV, "card_add_multiple", _MENU_("add card csv") + _(" "), _HELP_("add card csv"));
|
||||||
add_menu_item(menuCard, ID_CARD_REMOVE, "card_del", _MENU_("remove card")+_(" "), _HELP_("remove card"));
|
add_menu_item(menuCard, ID_CARD_REMOVE, "card_del", _MENU_("remove card")+_(" "), _HELP_("remove card"));
|
||||||
menuCard->AppendSeparator();
|
menuCard->AppendSeparator();
|
||||||
auto menuRotate = new wxMenu();
|
auto menuRotate = new wxMenu();
|
||||||
@@ -311,6 +312,9 @@ void CardsPanel::onCommand(int id) {
|
|||||||
case ID_CARD_ADD:
|
case ID_CARD_ADD:
|
||||||
set->actions.addAction(make_unique<AddCardAction>(*set));
|
set->actions.addAction(make_unique<AddCardAction>(*set));
|
||||||
break;
|
break;
|
||||||
|
case ID_CARD_ADD_CSV:
|
||||||
|
card_list->doAddCSV();
|
||||||
|
break;
|
||||||
case ID_CARD_REMOVE:
|
case ID_CARD_REMOVE:
|
||||||
card_list->doDelete();
|
card_list->doDelete();
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -684,6 +684,30 @@ SCRIPT_FUNCTION(random_select_many) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SCRIPT_FUNCTION(make_map) {
|
||||||
|
SCRIPT_PARAM(ScriptValueP, keys);
|
||||||
|
SCRIPT_PARAM(ScriptValueP, values);
|
||||||
|
ScriptValueP keys_it = keys->makeIterator();
|
||||||
|
ScriptValueP key;
|
||||||
|
ScriptValueP values_it = values->makeIterator();
|
||||||
|
ScriptValueP value;
|
||||||
|
ScriptCustomCollectionP map = make_intrusive<ScriptCustomCollection>();
|
||||||
|
while (key = keys_it->next()) {
|
||||||
|
if (key == script_nil) continue;
|
||||||
|
if (value = values_it->next()) {
|
||||||
|
map->key_value[key->toString()] = value;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
queue_message(MESSAGE_WARNING, "More keys than values given in function make_map!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (value = values_it->next()) {
|
||||||
|
queue_message(MESSAGE_WARNING, "More values than keys given in function make_map!");
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
SCRIPT_FUNCTION(get_card_styling) {
|
SCRIPT_FUNCTION(get_card_styling) {
|
||||||
SCRIPT_PARAM_C(ScriptValueP, input);
|
SCRIPT_PARAM_C(ScriptValueP, input);
|
||||||
SCRIPT_PARAM_C(ScriptValueP, set);
|
SCRIPT_PARAM_C(ScriptValueP, set);
|
||||||
@@ -795,6 +819,7 @@ void init_script_basic_functions(Context& ctx) {
|
|||||||
ctx.setVariable(_("to_date"), script_to_date);
|
ctx.setVariable(_("to_date"), script_to_date);
|
||||||
ctx.setVariable(_("to_code"), script_to_code);
|
ctx.setVariable(_("to_code"), script_to_code);
|
||||||
ctx.setVariable(_("type_name"), script_type_name);
|
ctx.setVariable(_("type_name"), script_type_name);
|
||||||
|
ctx.setVariable(_("make_map"), script_make_map);
|
||||||
ctx.setVariable(_("get_card_styling"), script_get_card_styling);
|
ctx.setVariable(_("get_card_styling"), script_get_card_styling);
|
||||||
ctx.setVariable(_("get_card_stylesheet"), script_get_card_stylesheet);
|
ctx.setVariable(_("get_card_stylesheet"), script_get_card_stylesheet);
|
||||||
// math
|
// math
|
||||||
|
|||||||
@@ -16,42 +16,139 @@
|
|||||||
#include <data/field/color.hpp>
|
#include <data/field/color.hpp>
|
||||||
#include <data/field/image.hpp>
|
#include <data/field/image.hpp>
|
||||||
#include <data/game.hpp>
|
#include <data/game.hpp>
|
||||||
|
#include <data/stylesheet.hpp>
|
||||||
#include <data/card.hpp>
|
#include <data/card.hpp>
|
||||||
#include <util/error.hpp>
|
#include <util/error.hpp>
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Helper functions
|
||||||
|
|
||||||
|
static Value* get_container(GameP& game, CardP& card, String key_name) {
|
||||||
|
// find value container to update
|
||||||
|
IndexMap<FieldP, ValueP>::const_iterator value_it = card->data.find(key_name);
|
||||||
|
if (value_it == card->data.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (value_it == card->data.end()) {
|
||||||
|
throw ScriptError(_ERROR_1_("no field with name", key_name));
|
||||||
|
}
|
||||||
|
return value_it->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
else if (ChoiceValue* cvalue = dynamic_cast<ChoiceValue*>(container)) {
|
||||||
|
cvalue->value = value->toString();
|
||||||
|
}
|
||||||
|
else if (PackageChoiceValue* pvalue = dynamic_cast<PackageChoiceValue*>(container)) {
|
||||||
|
pvalue->package_name = value->toString();
|
||||||
|
}
|
||||||
|
else if (ColorValue* cvalue = dynamic_cast<ColorValue*>(container)) {
|
||||||
|
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(), "");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw ScriptError(_ERROR_1_("can't set value", key_name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool set_builtin_container(GameP& game, CardP& card, ScriptValueP& value, String key_name) {
|
||||||
|
// 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")) {
|
||||||
|
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());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// else if (key_name == _("id") || key_name == _("multiverse_id")) {
|
||||||
|
// card->id = value->toString();
|
||||||
|
// return true;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//styling_data;
|
||||||
|
//linked_card;
|
||||||
|
//linked_relation_1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------- : new_card
|
// ----------------------------------------------------------------------------- : new_card
|
||||||
|
|
||||||
SCRIPT_FUNCTION(new_card) {
|
SCRIPT_FUNCTION(new_card) {
|
||||||
SCRIPT_PARAM(GameP, game);
|
SCRIPT_PARAM(GameP, game);
|
||||||
|
// create a new card object
|
||||||
CardP new_card = make_intrusive<Card>(*game);
|
CardP new_card = make_intrusive<Card>(*game);
|
||||||
// set field values
|
// iterate on the given key/value pairs
|
||||||
SCRIPT_PARAM(ScriptValueP, input);
|
SCRIPT_PARAM(ScriptValueP, input);
|
||||||
ScriptValueP it = input->makeIterator();
|
ScriptValueP it = input->makeIterator();
|
||||||
ScriptValueP key;
|
ScriptValueP key;
|
||||||
while (ScriptValueP v = it->next(&key)) {
|
while (ScriptValueP value = it->next(&key)) {
|
||||||
assert(key);
|
assert(key);
|
||||||
String name = key->toString();
|
String key_name = key->toString();
|
||||||
// find value to update
|
// check if the given value is for a built-in field
|
||||||
IndexMap<FieldP,ValueP>::const_iterator value_it = new_card->data.find(name);
|
if (set_builtin_container(game, new_card, value, key_name)) continue;
|
||||||
if (value_it == new_card->data.end()) {
|
// find the field value (container) that corresponds to the given value
|
||||||
throw ScriptError(_ERROR_1_("no field with name", name));
|
Value* container = get_container(game, new_card, key_name);
|
||||||
|
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
|
||||||
|
if (field->construction_script) {
|
||||||
|
ScriptValueP ctx_value = ctx.getVariableOpt(SCRIPT_VAR_value);
|
||||||
|
ScriptValueP ctx_card = ctx.getVariableOpt(SCRIPT_VAR_card);
|
||||||
|
ctx.setVariable(SCRIPT_VAR_value, value);
|
||||||
|
ctx.setVariable(SCRIPT_VAR_card, to_script(new_card));
|
||||||
|
ScriptValueP script_input = field->construction_script.invoke(ctx, true);
|
||||||
|
// iterate on the key/value pairs given by the script
|
||||||
|
ScriptValueP script_it = script_input->makeIterator();
|
||||||
|
ScriptValueP script_key;
|
||||||
|
while (ScriptValueP script_value = script_it->next(&script_key)) {
|
||||||
|
assert(script_key);
|
||||||
|
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)) continue;
|
||||||
|
// find the field value that corresponds to the script value
|
||||||
|
Value* script_container = get_container(game, new_card, script_key_name);
|
||||||
|
// set the field value to the script value
|
||||||
|
set_container(script_container, script_value, script_key_name);
|
||||||
}
|
}
|
||||||
Value* value = value_it->get();
|
// restore old value and card context variables
|
||||||
// set the value
|
if (ctx_value) ctx.setVariable(SCRIPT_VAR_value, ctx_value);
|
||||||
if (TextValue* tvalue = dynamic_cast<TextValue*>(value)) {
|
if (ctx_card) ctx.setVariable(SCRIPT_VAR_card, ctx_card);
|
||||||
tvalue->value = v->toString();
|
|
||||||
} else if (ChoiceValue* cvalue = dynamic_cast<ChoiceValue*>(value)) {
|
|
||||||
cvalue->value = v->toString();
|
|
||||||
} else if (PackageChoiceValue* pvalue = dynamic_cast<PackageChoiceValue*>(value)) {
|
|
||||||
pvalue->package_name = v->toString();
|
|
||||||
} else if (ColorValue* cvalue = dynamic_cast<ColorValue*>(value)) {
|
|
||||||
cvalue->value = v->toColor();
|
|
||||||
} else if (ImageValue* ivalue = dynamic_cast<ImageValue*>(value)) {
|
|
||||||
wxFileName fname( static_cast<ExternalImage*>(v.get())->toString() );
|
|
||||||
ivalue->filename = LocalFileName::fromReadString( fname.GetName(), "");
|
|
||||||
} else {
|
|
||||||
throw ScriptError(_ERROR_1_("can't set value", name));
|
|
||||||
}
|
}
|
||||||
|
// if the field has no construction script, simply set the field value to the given value
|
||||||
|
else {
|
||||||
|
set_container(container, value, key_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if the game has a construction script, set the card context variable to be this card, run script
|
||||||
|
if (game->construction_script) {
|
||||||
|
ScriptValueP ctx_card = ctx.getVariableOpt(SCRIPT_VAR_card);
|
||||||
|
ctx.setVariable(SCRIPT_VAR_card, to_script(new_card));
|
||||||
|
ScriptValueP script_input = game->construction_script.invoke(ctx, true);
|
||||||
|
// iterate on the key/value pairs given by the script
|
||||||
|
ScriptValueP script_it = script_input->makeIterator();
|
||||||
|
ScriptValueP script_key;
|
||||||
|
while (ScriptValueP script_value = script_it->next(&script_key)) {
|
||||||
|
assert(script_key);
|
||||||
|
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)) continue;
|
||||||
|
// find the field value that corresponds to the script value
|
||||||
|
Value* script_container = get_container(game, new_card, script_key_name);
|
||||||
|
// set the field value to the script value
|
||||||
|
set_container(script_container, script_value, script_key_name);
|
||||||
|
}
|
||||||
|
// restore old context card
|
||||||
|
if (ctx_card) ctx.setVariable(SCRIPT_VAR_card, ctx_card);
|
||||||
}
|
}
|
||||||
SCRIPT_RETURN(new_card);
|
SCRIPT_RETURN(new_card);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,6 +137,15 @@ void uncanonical_name_form_in_place(String& str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String unified_form(String& str) {
|
||||||
|
str = trim(str);
|
||||||
|
for (String::iterator it = str.begin(); it != str.end(); ++it) {
|
||||||
|
if (*it == ' ') *it = '_';
|
||||||
|
else *it = toLower(*it);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
String name_to_caption(const String& str) {
|
String name_to_caption(const String& str) {
|
||||||
String ret;
|
String ret;
|
||||||
ret.reserve(str.size());
|
ret.reserve(str.size());
|
||||||
|
|||||||
@@ -243,6 +243,9 @@ inline String uncanonical_name_form(String s) {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert a field name to canonical form, then to lower case, then trim it
|
||||||
|
String unified_form(String&);
|
||||||
|
|
||||||
/// Convert a field name to a string that can be shown to the user
|
/// Convert a field name to a string that can be shown to the user
|
||||||
String name_to_caption(const String&);
|
String name_to_caption(const String&);
|
||||||
|
|
||||||
|
|||||||
@@ -108,6 +108,9 @@ enum ChildMenuID {
|
|||||||
ID_CARD_ROTATE_270,
|
ID_CARD_ROTATE_270,
|
||||||
// CardList
|
// CardList
|
||||||
ID_SELECT_COLUMNS,
|
ID_SELECT_COLUMNS,
|
||||||
|
ID_CARD_ADD_CSV,
|
||||||
|
ID_CARD_ADD_CSV_SEP,
|
||||||
|
ID_CARD_ADD_CSV_BROWSE,
|
||||||
|
|
||||||
// Keyword menu
|
// Keyword menu
|
||||||
ID_KEYWORD_ADD = 6101,
|
ID_KEYWORD_ADD = 6101,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ $built_in_functions = array(
|
|||||||
'to_color' =>'',
|
'to_color' =>'',
|
||||||
'to_image' =>'',
|
'to_image' =>'',
|
||||||
'to_date' =>'',
|
'to_date' =>'',
|
||||||
|
'make_map' =>'',
|
||||||
// numbers
|
// numbers
|
||||||
'abs' =>'',
|
'abs' =>'',
|
||||||
'random_int' =>'',
|
'random_int' =>'',
|
||||||
|
|||||||
Reference in New Issue
Block a user