import re # Utilities for handling unicode, unary numbers, mana costs, and special symbols. # For convenience we redefine everything from config so that it can all be accessed # from the utils module. import config # special chunk of text that Magic Set Editor 2 requires at the start of all set files. mse_prepend = 'mse version: 0.3.8\ngame: magic\nstylesheet: m15\nset info:\n\tsymbol:\nstyling:\n\tmagic-m15:\n\t\ttext box mana symbols: magic-mana-small.mse-symbol-font\n\t\toverlay:\n\tmagic-m15-clear:\n\t\ttext box mana symbols: magic-mana-small.mse-symbol-font\n\t\toverlay: \n\tmagic-m15-extra-improved:\n\t\ttext box mana symbols: magic-mana-small.mse-symbol-font\n\t\tpt box symbols: magic-pt-symbols-extra.mse-symbol-font\n\t\toverlay: \n\tmagic-m15-planeswalker:\n\t\ttext box mana symbols: magic-mana-small.mse-symbol-font\n\t\toverlay: \n\tmagic-m15-planeswalker-promo-black:\n\t\ttext box mana symbols: magic-mana-small.mse-symbol-font\n\t\toverlay: \n\tmagic-m15-promo-dka:\n\t\ttext box mana symbols: magic-mana-small.mse-symbol-font\n\t\toverlay: \n\tmagic-m15-token-clear:\n\t\ttext box mana symbols: magic-mana-small.mse-symbol-font\n\t\toverlay: \n\tmagic-new-planeswalker:\n\t\ttext box mana symbols: magic-mana-small.mse-symbol-font\n\t\toverlay: \n\tmagic-new-planeswalker-4abil:\n\t\ttext box mana symbols: magic-mana-small.mse-symbol-font\n\t\toverlay: \n\tmagic-new-planeswalker-clear:\n\t\ttext box mana symbols: magic-mana-small.mse-symbol-font\n\t\toverlay: \n\tmagic-new-planeswalker-promo-black:\n\t\ttext box mana symbols: magic-mana-small.mse-symbol-font\n\t\toverlay: \n' # special chunk of text to start an HTML document. import html_extra_data segment_ids = html_extra_data.id_lables html_prepend = html_extra_data.html_prepend html_append = "\n\n" # encoding formats we know about formats = [ 'std', 'named', 'noname', 'rfields', 'old', 'norarity', 'vec', 'custom', ] # separators cardsep = config.cardsep fieldsep = config.fieldsep bsidesep = config.bsidesep newline = config.newline # special indicators dash_marker = config.dash_marker bullet_marker = config.bullet_marker this_marker = config.this_marker counter_marker = config.counter_marker reserved_marker = config.reserved_marker reserved_mana_marker = config.reserved_mana_marker choice_open_delimiter = config.choice_open_delimiter choice_close_delimiter = config.choice_close_delimiter x_marker = config.x_marker tap_marker = config.tap_marker untap_marker = config.untap_marker rarity_common_marker = config.rarity_common_marker rarity_uncommon_marker = config.rarity_uncommon_marker rarity_rare_marker = config.rarity_rare_marker rarity_mythic_marker = config.rarity_mythic_marker rarity_special_marker = config.rarity_special_marker rarity_basic_land_marker = config.rarity_basic_land_marker json_rarity_map = { 'Common' : rarity_common_marker, 'Uncommon' : rarity_uncommon_marker, 'Rare' : rarity_rare_marker, 'Mythic Rare' : rarity_mythic_marker, 'Special' : rarity_special_marker, 'Basic Land' : rarity_basic_land_marker, } json_rarity_unmap = {json_rarity_map[k] : k for k in json_rarity_map} # unambiguous synonyms counter_rename = config.counter_rename # field labels field_label_name = config.field_label_name field_label_rarity = config.field_label_rarity field_label_cost = config.field_label_cost field_label_supertypes = config.field_label_supertypes field_label_types = config.field_label_types field_label_subtypes = config.field_label_subtypes field_label_loyalty = config.field_label_loyalty field_label_pt = config.field_label_pt field_label_text = config.field_label_text # additional fields we add to the json cards json_field_bside = config.json_field_bside json_field_set_name = config.json_field_set_name json_field_info_code = config.json_field_info_code # unicode / ascii conversion unicode_trans = { '\u2014': dash_marker, # unicode long dash '\u2022': bullet_marker, # unicode bullet '\u2019': '"', # single quote '\u2018': '"', # single quote '\u2212': '-', # minus sign '\xe6': 'ae', # ae symbol '\xfb': 'u', # u with caret '\xfa': 'u', # u with accent '\xe9': 'e', # e with accent '\xe1': 'a', # a with accent '\xe0': 'a', # a with accent going the other way '\xe2': 'a', # a with caret '\xf6': 'o', # o with umlaut '\xed': 'i', # i with accent } # this one is one-way only def to_ascii(s): for uchar in unicode_trans: s = s.replace(uchar, unicode_trans[uchar]) return s # unary numbers unary_marker = config.unary_marker unary_counter = config.unary_counter unary_max = config.unary_max unary_exceptions = config.unary_exceptions def to_unary(s, warn = False): numbers = re.findall(r'[0123456789]+', s) # replace largest first to avoid accidentally replacing shared substrings for n in sorted(numbers, cmp = lambda x,y: cmp(int(x), int(y)), reverse = True): i = int(n) if i in unary_exceptions: s = s.replace(n, unary_exceptions[i]) elif i > unary_max: i = unary_max if warn: print(s) s = s.replace(n, unary_marker + unary_counter * i) else: s = s.replace(n, unary_marker + unary_counter * i) return s def from_unary(s): numbers = re.findall(re.escape(unary_marker + unary_counter) + '*', s) # again, largest first so we don't replace substrings and break everything for n in sorted(numbers, cmp = lambda x,y: cmp(len(x), len(y)), reverse = True): i = (len(n) - len(unary_marker)) / len(unary_counter) s = s.replace(n, str(i)) return s # mana syntax mana_open_delimiter = '{' mana_close_delimiter = '}' mana_json_open_delimiter = mana_open_delimiter mana_json_close_delimiter = mana_close_delimiter mana_json_hybrid_delimiter = '/' mana_forum_open_delimiter = '[mana]' mana_forum_close_delimiter = '[/mana]' mana_html_open_delimiter = "" mana_html_hybrid_delimiter = '-' mana_unary_marker = '' # if the same as unary_marker, from_unary WILL replace numbers in mana costs mana_unary_counter = unary_counter # The decoding from mtgjson format is dependent on the specific structure of # these internally used mana symbol strings, so if you want to change them you'll # also have to change the json decoding functions. # standard mana symbol set mana_W = 'W' # single color mana_U = 'U' mana_B = 'B' mana_R = 'R' mana_G = 'G' mana_P = 'P' # colorless phyrexian mana_S = 'S' # snow mana_X = 'X' # colorless X mana_C = 'C' # colorless only 'eldrazi' mana_WP = 'WP' # single color phyrexian mana_UP = 'UP' mana_BP = 'BP' mana_RP = 'RP' mana_GP = 'GP' mana_2W = '2W' # single color hybrid mana_2U = '2U' mana_2B = '2B' mana_2R = '2R' mana_2G = '2G' mana_WU = 'WU' # dual color hybrid mana_WB = 'WB' mana_RW = 'RW' mana_GW = 'GW' mana_UB = 'UB' mana_UR = 'UR' mana_GU = 'GU' mana_BR = 'BR' mana_BG = 'BG' mana_RG = 'RG' # alternative order symbols mana_WP_alt = 'PW' # single color phyrexian mana_UP_alt = 'PU' mana_BP_alt = 'PB' mana_RP_alt = 'PR' mana_GP_alt = 'PG' mana_2W_alt = 'W2' # single color hybrid mana_2U_alt = 'U2' mana_2B_alt = 'B2' mana_2R_alt = 'R2' mana_2G_alt = 'G2' mana_WU_alt = 'UW' # dual color hybrid mana_WB_alt = 'BW' mana_RW_alt = 'WR' mana_GW_alt = 'WG' mana_UB_alt = 'BU' mana_UR_alt = 'RU' mana_GU_alt = 'UG' mana_BR_alt = 'RB' mana_BG_alt = 'GB' mana_RG_alt = 'GR' # special mana_2 = '2' # use with 'in' to identify single color hybrid # master symbol lists mana_syms = [ mana_W, mana_U, mana_B, mana_R, mana_G, mana_P, mana_S, mana_X, mana_C, mana_WP, mana_UP, mana_BP, mana_RP, mana_GP, mana_2W, mana_2U, mana_2B, mana_2R, mana_2G, mana_WU, mana_WB, mana_RW, mana_GW, mana_UB, mana_UR, mana_GU, mana_BR, mana_BG, mana_RG, ] mana_symalt = [ mana_WP_alt, mana_UP_alt, mana_BP_alt, mana_RP_alt, mana_GP_alt, mana_2W_alt, mana_2U_alt, mana_2B_alt, mana_2R_alt, mana_2G_alt, mana_WU_alt, mana_WB_alt, mana_RW_alt, mana_GW_alt, mana_UB_alt, mana_UR_alt, mana_GU_alt, mana_BR_alt, mana_BG_alt, mana_RG_alt, ] mana_symall = mana_syms + mana_symalt # alt symbol conversion def mana_alt(sym): if not sym in mana_symall: raise ValueError('invalid mana symbol for mana_alt(): ' + repr(sym)) if len(sym) < 2: return sym else: return sym[::-1] # produce intended neural net output format def mana_sym_to_encoding(sym): if not sym in mana_symall: raise ValueError('invalid mana symbol for mana_sym_to_encoding(): ' + repr(sym)) if len(sym) < 2: return sym * 2 else: return sym # produce json formatting used in mtgjson def mana_sym_to_json(sym): if not sym in mana_symall: raise ValueError('invalid mana symbol for mana_sym_to_json(): ' + repr(sym)) if len(sym) < 2: return mana_json_open_delimiter + sym + mana_json_close_delimiter else: return (mana_json_open_delimiter + sym[0] + mana_json_hybrid_delimiter + sym[1] + mana_json_close_delimiter) # produce pretty formatting that renders on mtgsalvation forum # converts individual symbols; surrounding [mana][/mana] tags are added elsewhere def mana_sym_to_forum(sym): if not sym in mana_symall: raise ValueError('invalid mana symbol for mana_sym_to_forum(): ' + repr(sym)) if sym in mana_symalt: sym = mana_alt(sym) if len(sym) < 2: return sym else: return mana_json_open_delimiter + sym + mana_json_close_delimiter # forward symbol tables for encoding mana_syms_encode = {sym : mana_sym_to_encoding(sym) for sym in mana_syms} mana_symalt_encode = {sym : mana_sym_to_encoding(sym) for sym in mana_symalt} mana_symall_encode = {sym : mana_sym_to_encoding(sym) for sym in mana_symall} mana_syms_jencode = {sym : mana_sym_to_json(sym) for sym in mana_syms} mana_symalt_jencode = {sym : mana_sym_to_json(sym) for sym in mana_symalt} mana_symall_jencode = {sym : mana_sym_to_json(sym) for sym in mana_symall} # reverse symbol tables for decoding mana_syms_decode = {mana_sym_to_encoding(sym) : sym for sym in mana_syms} mana_symalt_decode = {mana_sym_to_encoding(sym) : sym for sym in mana_symalt} mana_symall_decode = {mana_sym_to_encoding(sym) : sym for sym in mana_symall} mana_syms_jdecode = {mana_sym_to_json(sym) : sym for sym in mana_syms} mana_symalt_jdecode = {mana_sym_to_json(sym) : sym for sym in mana_symalt} mana_symall_jdecode = {mana_sym_to_json(sym) : sym for sym in mana_symall} # going straight from json to encoding and vice versa def mana_encode_direct(jsym): if not jsym in mana_symall_jdecode: raise ValueError('json string not found in decode table for mana_encode_direct(): ' + repr(jsym)) else: return mana_symall_encode[mana_symall_jdecode[jsym]] def mana_decode_direct(sym): if not sym in mana_symall_decode: raise ValueError('mana symbol not found in decode table for mana_decode_direct(): ' + repr(sym)) else: return mana_symall_jencode[mana_symall_decode[sym]] # hacked in support for mtgsalvation forum def mana_decode_direct_forum(sym): if not sym in mana_symall_decode: raise ValueError('mana symbol not found in decode table for mana_decode_direct_forum(): ' + repr(sym)) else: return mana_sym_to_forum(mana_symall_decode[sym]) # processing entire strings def unique_string(s): return ''.join(set(s)) mana_charset_special = mana_unary_marker + mana_unary_counter mana_charset_strict = unique_string(''.join(mana_symall) + mana_charset_special) mana_charset = unique_string(mana_charset_strict + mana_charset_strict.lower()) mana_regex_strict = (re.escape(mana_open_delimiter) + '[' + re.escape(mana_charset_strict) + ']*' + re.escape(mana_close_delimiter)) mana_regex = (re.escape(mana_open_delimiter) + '[' + re.escape(mana_charset) + ']*' + re.escape(mana_close_delimiter)) # as a special case, we let unary or decimal numbers exist in json mana strings mana_json_charset_special = ('0123456789' + unary_marker + unary_counter) mana_json_charset_strict = unique_string(''.join(mana_symall_jdecode) + mana_json_charset_special) mana_json_charset = unique_string(mana_json_charset_strict + mana_json_charset_strict.lower()) # note that json mana strings can't be empty between the delimiters mana_json_regex_strict = (re.escape(mana_json_open_delimiter) + '[' + re.escape(mana_json_charset_strict) + ']+' + re.escape(mana_json_close_delimiter)) mana_json_regex = (re.escape(mana_json_open_delimiter) + '[' + re.escape(mana_json_charset) + ']+' + re.escape(mana_json_close_delimiter)) number_decimal_regex = r'[0123456789]+' number_unary_regex = re.escape(unary_marker) + re.escape(unary_counter) + '*' mana_decimal_regex = (re.escape(mana_json_open_delimiter) + number_decimal_regex + re.escape(mana_json_close_delimiter)) mana_unary_regex = (re.escape(mana_json_open_delimiter) + number_unary_regex + re.escape(mana_json_close_delimiter)) # convert a json mana string to the proper encoding def mana_translate(jmanastr): manastr = jmanastr for n in sorted(re.findall(mana_unary_regex, manastr), lambda x,y: cmp(len(x), len(y)), reverse = True): ns = re.findall(number_unary_regex, n) i = (len(ns[0]) - len(unary_marker)) / len(unary_counter) manastr = manastr.replace(n, mana_unary_marker + mana_unary_counter * i) for n in sorted(re.findall(mana_decimal_regex, manastr), lambda x,y: cmp(len(x), len(y)), reverse = True): ns = re.findall(number_decimal_regex, n) i = int(ns[0]) manastr = manastr.replace(n, mana_unary_marker + mana_unary_counter * i) for jsym in sorted(mana_symall_jdecode, lambda x,y: cmp(len(x), len(y)), reverse = True): if jsym in manastr: manastr = manastr.replace(jsym, mana_encode_direct(jsym)) return mana_open_delimiter + manastr + mana_close_delimiter # convert an encoded mana string back to json mana_symlen_min = min([len(sym) for sym in mana_symall_decode]) mana_symlen_max = max([len(sym) for sym in mana_symall_decode]) def mana_untranslate(manastr, for_forum = False, for_html = False): inner = manastr[1:-1] jmanastr = '' colorless_total = 0 idx = 0 while idx < len(inner): # taking this branch is an infinite loop if unary_marker is empty if len(mana_unary_marker) > 0 and inner[idx:idx+len(mana_unary_marker)] == mana_unary_marker: idx += len(mana_unary_marker) elif inner[idx:idx+len(mana_unary_counter)] == mana_unary_counter: idx += len(mana_unary_counter) colorless_total += 1 else: old_idx = idx for symlen in range(mana_symlen_min, mana_symlen_max + 1): sym = inner[idx:idx+symlen] if sym in mana_symall_decode: idx += symlen if for_html: jmanastr = jmanastr + mana_decode_direct(sym) jmanastr = jmanastr.replace(mana_open_delimiter, mana_html_open_delimiter) jmanastr = jmanastr.replace(mana_close_delimiter, mana_html_close_delimiter) jmanastr = jmanastr.replace(mana_open_delimiter, mana_html_open_delimiter) jmanastr = jmanastr.replace(mana_json_hybrid_delimiter, mana_html_hybrid_delimiter) elif for_forum: jmanastr = jmanastr + mana_decode_direct_forum(sym) else: jmanastr = jmanastr + mana_decode_direct(sym) break # otherwise we'll go into an infinite loop if we see a symbol we don't know if idx == old_idx: idx += 1 if for_html: if jmanastr == '': return mana_html_open_delimiter + str(colorless_total) + mana_html_close_delimiter else: return (('' if colorless_total == 0 else mana_html_open_delimiter + str(colorless_total) + mana_html_close_delimiter) + jmanastr) elif for_forum: if jmanastr == '': return mana_forum_open_delimiter + str(colorless_total) + mana_forum_close_delimiter else: return (mana_forum_open_delimiter + ('' if colorless_total == 0 else str(colorless_total)) + jmanastr + mana_forum_close_delimiter) else: if jmanastr == '': return mana_json_open_delimiter + str(colorless_total) + mana_json_close_delimiter else: return (('' if colorless_total == 0 else mana_json_open_delimiter + str(colorless_total) + mana_json_close_delimiter) + jmanastr) # finally, replacing all instances in a string # notice the calls to .upper(), this way we recognize lowercase symbols as well just in case def to_mana(s): jmanastrs = re.findall(mana_json_regex, s) for jmanastr in sorted(jmanastrs, lambda x,y: cmp(len(x), len(y)), reverse = True): s = s.replace(jmanastr, mana_translate(jmanastr.upper())) return s def from_mana(s, for_forum = False): manastrs = re.findall(mana_regex, s) for manastr in sorted(manastrs, lambda x,y: cmp(len(x), len(y)), reverse = True): s = s.replace(manastr, mana_untranslate(manastr.upper(), for_forum = for_forum)) return s # Translation could also be accomplished using the datamine.Manacost object's # display methods, but these direct string transformations are retained for # quick scripting and convenience (and used under the hood by that class to # do its formatting). # more convenience features for formatting tap / untap symbols json_symbol_tap = tap_marker json_symbol_untap = untap_marker json_symbol_trans = { mana_json_open_delimiter + json_symbol_tap + mana_json_close_delimiter : tap_marker, mana_json_open_delimiter + json_symbol_tap.lower() + mana_json_close_delimiter : tap_marker, mana_json_open_delimiter + json_symbol_untap + mana_json_close_delimiter : untap_marker, mana_json_open_delimiter + json_symbol_untap.lower() + mana_json_close_delimiter : untap_marker, } symbol_trans = { tap_marker : mana_json_open_delimiter + json_symbol_tap + mana_json_close_delimiter, untap_marker : mana_json_open_delimiter + json_symbol_untap + mana_json_close_delimiter, } symbol_forum_trans = { tap_marker : mana_forum_open_delimiter + json_symbol_tap + mana_forum_close_delimiter, untap_marker : mana_forum_open_delimiter + json_symbol_untap + mana_forum_close_delimiter, } symbol_html_trans = { tap_marker : mana_html_open_delimiter + json_symbol_tap + mana_html_close_delimiter, untap_marker : mana_html_open_delimiter + json_symbol_untap + mana_html_close_delimiter, } json_symbol_regex = (re.escape(mana_json_open_delimiter) + '[' + json_symbol_tap + json_symbol_tap.lower() + json_symbol_untap + json_symbol_untap.lower() + ']' + re.escape(mana_json_close_delimiter)) symbol_regex = '[' + tap_marker + untap_marker + ']' def to_symbols(s): jsymstrs = re.findall(json_symbol_regex, s) for jsymstr in sorted(jsymstrs, lambda x,y: cmp(len(x), len(y)), reverse = True): s = s.replace(jsymstr, json_symbol_trans[jsymstr]) return s def from_symbols(s, for_forum = False, for_html = False): symstrs = re.findall(symbol_regex, s) #for symstr in sorted(symstrs, lambda x,y: cmp(len(x), len(y)), reverse = True): # We have to do the right thing here, because the thing we replace exists in the thing # we replace it with... for symstr in set(symstrs): if for_html: s = s.replace(symstr, symbol_html_trans[symstr]) elif for_forum: s = s.replace(symstr, symbol_forum_trans[symstr]) else: s = s.replace(symstr, symbol_trans[symstr]) return s unletters_regex = r"[^abcdefghijklmnopqrstuvwxyz']"