mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-10 04:57:00 -04:00
0baa73ae7d
- dimensions_of should now work in all contexts - import_image now works in style files, although should be avoided, since it reloads the file every time the code in run, it should be used in one shot scripts, like import scripts and bulk modification scripts - process_english_hints now correctly processes <singular> and <plural> tags
392 lines
14 KiB
C++
392 lines
14 KiB
C++
//+----------------------------------------------------------------------------+
|
|
//| Description: Magic Set Editor - Program to make card games |
|
|
//| Copyright: (C) Twan van Laarhoven and the other MSE developers |
|
|
//| License: GNU General Public License 2 or later (see file COPYING) |
|
|
//+----------------------------------------------------------------------------+
|
|
|
|
// ----------------------------------------------------------------------------- : Includes
|
|
|
|
#include <util/prec.hpp>
|
|
#include <script/functions/english_exceptions.cpp>
|
|
#include <script/functions/functions.hpp>
|
|
#include <script/functions/util.hpp>
|
|
#include <util/tagged_string.hpp>
|
|
#include <util/error.hpp>
|
|
|
|
// ----------------------------------------------------------------------------- : Util
|
|
|
|
bool is_vowel(Char c) {
|
|
return c == _('a') || c == _('e') || c == _('i') || c == _('o') || c == _('u')
|
|
|| c == _('A') || c == _('E') || c == _('I') || c == _('O') || c == _('U');
|
|
}
|
|
inline bool is_constant(Char c) {
|
|
return !is_vowel(c);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------- : Numbers
|
|
|
|
/// Write a number using words, for example 23 -> "twenty-three"
|
|
String english_number(int i) {
|
|
switch (i) {
|
|
case 0: return _("zero");
|
|
case 1: return _("one");
|
|
case 2: return _("two");
|
|
case 3: return _("three");
|
|
case 4: return _("four");
|
|
case 5: return _("five");
|
|
case 6: return _("six");
|
|
case 7: return _("seven");
|
|
case 8: return _("eight");
|
|
case 9: return _("nine");
|
|
case 10: return _("ten");
|
|
case 11: return _("eleven");
|
|
case 12: return _("twelve");
|
|
case 13: return _("thirteen");
|
|
case 15: return _("fifteen");
|
|
case 18: return _("eighteen");
|
|
case 20: return _("twenty");
|
|
case 30: return _("thirty");
|
|
case 40: return _("forty");
|
|
case 50: return _("fifty");
|
|
case 80: return _("eighty");
|
|
default: {
|
|
if (i < 0 || i >= 100) {
|
|
// number too large, keep as digits
|
|
return (String() << i);
|
|
} else if (i < 20) {
|
|
return english_number(i%10) + _("teen");
|
|
} else if (i % 10 == 0) {
|
|
return english_number(i/10) + _("ty");
|
|
} else {
|
|
// <a>ty-<b>
|
|
return english_number(i/10*10) + _("-") + english_number(i%10);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Write an ordinal number using words, for example 23 -> "twenty-third"
|
|
String english_ordinal(int i) {
|
|
switch (i) {
|
|
case 1: return _("first");
|
|
case 2: return _("second");
|
|
case 3: return _("third");
|
|
case 5: return _("fifth");
|
|
case 8: return _("eighth");
|
|
case 9: return _("ninth");
|
|
case 11: return _("eleventh");
|
|
case 12: return _("twelfth");
|
|
default: {
|
|
if (i <= 0 || i >= 100) {
|
|
// number too large, keep as digits
|
|
return String::Format(_("%dth"), i);
|
|
} else if (i < 20) {
|
|
return english_number(i) + _("th");
|
|
} else if (i % 10 == 0) {
|
|
String num = english_number(i);
|
|
return num.substr(0,num.size()-1) + _("ieth"); // twentieth
|
|
} else {
|
|
// <a>ty-<b>th
|
|
return english_number(i/10*10) + _("-") + english_ordinal(i%10);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Write a number using words, use "a" for 1
|
|
String english_number_a(int i) {
|
|
if (i == 1) return _("a");
|
|
else return english_number(i);
|
|
}
|
|
/// Write a number using words, use "" for 1
|
|
String english_number_multiple(int i) {
|
|
if (i == 1) return _("");
|
|
else return english_number(i);
|
|
}
|
|
|
|
|
|
// script_english_number_*
|
|
String do_english_num(String input, String(*fun)(int)) {
|
|
if (is_substr(input, 0, _("<param-"))) {
|
|
// a keyword parameter, of the form "<param->123</param->"
|
|
size_t start = skip_tag(input, 0);
|
|
if (start != String::npos) {
|
|
size_t end = input.find_first_of(_('<'), start);
|
|
if (end != String::npos) {
|
|
String is = input.substr(start, end - start);
|
|
long i = 0;
|
|
if (is.ToLong(&i)) {
|
|
if (i == 1) {
|
|
return _("<hint-1>") + substr_replace(input, start, end, fun(i));
|
|
} else {
|
|
return _("<hint-2>") + substr_replace(input, start, end, fun(i));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return _("<hint-2>") + input;
|
|
} else {
|
|
long i = 0;
|
|
if (input.ToLong(&i)) {
|
|
return fun(i);
|
|
}
|
|
return input;
|
|
}
|
|
}
|
|
|
|
SCRIPT_FUNCTION(english_number) {
|
|
SCRIPT_PARAM_C(String, input);
|
|
SCRIPT_RETURN(do_english_num(input, english_number));
|
|
}
|
|
SCRIPT_FUNCTION(english_number_a) {
|
|
SCRIPT_PARAM_C(String, input);
|
|
SCRIPT_RETURN(do_english_num(input, english_number_a));
|
|
}
|
|
SCRIPT_FUNCTION(english_number_multiple) {
|
|
SCRIPT_PARAM_C(String, input);
|
|
SCRIPT_RETURN(do_english_num(input, english_number_multiple));
|
|
}
|
|
SCRIPT_FUNCTION(english_number_ordinal) {
|
|
SCRIPT_PARAM_C(String, input);
|
|
SCRIPT_RETURN(do_english_num(input, english_ordinal));
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------- : Singular/plural
|
|
|
|
String english_singular(const String& str) {
|
|
if (str.empty()) return String();
|
|
|
|
String lower = str.Lower();
|
|
if (english_singular_exceptions.find(lower) != english_singular_exceptions.end()) {
|
|
if (isupper(str[0])) {
|
|
if (isupper(str[str.size()-1])) {
|
|
return english_singular_exceptions[lower].Upper();
|
|
} else {
|
|
return capitalize(english_singular_exceptions[lower]);
|
|
}
|
|
} else {
|
|
return english_singular_exceptions[lower];
|
|
}
|
|
}
|
|
|
|
if (str.size() > 3 && is_substr(str, str.size()-3, _("ies"))) {
|
|
return str.substr(0, str.size() - 3) + _("y");
|
|
} else if (str.size() > 3 && is_substr(str, str.size()-3, _("oes"))) {
|
|
return str.substr(0, str.size() - 2);
|
|
} else if (str.size() > 4 && is_substr(str, str.size()-4, _("ches"))) {
|
|
return str.substr(0, str.size() - 2);
|
|
} else if (str.size() > 4 && is_substr(str, str.size()-4, _("shes"))) {
|
|
return str.substr(0, str.size() - 2);
|
|
} else if (str.size() > 4 && is_substr(str, str.size()-4, _("sses"))) {
|
|
return str.substr(0, str.size() - 2);
|
|
} else if (str.size() > 5 && is_substr(str, str.size()-3, _("ves")) && (is_substr(str, str.size()-5, _("el")) || is_substr(str, str.size()-5, _("ar")) )) {
|
|
return str.substr(0, str.size() - 3) + _("f");
|
|
} else if (str.size() > 1 && str.GetChar(str.size() - 1) == _('s')) {
|
|
return str.substr(0, str.size() - 1);
|
|
} else if (str.size() >= 3 && is_substr(str, str.size()-3, _("men"))) {
|
|
return str.substr(0, str.size() - 2) + _("an");
|
|
} else {
|
|
return str;
|
|
}
|
|
}
|
|
String english_plural(const String& str) {
|
|
if (str.empty()) return String();
|
|
|
|
String lower = str.Lower();
|
|
if (english_plural_exceptions.find(lower) != english_plural_exceptions.end()) {
|
|
if (isupper(str[0])) {
|
|
if (isupper(str[str.size()-1])) {
|
|
return english_plural_exceptions[lower].Upper();
|
|
} else {
|
|
return capitalize(english_plural_exceptions[lower]);
|
|
}
|
|
} else {
|
|
return english_plural_exceptions[lower];
|
|
}
|
|
}
|
|
|
|
if (str.size() > 2) {
|
|
Char a = str.GetChar(str.size() - 2);
|
|
Char b = str.GetChar(str.size() - 1);
|
|
if (b == _('y') && is_constant(a)) {
|
|
return str.substr(0, str.size() - 1) + _("ies");
|
|
} else if (b == _('o') && is_constant(a)) {
|
|
return str + _("es");
|
|
} else if ((a == _('s') || a == _('c')) && b == _('h')) {
|
|
return str + _("es");
|
|
} else if (b == _('s')) {
|
|
return str + _("es");
|
|
} else {
|
|
return str + _("s");
|
|
}
|
|
}
|
|
return str + _("s");
|
|
}
|
|
|
|
// script_english_singular/plural/singplur
|
|
String do_english(String input, String(*fun)(const String&)) {
|
|
if (is_substr(input, 0, _("<param-"))) {
|
|
// a keyword parameter, of the form "<param->123</param->"
|
|
size_t start = skip_tag(input, 0);
|
|
if (start != String::npos) {
|
|
size_t end = input.find_first_of(_('<'), start);
|
|
if (end != String::npos) {
|
|
String is = input.substr(start, end - start);
|
|
return substr_replace(input, start, end, fun(is));
|
|
}
|
|
}
|
|
return input; // failed
|
|
} else {
|
|
return fun(input);
|
|
}
|
|
}
|
|
|
|
SCRIPT_FUNCTION(english_singular) {
|
|
SCRIPT_PARAM_C(String, input);
|
|
SCRIPT_RETURN(do_english(input, english_singular));
|
|
}
|
|
SCRIPT_FUNCTION(english_plural) {
|
|
SCRIPT_PARAM_C(String, input);
|
|
SCRIPT_RETURN(do_english(input, english_plural));
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------- : Hints
|
|
|
|
/// Process english hints in the input string
|
|
/** A hint is formed by
|
|
* 1. an insertion of a parameter, <param-..>...</param->.
|
|
* 2. a <hint-1> or <hint-2> tag
|
|
*
|
|
* Hints have the following meaning:
|
|
* - "<hint-1>xxx(yyy)zzz" ---> "xxxzzz" (singular)
|
|
* - "<hint-2>xxx(yyy)zzz" ---> "xxxyyyzzz" (plural)
|
|
* - "[^., ]a <param-..>[aeiou]" ---> "\1 an \2" (articla 'an', case insensitive)
|
|
* - "<hint-?>" ---> "" (remove <hint>s afterwards)
|
|
*
|
|
* Note: there is no close tags for hints
|
|
*/
|
|
String process_english_hints(const String& str) {
|
|
String ret; ret.reserve(str.size());
|
|
// have we seen a <hint-1/2>?
|
|
// 1 for singular, 2 for plural
|
|
int singplur = 0;
|
|
int sing_found = 0;
|
|
int plur_found = 0;
|
|
for (size_t i = 0 ; i < str.size() ; ) {
|
|
Char c = str.GetChar(i);
|
|
if (is_substr(str, i, _("<hint-"))) {
|
|
Char h = str.GetChar(i + 6); // hint code
|
|
if (h == _('1')) {
|
|
singplur = 1;
|
|
} else if (h == _('2')) {
|
|
singplur = 2;
|
|
}
|
|
i = skip_tag(str, i);
|
|
} else if (is_substr(str, i, _("<param-"))) {
|
|
size_t after = skip_tag(str, i);
|
|
if (after != String::npos) {
|
|
Char c = str.GetChar(after);
|
|
if (singplur == 1 && is_substr(str, after, _("a</param-"))) {
|
|
// a -> an, where the a originates from english_number_a(1)
|
|
size_t after_end = skip_tag(str,after+1);
|
|
if (after_end == String::npos) {
|
|
throw ScriptError(_("Incomplete </param> tag"));
|
|
}
|
|
if (after_end != String::npos && after_end + 1 < str.size()
|
|
&& isSpace(str.GetChar(after_end)) && is_vowel(str.GetChar(after_end+1))) {
|
|
ret.append(str,i,after-i);
|
|
ret += _("an");
|
|
i = after + 1;
|
|
continue;
|
|
}
|
|
} else if (is_vowel(c) && ret.size() >= 2) {
|
|
// a -> an?
|
|
// is there "a" before this?
|
|
String last = ret.substr(ret.size() - 2);
|
|
if ( (ret.size() == 2 || !isAlpha(ret.GetChar(ret.size() - 3))) &&
|
|
(last == _("a ") || last == _("A ")) ) {
|
|
ret.insert(ret.size() - 1, _('n'));
|
|
}
|
|
} else if (is_substr(str, after, _("</param-")) && ret.size() >= 1 &&
|
|
ret.GetChar(ret.size() - 1) == _(' ')) {
|
|
// empty param, drop space before it
|
|
ret.resize(ret.size() - 1);
|
|
}
|
|
}
|
|
ret.append(str,i,min(after,str.size())-i);
|
|
i = after;
|
|
} else if (is_substr(str, i, _("<singular>"))) {
|
|
sing_found++;
|
|
// singular -> keep, plural -> drop
|
|
size_t start = skip_tag(str, i);
|
|
size_t end = match_close_tag(str, i);
|
|
if (end == String::npos) {
|
|
throw ScriptError(_("<singular> tag found without matching closing tag."));
|
|
}
|
|
else if (singplur == 1) {
|
|
ret += str.substr(start, end - start);
|
|
singplur = 0;
|
|
}
|
|
i = skip_tag(str, end);
|
|
} else if (is_substr(str, i, _("<plural>"))) {
|
|
plur_found++;
|
|
// singular -> drop, plural -> keep
|
|
size_t start = skip_tag(str, i);
|
|
size_t end = match_close_tag(str, i);
|
|
if (end == String::npos) {
|
|
throw ScriptError(_("<plural> tag found without matching closing tag."));
|
|
}
|
|
else if (singplur == 2) {
|
|
ret += str.substr(start, end - start);
|
|
singplur = 0;
|
|
}
|
|
i = skip_tag(str, end);
|
|
} else if (c == _('(') && singplur) {
|
|
// singular -> drop (...), plural -> keep it
|
|
size_t end = str.find_first_of(_(')'), i);
|
|
if (end != String::npos) {
|
|
if (singplur == 2) {
|
|
ret += str.substr(i + 1, end - i - 1);
|
|
}
|
|
i = end + 1;
|
|
} else { // handle like normal
|
|
ret += c;
|
|
++i;
|
|
}
|
|
singplur = 0;
|
|
} else if (c == _('<')) {
|
|
size_t after = skip_tag(str, i);
|
|
ret.append(str,i,min(after,str.size())-i);
|
|
i = after;
|
|
} else {
|
|
ret += c;
|
|
++i;
|
|
}
|
|
}
|
|
if (sing_found > plur_found) {
|
|
throw ScriptError(_("<singular> tag found without matching <plural> tag."));
|
|
}
|
|
else if (sing_found < plur_found) {
|
|
throw ScriptError(_("<plural> tag found without matching <singular> tag."));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
SCRIPT_FUNCTION(process_english_hints) {
|
|
SCRIPT_PARAM_C(String, input);
|
|
assert_tagged(input);
|
|
SCRIPT_RETURN(process_english_hints(input));
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------- : Init
|
|
|
|
void init_script_english_functions(Context& ctx) {
|
|
ctx.setVariable(_("english_number"), script_english_number);
|
|
ctx.setVariable(_("english_number_a"), script_english_number_a);
|
|
ctx.setVariable(_("english_number_multiple"), script_english_number_multiple);
|
|
ctx.setVariable(_("english_number_ordinal"), script_english_number_ordinal);
|
|
ctx.setVariable(_("english_singular"), script_english_singular);
|
|
ctx.setVariable(_("english_plural"), script_english_plural);
|
|
ctx.setVariable(_("process_english_hints"), script_process_english_hints);
|
|
}
|