Commit 6ea72fdd authored by twanvl's avatar twanvl

The check_spelling function now has support for additional dictionaries and regexes to match.

The magic game uses these features.
parent da51f0ef
33
mana
untap/MSDRJZG
face-down
nonwhite
nonblue
nonblack
nonred
nongreen
non-land
unblock/USDG
precombat
postcombat
scry
Plainswalk
Islandwalk
Swampwalk
Mountainwalk
Forestwalk
Landwalk
Desertwalk
Plainshome
Islandhome
Swamphome
Mountainhome
Foresthome
Landhome
Soulshift
Ninjitsu
Bushido
Lifelink
Gravestorm
Fateseal
Bloodthirst
......@@ -334,8 +334,17 @@ mana_context :=
| <param-cost>[ ]*<match></param-cost> # keyword argument that is declared as cost
| <param-cost><match>, # keyword argument that is declared as cost
";
# truncates the name of legends
legend_filter := replace@(match:"(, | of | the ).*", replace: "" )
# these are considered a correct 'word' for spellchecking in the text box:
additional_text_words := match@(match:
"(?ix)^(?: # match whole word
<atom-[^>]*>.*?</atom-[^>]*> # cardnames and stuff
| [+-]?[0-9X]+ / [+-]?[0-9X]+ # '3/3', '+X/+X'
)$")
# the rule text filter
# - adds mana symbols
# - makes text in parentheses italic
......@@ -413,7 +422,11 @@ text_filter :=
replace: { _1 + to_upper(_2) }) +
curly_quotes +
# step 9 : spellcheck
{ check_spelling(language:language().spellcheck_code) }
{ check_spelling(
language: language().spellcheck_code,
extra_dictionary: "/magic.mse-game/magic-words",
extra_match: additional_text_words
)}
############################################################## Other boxes
......
......@@ -193,6 +193,12 @@ void SymbolControl::draw(DC& dc) {
}
dc.SetLogicalFunction(wxCOPY);
}
// draw aspect ratio indicators
double ar = symbol->aspectRatio();
// TODO: limit aspect ratio
if (ar > 0) {
} else if (ar < 0) {
}
// draw editing overlay
if (editor) {
editor->draw(dc);
......
......@@ -3948,11 +3948,48 @@
</File>
</Filter>
<Filter
Name="tests"
Name="template-stuff"
Filter="">
<File
RelativePath="..\data\magic-test.mse-style\script-language-tests">
</File>
<Filter
Name="magic"
Filter="">
<File
RelativePath="..\data\magic.mse-game\add_cards_scripts">
</File>
<File
RelativePath="..\data\magic.mse-game\auto_replace">
</File>
<File
RelativePath="..\data\magic.mse-game\card_fields">
</File>
<File
RelativePath="..\data\magic.mse-game\game">
</File>
<File
RelativePath="..\data\magic.mse-game\keywords">
</File>
<File
RelativePath="..\data\magic.mse-game\language">
</File>
<File
RelativePath="..\data\magic.mse-game\packs">
</File>
<File
RelativePath="..\data\magic.mse-game\script">
</File>
<File
RelativePath="..\data\magic.mse-game\set_fields">
</File>
<File
RelativePath="..\data\magic.mse-game\statistics">
</File>
<File
RelativePath="..\data\magic.mse-game\word_lists">
</File>
</Filter>
</Filter>
<File
RelativePath=".\code_template.cpp">
......
......@@ -15,7 +15,7 @@
// ----------------------------------------------------------------------------- : Functions
bool spelled_correctly(const String& input, size_t start, size_t end, SpellChecker& checker) {
inline bool spelled_correctly(const String& input, size_t start, size_t end, SpellChecker** checkers, const ScriptValueP& extra_test, Context& ctx) {
// untag
String word = untag(input.substr(start,end-start));
if (word.empty()) return true;
......@@ -33,28 +33,48 @@ bool spelled_correctly(const String& input, size_t start, size_t end, SpellCheck
// symbols are always spelled correctly
return true;
}
// run through spellchecker
return checker.spell(word.substr(start_u,end_u));
// run through spellchecker(s)
word.erase(end_u,String::npos);
word.erase(0,start_u);
for (SpellChecker** c = checkers ; *c ; ++c) {
if ((*c)->spell(word)) {
return true;
}
}
// run through additional words regex
if (extra_test) {
ctx.setVariable(SCRIPT_VAR_input, to_script(input.substr(start2,end2-start2)));
if (*extra_test->eval(ctx)) {
return true;
}
}
return false;
}
void check_word(const String& input, String& out, size_t start, size_t end, SpellChecker& checker) {
void check_word(const String& input, String& out, size_t start, size_t end, SpellChecker** checkers, const ScriptValueP& extra_test, Context& ctx) {
if (start >= end) return;
bool good = spelled_correctly(input, start, end, checker);
bool good = spelled_correctly(input, start, end, checkers, extra_test, ctx);
if (!good) out += _("<error-spelling>");
out.append(input, start, end-start);
if (!good) out += _("</error-spelling>");
}
SCRIPT_FUNCTION(check_spelling) {
SCRIPT_PARAM(String,language);
SCRIPT_PARAM(String,input);
if (language.empty()) {
// no language -> spelling checking
SCRIPT_RETURN(true);
}
SpellChecker& checker = SpellChecker::get(language);
SCRIPT_PARAM_C(String,language);
SCRIPT_PARAM_C(String,input);
SCRIPT_OPTIONAL_PARAM_N_(String,_("extra dictionary"),extra_dictionary);
SCRIPT_OPTIONAL_PARAM_N_(ScriptValueP,_("extra match"),extra_match);
// remove old spelling error tags
input = remove_tag(input, _("<error-spelling"));
// no language -> spelling checking
if (language.empty()) {
SCRIPT_RETURN(input);
}
SpellChecker* checkers[3] = {nullptr};
checkers[0] = &SpellChecker::get(language);
if (!extra_dictionary.empty()) {
checkers[1] = &SpellChecker::get(extra_dictionary,language);
}
// now walk over the words in the input, and mark misspellings
String result;
size_t word_start = 0, word_end = 0, pos = 0;
......@@ -71,7 +91,7 @@ SCRIPT_FUNCTION(check_spelling) {
}
} else if (isSpace(c) || c == EM_DASH || c == EN_DASH) {
// word boundary -> check word
check_word(input, result, word_start, word_end, checker);
check_word(input, result, word_start, word_end, checkers, extra_match, ctx);
// non-word characters
result.append(input, word_end, pos - word_end + 1);
// next
......@@ -81,15 +101,15 @@ SCRIPT_FUNCTION(check_spelling) {
}
}
// last word
check_word(input, result, word_start, word_end, checker);
check_word(input, result, word_start, word_end, checkers, extra_match, ctx);
result.append(input, word_end, String::npos);
// done
SCRIPT_RETURN(result);
}
SCRIPT_FUNCTION(check_spelling_word) {
SCRIPT_PARAM(String,language);
SCRIPT_PARAM(String,input);
SCRIPT_PARAM_C(String,language);
SCRIPT_PARAM_C(String,input);
if (language.empty()) {
// no language -> spelling checking
SCRIPT_RETURN(true);
......
......@@ -77,6 +77,7 @@ void init_script_variables() {
Var(styling);
Var(value);
Var(condition);
Var(language);
assert(variables.size() == SCRIPT_VAR_CUSTOM_FIRST);
}
......
......@@ -136,6 +136,7 @@ enum Variable
, SCRIPT_VAR_styling
, SCRIPT_VAR_value
, SCRIPT_VAR_condition
, SCRIPT_VAR_language
, SCRIPT_VAR_CUSTOM_FIRST // other variables start from here
, SCRIPT_VAR_CUSTOM_LOTS = 0xFFFFFF // ensure that sizeof(Variable) is large enough
};
......
......@@ -120,6 +120,27 @@ InputStreamP PackageManager::openFileFromPackage(Packaged*& package, const Strin
throw FileNotFoundError(name, _("No package name specified, use '/package/filename'"));
}
String PackageManager::openFilenameFromPackage(Packaged*& package, const String& name) {
if (!name.empty() && name.GetChar(0) == _('/')) {
// absolute name; break name
size_t start = name.find_first_not_of(_("/\\"), 1); // allow "//package/name" from incorrect scripts
size_t pos = name.find_first_of(_("/\\"), start);
if (start < pos && pos != String::npos) {
// open package
PackagedP p = openAny(name.substr(start, pos-start));
if (package && !is_substr(name,start,_(":NO-WARN-DEP:"))) {
package->requireDependency(p.get());
}
package = p.get();
return p->absoluteFilename() + _("/") + name.substr(pos + 1);
}
} else if (package) {
// relative name
return package->absoluteFilename() + _("/") + name;
}
throw FileNotFoundError(name, _("No package name specified, use '/package/filename'"));
}
String PackageManager::getDictionaryDir(bool l) const {
String dir = (l ? local : global).getDirectory();
if (dir.empty()) return wxEmptyString;
......
......@@ -150,6 +150,12 @@ class PackageManager {
*/
InputStreamP openFileFromPackage(Packaged*& package, const String& name);
/// Get a filename to open from a package
/** WARNING: this is a bit of a hack, since not all package types support names in this way.
* It is needed for third party libraries (i.e. hunspell) that load stuff from files.
*/
String openFilenameFromPackage(Packaged*& package, const String& name);
// --------------------------------------------------- : Packages on disk
/// Check if the given dependency is currently installed
......
......@@ -35,6 +35,31 @@ SpellChecker& SpellChecker::get(const String& language) {
return *speller;
}
SpellChecker& SpellChecker::get(const String& filename, const String& language) {
SpellCheckerP& speller = spellers[filename + _(".") + language];
if (!speller) {
Packaged* package = nullptr;
String prefix = package_manager.openFilenameFromPackage(package, filename) + _(".");
String local_dir = package_manager.getDictionaryDir(true);
String global_dir = package_manager.getDictionaryDir(false);
String aff_path = language + _(".aff");
String dic_path = language + _(".dic");
if (wxFileExists(prefix + aff_path) && wxFileExists(prefix + dic_path)) {
speller = SpellCheckerP(new SpellChecker((prefix + aff_path).mb_str(),
(prefix + dic_path).mb_str()));
} else if (wxFileExists(local_dir + aff_path) && wxFileExists(prefix + dic_path)) {
speller = SpellCheckerP(new SpellChecker((local_dir + aff_path).mb_str(),
(prefix + dic_path).mb_str()));
} else if (wxFileExists(global_dir + aff_path) && wxFileExists(prefix + dic_path)) {
speller = SpellCheckerP(new SpellChecker((global_dir + aff_path).mb_str(),
(prefix + dic_path).mb_str()));
} else {
throw Error(_("Dictionary '") + filename + _("' not found for language: ") + language);
}
}
return *speller;
}
SpellChecker::SpellChecker(const char* aff_path, const char* dic_path)
: Hunspell(aff_path,dic_path)
, encoding(String(get_dic_encoding(), IF_UNICODE(wxConvLibc, wxSTRING_MAXLEN)))
......
......@@ -22,6 +22,9 @@ class SpellChecker : public Hunspell, public IntrusivePtrBase<SpellChecker> {
/// Get a SpellChecker object for the given language.
/** Note: This is not threadsafe yet */
static SpellChecker& get(const String& language);
/// Get a SpellChecker object for the given language and filename
/** Note: This is not threadsafe yet */
static SpellChecker& get(const String& filename, const String& language);
/// Destroy all cached SpellChecker objects
static void destroyAll();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment