#!/usr/bin/env python3.8 # -*- coding: utf-8 -*- ################################################### ## Evgeny Muravjev Typograph, http://mdash.ru ## ## Version: 3.5-py ## ## Release Date: Jyly 2, 2015 ## ## Authors: Evgeny Muravjev & Alexander Drutsa ## ## ## ## Adaptation for Python 3.x: January 13, 2022 ## ## Author of adaptation: Sergey Erjemin ## ################################################### import re import base64 import binascii LAYOUT_STYLE = 1 LAYOUT_CLASS = 2 INTERNAL_BLOCK_OPEN = '%%%INTBLOCKO235978%%%' INTERNAL_BLOCK_CLOSE = '%%%INTBLOCKC235978%%%' # static (TO BE DONE: protected) _typographSpecificTagId = False class _EMT_Lib: _charsTable = { '"': {'html': {'«', '»', '”', '‘', '„', '“', '"', '«', '»'}, 'utf8': {0x201E, 0x201C, 0x201F, 0x201D, 0x00AB, 0x00BB}}, ' ': {'html': {' ', ' ', ' '}, 'utf8': {0x00A0, 0x2002, 0x2003, 0x2008, 0x2009}}, '-': {'html': {'–', '−', '—', '—', '–'}, # '—', 'utf8': {0x002D, 0x2010, 0x2012, 0x2013}}, # 0x2014, '—': {'html': {'—'}, 'utf8': {0x2014}}, '==': {'html': {'≡'}, 'utf8': {0x2261}}, '...': {'html': {'…', '…'}, 'utf8': {0x2026}}, '!=': {'html': {'≠', '≠'}, 'utf8': {0x2260}}, '<=': {'html': {'≤', '≤'}, 'utf8': {0x2264}}, '>=': {'html': {'≥', '≥'}, 'utf8': {0x2265}}, '1/2': {'html': {'½', '½'}, 'utf8': {0x00BD}}, '1/4': {'html': {'¼', '¼'}, 'utf8': {0x00BC}}, '3/4': {'html': {'¾', '¾'}, 'utf8': {0x00BE}}, '+/-': {'html': {'±', '±'}, 'utf8': {0x00B1}}, '&': {'html': {'&', '&'}}, '(tm)': {'html': {'™', '™'}, 'utf8': {0x2122}}, # '(r)' : {'html' : {'®', '®', '®'}, '(r)': {'html': {'®', '®'}, 'utf8': {0x00AE}}, '(c)': {'html': {'©', '©'}, 'utf8': {0x00A9}}, '(P)': {'html': {'Ⓟ'}, 'utf8': {0x24C5}}, # знак "копирайт на фонограмму" '(p)': {'html': {'ⓟ'}, 'utf8': {0x24DF}}, # знак "копирайт на фонограмму" '§': {'html': {'§', '§'}, 'utf8': {0x00A7}}, '`': {'html': {'́'}}, '\'': {'html': {'’', '’'}}, 'x': {'html': {'×', '×'}, 'utf8': {'×'}}, # ????? ?? ? ???? ????? ???? ???? 'Р.': {'html': {'₽'}, 'utf8': {0x20BD}}, # знак рубля } # Добавление к тегам атрибута 'id', благодаря которому # при повторном типографировании текста будут удалены теги, # Расставленные данным типографом # Удаление кодов HTML из текста # # # // Remove UTF-8 chars: # $str = EMT_Lib::clear_special_chars('your text', 'utf8'); # // ... or HTML codes only: # $str = EMT_Lib::clear_special_chars('your text', 'html'); # // ... or combo: # $str = EMT_Lib::clear_special_chars('your text'); # # # @param string $text # @param mixed $mode # @return string|bool # / # static public def clear_special_chars(self, text: str, mode=None): if isinstance(mode, str): mode = [mode] if mode is None: mode = ['utf8', 'html'] if not (isinstance(mode, (list, tuple)) and not isinstance(mode, str)): return False moder = [] for mod in mode: if mod in ['utf8', 'html']: moder.append(mod) if len(moder) == 0: return False for char in self._charsTable: vals = self._charsTable[char] for code_type in mode: if code_type in vals: for v in vals[code_type]: if 'utf8' == code_type and isinstance(v, int): v = chr(v) if 'html' == code_type: if re.search(r"<[a-z]+>", v, re.I): # OK v = self.safe_tag_chars(v, True) text = text.replace(v, char) # OK return text # NOTUSED # Удаление тегов HTML из текста # Тег
будет преобразован в перенос строки \n, сочетание тегов

- # в двойной перенос # # @param string $text # @param array $allowableTag массив из тегов, которые будут проигнорированы # @return string # / def remove_html_tags(self, text, allowable_tag=None): ignore = None if allowable_tag is not None: if isinstance(allowable_tag): allowable_tag = [allowable_tag] if (not (isinstance(allowable_tag, (list, tuple)) and not isinstance(allowable_tag, str))): tags = [] for tag in allowable_tag: if '<' != tag[0:1] or '>' != tag[-1]: # OK continue if '/' == tag[1:1]: # OK continue tags.append(tag) ignore = ''.join('', tags) # OK text = re.sub('\', "\n", text, 0, re.I) # OK text = re.sub('\\s*\', "\n\n", text) # OK # text = strip_tags(text, ignore) #TODO return text # Сохраняем содержимое тегов HTML # # Тег 'a' кодируется со специальным префиксом для дальнейшей # возможности выносить за него кавычки. # # @param string $text # @param bool $safe # @return string # / def safe_tag_chars(self, text, way): if way: # OK: text = re.sub('(\]+?)(\>)', lambda m: m.group(0) if ( len(m.group(1)) == 1 and m.group(2).strip()[0:1] == '-' and m.group(2).strip()[ 1:2] != '-') else ( m.group(1) + ("%%___" if m.group(2).strip()[0:1] == 'a' else "") + EMT_Lib.encrypt_tag( m.group(2).strip()) + m.group(3)), text, 0, re.S | re.U) else: # OK: text = re.sub('(\]+?)(\>)', lambda m: m.group(0) if ( len(m.group(1)) == 1 and m.group(2).strip()[0:1] == '-' and m.group(2).strip()[ 1:2] != '-') else (m.group(1) + ( EMT_Lib.decrypt_tag(m.group(2).strip()[4:]) if m.group(2).strip()[ 0:3] == '%%___' else EMT_Lib.decrypt_tag( m.group(2).strip())) + m.group(3)), text, 0, re.S | re.U) return text # Декодирует спец блоки # # @param string $text # @return string # / def decode_internal_blocks(self, text): # Раньше было так: # text = re.sub(INTERNAL_BLOCK_OPEN+'([a-zA-Z0-9/=]+?)'+INTERNAL_BLOCK_CLOSE, # lambda m: EMT_Lib.decrypt_tag(m.group(1)), text, 0, re.S) # Стало так: return text.replace(INTERNAL_BLOCK_OPEN, '').replace(INTERNAL_BLOCK_CLOSE, '\n') # Кодирует спец блок # # @param string $text # @return string # / def iblock(self, text): return INTERNAL_BLOCK_OPEN + EMT_Lib.encrypt_tag(text) + INTERNAL_BLOCK_CLOSE # Создание тега с защищенным содержимым # # @param string $content текст, который будет обрамлен тегом # @param string $tag тэг # @param array $attribute список атрибутов, где ключ - имя атрибута, а значение - само значение данного атрибута # @return string # / # static public def build_safe_tag(self, content, tag='span', attribute={}, layout=LAYOUT_STYLE): # TODO: attribute - list or dict ?? html_tag = tag if _typographSpecificTagId: if not 'id' in attribute: attribute['id'] = 'emt-2' + mt_rand(1000, 9999) # TODO classname = "" if len(attribute): if layout & LAYOUT_STYLE: if '__style' in attribute and attribute['__style']: if 'style' in attribute and attribute['style']: st = attribute['style'].strip() # TODO if st[-1] != ";": # OK st += ";" st += attribute['__style'] attribute['style'] = st else: attribute['style'] = attribute['__style'] del attribute['__style'] for attr in attribute: value = attribute[attr] if attr == "__style": continue if attr == "class": classname = str(value) continue html_tag += f" {str(attr)}=\"{str(value)}\"" if (layout & LAYOUT_CLASS) and classname: html_tag += f" class=\"{classname}\"" return f"<{EMT_Lib.encrypt_tag(html_tag)}>{content}" # Метод, осуществляющий кодирование (сохранение) информации # с целью невозможности типографировать её # # @param string $text # @return string # / def encrypt_tag(self, text): return str(base64.b64encode(text.encode('utf-8'))) # TODO # Метод, осуществляющий декодирование информации # # @param string $text # @return string # / def decrypt_tag(self, text): # Костыль для совместимости с Python 3.4+ (до конца не оттестирован, но вроде работает) try: result = base64.b64decode(text + "===").decode("utf-8") # для коротких base64 except (binascii.Error, UnicodeDecodeError, ): result = base64.b64decode(text[2:-1] + '===').decode("utf-8") return result # TODO def strpos_ex(self, haystack, needle, offset=None): # TODO: &$haystack - '&' couldn't work if isinstance(needle, (list, tuple)) and not isinstance(needle, str): m = -1 w = -1 for n in needle: p = haystack.find(n, offset) # TODO if p == -1: continue if m == -1: m = p w = n continue if p < m: m = p w = n if m == -1: return False return {'pos': m, 'str': w} return haystack.find(needle, offset) # TODO def process_selector_pattern(self, pattern): # TODO: &$pattern - '&' couldn't work if pattern == False: return False # pattern = preg_quote(pattern , '/') #TODO pattern = pattern.replace("*", "[a-z0-9_\-]*") # TODO return pattern def test_pattern(self, pattern, text): if pattern == False or pattern == None: return True return re.match(pattern, text) # TODO def strtolower(self, string): return string.lower() # взято с http://www.w3.org/TR/html4/sgml/entities.html html4_char_ents = { 'nbsp': 160, 'iexcl': 161, 'cent': 162, 'pound': 163, 'curren': 164, 'yen': 165, 'brvbar': 166, 'sect': 167, 'uml': 168, 'copy': 169, 'ordf': 170, 'laquo': 171, 'not': 172, 'shy': 173, 'reg': 174, 'macr': 175, 'deg': 176, 'plusmn': 177, 'sup2': 178, 'sup3': 179, 'acute': 180, 'micro': 181, 'para': 182, 'middot': 183, 'cedil': 184, 'sup1': 185, 'ordm': 186, 'raquo': 187, 'frac14': 188, 'frac12': 189, 'frac34': 190, 'iquest': 191, 'Agrave': 192, 'Aacute': 193, 'Acirc': 194, 'Atilde': 195, 'Auml': 196, 'Aring': 197, 'AElig': 198, 'Ccedil': 199, 'Egrave': 200, 'Eacute': 201, 'Ecirc': 202, 'Euml': 203, 'Igrave': 204, 'Iacute': 205, 'Icirc': 206, 'Iuml': 207, 'ETH': 208, 'Ntilde': 209, 'Ograve': 210, 'Oacute': 211, 'Ocirc': 212, 'Otilde': 213, 'Ouml': 214, 'times': 215, 'Oslash': 216, 'Ugrave': 217, 'Uacute': 218, 'Ucirc': 219, 'Uuml': 220, 'Yacute': 221, 'THORN': 222, 'szlig': 223, 'agrave': 224, 'aacute': 225, 'acirc': 226, 'atilde': 227, 'auml': 228, 'aring': 229, 'aelig': 230, 'ccedil': 231, 'egrave': 232, 'eacute': 233, 'ecirc': 234, 'euml': 235, 'igrave': 236, 'iacute': 237, 'icirc': 238, 'iuml': 239, 'eth': 240, 'ntilde': 241, 'ograve': 242, 'oacute': 243, 'ocirc': 244, 'otilde': 245, 'ouml': 246, 'divide': 247, 'oslash': 248, 'ugrave': 249, 'uacute': 250, 'ucirc': 251, 'uuml': 252, 'yacute': 253, 'thorn': 254, 'yuml': 255, 'fnof': 402, 'Alpha': 913, 'Beta': 914, 'Gamma': 915, 'Delta': 916, 'Epsilon': 917, 'Zeta': 918, 'Eta': 919, 'Theta': 920, 'Iota': 921, 'Kappa': 922, 'Lambda': 923, 'Mu': 924, 'Nu': 925, 'Xi': 926, 'Omicron': 927, 'Pi': 928, 'Rho': 929, 'Sigma': 931, 'Tau': 932, 'Upsilon': 933, 'Phi': 934, 'Chi': 935, 'Psi': 936, 'Omega': 937, 'alpha': 945, 'beta': 946, 'gamma': 947, 'delta': 948, 'epsilon': 949, 'zeta': 950, 'eta': 951, 'theta': 952, 'iota': 953, 'kappa': 954, 'lambda': 955, 'mu': 956, 'nu': 957, 'xi': 958, 'omicron': 959, 'pi': 960, 'rho': 961, 'sigmaf': 962, 'sigma': 963, 'tau': 964, 'upsilon': 965, 'phi': 966, 'chi': 967, 'psi': 968, 'omega': 969, 'thetasym': 977, 'upsih': 978, 'piv': 982, 'bull': 8226, 'hellip': 8230, 'prime': 8242, 'Prime': 8243, 'oline': 8254, 'frasl': 8260, 'weierp': 8472, 'image': 8465, 'real': 8476, 'trade': 8482, 'alefsym': 8501, 'larr': 8592, 'uarr': 8593, 'rarr': 8594, 'darr': 8595, 'harr': 8596, 'crarr': 8629, 'lArr': 8656, 'uArr': 8657, 'rArr': 8658, 'dArr': 8659, 'hArr': 8660, 'forall': 8704, 'part': 8706, 'exist': 8707, 'empty': 8709, 'nabla': 8711, 'isin': 8712, 'notin': 8713, 'ni': 8715, 'prod': 8719, 'sum': 8721, 'minus': 8722, 'lowast': 8727, 'radic': 8730, 'prop': 8733, 'infin': 8734, 'ang': 8736, 'and': 8743, 'or': 8744, 'cap': 8745, 'cup': 8746, 'int': 8747, 'there4': 8756, 'sim': 8764, 'cong': 8773, 'asymp': 8776, 'ne': 8800, 'equiv': 8801, 'le': 8804, 'ge': 8805, 'sub': 8834, 'sup': 8835, 'nsub': 8836, 'sube': 8838, 'supe': 8839, 'oplus': 8853, 'otimes': 8855, 'perp': 8869, 'sdot': 8901, 'lceil': 8968, 'rceil': 8969, 'lfloor': 8970, 'rfloor': 8971, 'lang': 9001, 'rang': 9002, 'loz': 9674, 'spades': 9824, 'clubs': 9827, 'hearts': 9829, 'diams': 9830, 'quot': 34, 'amp': 38, 'lt': 60, 'gt': 62, 'OElig': 338, 'oelig': 339, 'Scaron': 352, 'scaron': 353, 'Yuml': 376, 'circ': 710, 'tilde': 732, 'ensp': 8194, 'emsp': 8195, 'thinsp': 8201, 'zwnj': 8204, 'zwj': 8205, 'lrm': 8206, 'rlm': 8207, 'ndash': 8211, 'mdash': 8212, 'lsquo': 8216, 'rsquo': 8217, 'sbquo': 8218, 'ldquo': 8220, 'rdquo': 8221, 'bdquo': 8222, 'dagger': 8224, 'Dagger': 8225, 'permil': 8240, 'lsaquo': 8249, 'rsaquo': 8250, 'euro': 8364, } # Вернуть юникод символ по html entinty # # @param string $entity # @return string # / def html_char_entity_to_unicode(self, entity): if EMT_Lib.html4_char_ents.get(entity): return chr(EMT_Lib.html4_char_ents[entity]) return False # Конвериторвать все html entity в соответсвующее юникод символы # # @param string $text # / def convert_html_entities_to_unicode(self, text): # TODO: &$text - '&' couldn't work text = re.sub(r"\&#([0-9]+)\;", lambda m: chr(int(m.group(1))), text) # TODO text = re.sub(r"\&#x([0-9A-F]+)\;", lambda m: chr(int(m.group(1), 16)), text) # TODO text = re.sub(r"\&([a-zA-Z0-9]+)\;", lambda m: EMT_Lib.html_char_entity_to_unicode(m.group(1)) if EMT_Lib.html_char_entity_to_unicode( m.group(1)) else m.group(0), text) # TODO return text def process_preg_replacement(self, r): return re.sub(r'\\\\([0-9]+)', r'\\\\g<\g<1>>', r, 0, re.U) def parse_preg_pattern(self, pattern): es = pattern[0:1] modifiers = pattern.split(es).pop() b = {'i': re.I, 's': re.S, 'm': re.M, 'u': re.U} flags = re.U x_eval = False for i in modifiers: if i in b: flags |= b[i] if i == 'e': x_eval = True new_pattern = pattern[1:-1 - len(modifiers)] new_pattern = new_pattern.replace('\\' + es, es) return {'pattern': new_pattern, 'flags': flags, 'eval': x_eval} def preg_replace_one(self, pattern, replacement, text): p = EMT_Lib.parse_preg_pattern(pattern) if not p['eval']: return re.sub(p['pattern'], EMT_Lib.process_preg_replacement(replacement), text, 0, p['flags']) loc = locals() exec("f = lambda m: " + replacement, globals(), loc) return re.sub(p['pattern'], loc['f'], text, 0, p['flags']) def preg_replace(self, pattern, replacement, text): if isinstance(pattern, str): return EMT_Lib.preg_replace_one(pattern, replacement, text) for k, i in enumerate(pattern): if isinstance(replacement, str): repl = replacement else: repl = replacement[k] text = EMT_Lib.preg_replace_one(i, repl, text) return text def preg_replace_ex(self, pattern, replacement, text, cycled=False): while True: texto = text text = EMT_Lib.preg_replace(pattern, replacement, text) if not cycled: break if text == texto: break return text def str_replace_one(self, pattern, replacement, text): return text.replace(pattern, replacement) def str_replace(self, pattern, replacement, text): if isinstance(pattern, str): return EMT_Lib.str_replace_one(pattern, replacement, text) for k, i in enumerate(pattern): if isinstance(replacement, str): repl = replacement else: repl = replacement[k] text = EMT_Lib.str_replace_one(i, repl, text) return text def str_ireplace_one(self, pattern, replacement, text): return re.sub(re.escape(pattern), lambda m: replacement, text, 0, re.I) # return re.sub(re.escape(pattern), re.escape(replacement), text, 0, re.I) def str_ireplace(self, pattern, replacement, text): if isinstance(pattern, str): return EMT_Lib.str_ireplace_one(pattern, replacement, text) for k, i in enumerate(pattern): if isinstance(replacement, str): repl = replacement else: repl = replacement[k] text = EMT_Lib.str_ireplace_one(i, repl, text) return text def substr(self, s, start, length=None): if len(s) <= start: return "" if length is None: return s[start:] elif length == 0: return "" elif length > 0: return s[start:start + length] else: return s[start:length] def ifop(self, cond, ontrue, onfalse): return ontrue if cond else onfalse def re_sub(self, pattern, replacement, string, count, flags): # ЭТОТ ТРЮК БЫЛ НУЖЕН ДЛЯ PYTHON 2.7.x -- 3.4.x # def _r(m): # # Now this is ugly. # # Python has a "feature" where unmatched groups return None # # then re.sub chokes on this. # # see http://bugs.python.org/issue1519638 # # this works around and hooks into the internal of the re module... # # the match object is replaced with a wrapper that # # returns "" instead of None for unmatched groups # class _m(): # def __init__(self, m): # self.m=m # self.string=m.string # def group(self, n): # return m.group(n) or "" # # return re._expand(pattern, _m(m), replacement) # return re.sub(pattern, _r, string, count, flags) return re.sub(pattern, replacement, string, count, flags) def split_number(self, num): repl = "" for i in range(len(num), -1, -3): if i - 3 >= 0: repl = (" " if i > 3 else "") + num[i - 3:i] + repl else: repl = num[0:i] + repl return repl EMT_Lib = _EMT_Lib() BASE64_PARAGRAPH_TAG = 'cA===' BASE64_BREAKLINE_TAG = 'YnIgLw===' BASE64_NOBR_OTAG = 'bm9icg===' BASE64_NOBR_CTAG = 'L25vYnI==' QUOTE_FIRS_OPEN = '«' QUOTE_FIRS_CLOSE = '»' QUOTE_CRAWSE_OPEN = '„' QUOTE_CRAWSE_CLOSE = '“' # /* # * Базовый класс для группы правил обработки текста # * Класс группы должен наследовать, данный класс и задавать # * в нём EMT_Tret::rules и EMT_Tret::$name # * # */ class EMT_Tret: # # Набор правил в данной группе, который задан изначально # Его можно менять динамически добавляя туда правила с помощью put_rule # # @var unknown_type # def __init__(self): self.rules = {} self.rule_order = [] self.title = "" self.disabled = {} self.enabled = {} self._text = '' self.logging = False self.logs = [] self.errors = [] self.debug_enabled = False self.debug_info = [] self.use_layout = False self.use_layout_set = False self.class_layout_prefix = False self.class_names = {} self.classes = {} self.settings = {} self.intrep = "" def log(self, str, data=None): if not self.logging: return self.logs.append({'info': str, 'data': data}) def error(self, info, data=None): self.errors.append({'info': info, 'data': data}) self.log('ERROR: ' + info, data) def debug(self, place, after_text): if not self.debug_info: return self.debug_info.append({'place': place, 'text': after_text}) # /** # * Установить режим разметки для данного Трэта если не было раньше установлено, # * EMT_Lib::LAYOUT_STYLE - с помощью стилей # * EMT_Lib::LAYOUT_CLASS - с помощью классов # * # * @param int $kind # */ def set_tag_layout_ifnotset(self, layout): if self.use_layout_set: return self.use_layout = layout # /** # * Установить режим разметки для данного Трэта, # * EMT_Lib::LAYOUT_STYLE - с помощью стилей # * EMT_Lib::LAYOUT_CLASS - с помощью классов # * EMT_Lib::LAYOUT_STYLE|EMT_Lib::LAYOUT_CLASS - оба метода # * # * @param int $kind # */ def set_tag_layout(self, layout=LAYOUT_STYLE): self.use_layout = layout self.use_layout_set = True def set_class_layout_prefix(self, prefix): self.class_layout_prefix = prefix def debug_on(self): self.debug_enabled = True def log_on(self): self.debug_enabled = True # def getmethod(self, name): # if not name: return False # if not method_exists(his, $name)) return False; # return array($this, $name); def _pre_parse(self): self.pre_parse() # foreach($this->rules as $rule) # { # if(!isset($rule['init'])) continue; # $m = $this->getmethod($rule['init']); # if(!$m) continue; # call_user_func($m); # } def _post_parse(self): # foreach($this->rules as $rule) # { # if(!isset($rule['deinit'])) continue; # $m = $this->getmethod($rule['deinit']); # if(!$m) continue; # call_user_func($m); # } self.post_parse() def intrepfun(self, m): loc = locals() exec('x = ' + self.intrep + '', globals(), loc) return loc['x'] def preg_replace_one(self, pattern, replacement, text): p = EMT_Lib.parse_preg_pattern(pattern) if not p['eval']: # print(p['pattern']) # print(EMT_Lib.process_preg_replacement(replacement))s # EMT_Lib.process_preg_replacement return EMT_Lib.re_sub(p['pattern'], replacement, text, 0, p['flags']) self.intrep = replacement return re.sub(p['pattern'], self.intrepfun, text, 0, p['flags']) def preg_replace(self, pattern, replacement, text): if isinstance(pattern, str): return self.preg_replace_one(pattern, replacement, text) for k, i in enumerate(pattern): if isinstance(replacement, str): repl = replacement else: repl = replacement[k] text = self.preg_replace_one(i, repl, text) return text def preg_replace_ex(self, pattern, replacement, text, cycled=False): while True: texto = text text = self.preg_replace(pattern, replacement, text) if not cycled: break if text == texto: break return text # def rule_order_sort(self, $a, $b): # if($a['order'] == $b['order']) return 0; # if($a['order'] < $b['order']) return -1; # return 1; def apply_rule(self, rule): name = rule['id'] disabled = self.disabled.get(rule['id']) or (rule.get('disabled') and not self.enabled.get(rule['id'])) if disabled: self.log("Правило $name", "Правило отключено" + " (по умолчанию)" if self.disabled.get(rule['id']) else "") return if rule.get('function'): if not rule.get('pattern'): if rule['function'] in dir(self): self.log("Правило " + name, "Используется метод " + rule['function'] + " в правиле") getattr(self, rule['function'])() return if rule['function'] in globals(): self.log("Правило " + name, "Используется функция " + rule['function'] + " в правиле") globals()[rule['function']]() return self.error('Функция ' + rule['function'] + ' из правила ' + rule['id'] + " не найдена") return else: if re.match("^[a-z_0-9]+$", rule['function'], re.I): p = EMT_Lib.parse_preg_pattern(rule['pattern']) if rule['function'] in dir(self): self.log("Правило " + name, "Замена с использованием preg_replace_callback с методом " + rule['function']) self._text = re.sub(p['pattern'], getattr(self, rule['function']), self._text, 0, p['flags']) return if rule['function'] in globals(): self.log("Правило " + name, "Замена с использованием preg_replace_callback с функцией " + rule[ 'function'] + " в правиле") self._text = re.sub(p['pattern'], globals()[rule['function']], self._text, 0, p['flags']) return self.error('Функция ' + rule['function'] + ' из правила ' + rule['id'] + " не найдена") else: self.preg_replace(rule['pattern'] + 'e', rule['function'], self._text) self.log('Замена с использованием preg_replace_callback с инлайн функцией из правила ' + rule['id']) return return if rule.get('simple_replace'): if rule.get('case_sensitive'): self.log("Правило " + name, "Простая замена с использованием str_replace") self._text = EMT_Lib.str_replace(rule['pattern'], rule['replacement'], self._text) return self.log("Правило " + name, "Простая замена с использованием str_ireplace") self._text = EMT_Lib.str_ireplace(rule['pattern'], rule['replacement'], self._text) return cycled = False if rule.get('cycled'): cycled = True pattern = rule['pattern'] # p = EMT_Lib.parse_preg_pattern(pattern) # if isinstance(pattern, basestring): # pattern = [pattern] # if not p['eval']: # self.log("Правило "+name, "Замена с использованием preg_replace") # self._text = EMT_Lib.preg_replace_ex( rule['pattern'], rule['replacement'], self._text, cycled ) # return self.log("Правило " + name, "Замена с использованием preg_replace или preg_replace_callback вместо eval") self._text = self.preg_replace_ex(rule['pattern'], rule['replacement'], self._text, cycled) def _apply(self, xlist): self.errors = [] self._pre_parse() self.log("Применяется набор правил", ','.join(xlist)) rulelist = [] for k in xlist: rule = self.rules[k] rule['id'] = k if 'order' not in rule: rule['order'] = 5 rulelist.append(rule) for rule in rulelist: self.apply_rule(rule) self.debug(rule['id'], self._text) self._post_parse() # /** # * Создание защищенного тега с содержимым # * # * @see EMT_lib::build_safe_tag # * @param string $content # * @param string $tag # * @param array $attribute # * @return string # */ def tag(self, content, tag='span', attribute={}): if 'class' in attribute: classname = attribute['class'] if classname == "nowrap": if not self.is_on('nowrap'): tag = "nobr" attribute = {} classname = "" if classname in self.classes: style_inline = self.classes[classname] if style_inline: attribute['__style'] = style_inline if classname in self.class_names: classname = class_names(classname) classname = (self.class_layout_prefix if self.class_layout_prefix else "") + classname attribute['class'] = classname layout = LAYOUT_STYLE if self.use_layout: layout = self.use_layout return EMT_Lib.build_safe_tag(content, tag, attribute, layout) # /** # * Добавить правило в группу # * # * @param string $name # * @param array $params # */ def put_rule(self, name, params): self.rules[name] = params return self # /** # * Отключить правило, в обработке # * # * @param string $name # */ def disable_rule(self, name): self.disabled[name] = True if name in self.enabled: del self.enabled[name] # /** # * Включить правило # * # * @param string $name # */ def enable_rule(self, name): self.enabled[name] = True if name in self.disabled: del self.disabled[name] # /** # * Добавить настройку в трет # * # * @param string $key ключ # * @param mixed $value значение # */ def set(self, key, value): self.settings[key] = value # /** # * Установлена ли настройка # * # * @param string $key # */ def is_on(self, key): if key not in self.settings: return False kk = self.settings[key] if isinstance(kk, str) and kk.lower() == "on": return True if isinstance(kk, str) and kk == "1": return True if isinstance(kk, bool) and kk: return True if isinstance(kk, int) and kk == 1: return True return False # /** # * Получить строковое значение настройки # * # * @param unknown_type $key # * @return unknown # */ def ss(self, key): if key not in self.settings: return "" return self.settings[key] # /** # * Добавить настройку в правило # * # * @param string $rule_name идентификатор правила # * @param string $key ключ # * @param mixed $value значение # */ def set_rule(self, rule_name, key, value): if rule_name not in self.rules: self.rules[rule_name] = {} self.rules[rule_name][key] = value # /** # * Включить правила, согласно списку # * # * @param array $list список правил # * @param boolean $disable выключить их или включить # * @param boolean $strict строго, т.е. те которые не в списку будут тоже обработаны # */ def activate(self, xlist, disable=False, x_strict=True): for rule_name in xlist: if disable: self.disable_rule(rule_name) else: self.enable_rule(rule_name) if x_strict: for rule_name in self.rules: y = self.rules[rule_name] if rule_name in xlist: continue if not disable: self.disable_rule(rule_name) else: self.enable_rule(rule_name) def set_text(self, text): self._text = text self.debug_info = [] self.logs = [] # /** # * Применить к тексту # * # * @param string $text - текст к которому применить # * @param mixed $list - список правил, null - все правила # * @return string # */ def apply(self, xlist=None): if isinstance(xlist, str): rlist = [xlist] elif isinstance(xlist, (list, tuple)): rlist = xlist else: rlist = self.rule_order self._apply(rlist) return self._text # /** # * Код, выполняем до того, как применить правила # * # */ def pre_parse(self): return # /** # * После выполнения всех правил, выполняется этот метод # * # */ def post_parse(self): return # EMT_Lib.preg_replace('/aaa/msi', 'bbb', 'xxx aaa yyy') ####################################################### # EMT_Tret_Quote ####################################################### class EMT_Tret_Quote(EMT_Tret): def __init__(self): EMT_Tret.__init__(self) self.title = "Кавычки" self.rules = { "quotes_outside_a": { "description": "Кавычки вне тэга ", "pattern": "/(\\<%%\\_\\_[^\\>]+\\>)\\\"(.+?)\\\"(\\<\\/%%\\_\\_[^\\>]+\\>)/s", "replacement": "\"\\1\\2\\3\"" }, "open_quote": { "description": "Открывающая кавычка", "pattern": "/(^|\\(|\\s|\\>|-)((\\\"|\\\\\")+)(\\S+)/iue", "replacement": "m.group(1) + QUOTE_FIRS_OPEN * ( m.group(2).count(u\"\\\"\") ) + m.group(4)" }, "close_quote": { "description": "Закрывающая кавычка", "pattern": "/([a-zа-яё0-9]|\\.|\\&hellip\\;|\\!|\\?|\\>|\\)|\\:|\\+|\\%|\\@|\\#|\\$|\\*)((\\\"|\\\\\")+)(\\.|\\&hellip\\;|\\;|\\:|\\?|\\!|\\,|\\s|\\)|\\<\\/|\\<|$)/uie", "replacement": "m.group(1) + QUOTE_FIRS_CLOSE * ( m.group(2).count(u\"\\\"\") ) + m.group(4)" }, "close_quote_adv": { "description": "Закрывающая кавычка особые случаи", "pattern": [ "/([a-zа-яё0-9]|\\.|\\&hellip\\;|\\!|\\?|\\>|\\)|\\:|\\+|\\%|\\@|\\#|\\$|\\*)((\\\"|\\\\\"|\\«\\;)+)(\\<[^\\>]+\\>)(\\.|\\&hellip\\;|\\;|\\:|\\?|\\!|\\,|\\)|\\<\\/|$| )/uie", "/([a-zа-яё0-9]|\\.|\\&hellip\\;|\\!|\\?|\\>|\\)|\\:|\\+|\\%|\\@|\\#|\\$|\\*)(\\s+)((\\\"|\\\\\")+)(\\s+)(\\.|\\&hellip\\;|\\;|\\:|\\?|\\!|\\,|\\)|\\<\\/|$| )/uie", "/\\>(\\«\\;)\\.($|\\s|\\<)/ui", "/\\>(\\«\\;),($|\\s|\\<|\\S)/ui", "/\\>(\\«\\;):($|\\s|\\<|\\S)/ui", "/\\>(\\«\\;);($|\\s|\\<|\\S)/ui", "/\\>(\\«\\;)\\)($|\\s|\\<|\\S)/ui", "/((\\\"|\\\\\")+)$/uie" ], "replacement": [ "m.group(1) + QUOTE_FIRS_CLOSE * ( m.group(2).count(u\"\\\"\")+m.group(2).count(u\"«\") ) + m.group(4)+ m.group(5)", "m.group(1) +m.group(2)+ QUOTE_FIRS_CLOSE * ( m.group(3).count(u\"\\\"\")+m.group(3).count(u\"«\") ) + m.group(5)+ m.group(6)", ">».\\2", ">»,\\2", ">»:\\2", ">»;\\2", ">»)\\2", "QUOTE_FIRS_CLOSE * ( m.group(1).count(u\"\\\"\") )" ] }, "open_quote_adv": { "description": "Открывающая кавычка особые случаи", "pattern": "/(^|\\(|\\s|\\>)(\\\"|\\\\\")(\\s)(\\S+)/iue", "replacement": "m.group(1) + QUOTE_FIRS_OPEN +m.group(4)" }, "close_quote_adv_2": { "description": "Закрывающая кавычка последний шанс", "pattern": "/(\\S)((\\\"|\\\\\")+)(\\.|\\&hellip\\;|\\;|\\:|\\?|\\!|\\,|\\s|\\)|\\<\\/|\\<|$)/uie", "replacement": "m.group(1) + QUOTE_FIRS_CLOSE * ( m.group(2).count(u\"\\\"\") ) + m.group(4)" }, "quotation": { "description": "Внутренние кавычки-лапки и дюймы", "function": "build_sub_quotations" } } self.rule_order = [ "quotes_outside_a", "open_quote", "close_quote", "close_quote_adv", "open_quote_adv", "close_quote_adv_2", "quotation" ] def inject_in(self, pos, text, chtext): chtext = (chtext[0:pos] if pos > 0 else '') + text + chtext[pos + len(text):] return chtext def build_sub_quotations(self): global __ax, __ay exp = "" if self._text.find("") >= 0 else ( "\r\n\r\n" if self._text.find("\r\n") >= 0 else "\n\n") texts_in = self._text.split(exp) texts_out = [] for textx in texts_in: okposstack = [0] okpos = 0 level = 0 off = 0 while True: p = EMT_Lib.strpos_ex(textx, ["«", "»"], off) if isinstance(p, bool) and (p == False): break if p['str'] == "«": if (level > 0) and (not self.is_on('no_bdquotes')): textx = self.inject_in(p['pos'], QUOTE_CRAWSE_OPEN, textx) # TODO::: WTF self::QUOTE_CRAWSE_OPEN ??? level += 1; if p['str'] == "»": level -= 1 if (level > 0) and (not self.is_on('no_bdquotes')): textx = self.inject_in(p['pos'], QUOTE_CRAWSE_CLOSE, textx) # TODO::: WTF self::QUOTE_CRAWSE_OPEN ??? off = p['pos'] + len(p['str']) if level == 0: okpos = off okposstack.append(okpos) elif level < 0: # // уровень стал меньше нуля if not self.is_on('no_inches'): while True: lokpos = okposstack.pop(len(okposstack) - 1) k = EMT_Lib.substr(textx, lokpos, off - lokpos) k = EMT_Lib.str_replace(QUOTE_CRAWSE_OPEN, QUOTE_FIRS_OPEN, k) k = EMT_Lib.str_replace(QUOTE_CRAWSE_CLOSE, QUOTE_FIRS_CLOSE, k) # //$k = preg_replace("/(^|[^0-9])([0-9]+)\»\;/ui", '\1\2″', $k, 1, $amount); amount = 0 m = re.findall("(^|[^0-9])([0-9]+)\»\;", k, re.I | re.U) __ax = len(m) __ay = 0 if __ax: def quote_extra_replace_function(m): global __ax, __ay __ay += 1 if __ay == __ax: return m.group(1) + m.group(2) + "″" return m.group(0) k = re.sub("(^|[^0-9])([0-9]+)\»\;", quote_extra_replace_function, k, 0, re.I | re.U); amount = 1 if not ((amount == 0) and len(okposstack)): break # // успешно сделали замену if amount == 1: # // заново просмотрим содержимое textx = EMT_Lib.substr(textx, 0, lokpos) + k + EMT_Lib.substr(textx, off) off = lokpos level = 0 continue # // иначе просто заменим последнюю явно на " от отчаяния if amount == 0: # // говорим, что всё в порядке level = 0 textx = EMT_Lib.substr(textx, 0, p['pos']) + '"' + EMT_Lib.substr(textx, off) off = p['pos'] + len('"') okposstack = [off] continue # // не совпало количество, отменяем все подкавычки if level != 0: # // закрывающих меньше, чем надо if level > 0: k = EMT_Lib.substr(textx, okpos) k = EMT_Lib.str_replace(QUOTE_CRAWSE_OPEN, QUOTE_FIRS_OPEN, k) k = EMT_Lib.str_replace(QUOTE_CRAWSE_CLOSE, QUOTE_FIRS_CLOSE, k) textx = EMT_Lib.substr(textx, 0, okpos) + k texts_out.append(textx) self._text = exp.join(texts_out) ####################################################### # EMT_Tret_Dash ####################################################### class EMT_Tret_Dash(EMT_Tret): def __init__(self): EMT_Tret.__init__(self) self.title = "Дефисы и тире" self.rules = { "mdash_symbol_to_html_mdash": { "description": "Замена символа тире на html конструкцию", "pattern": "/—/iu", "replacement": "—" }, "mdash": { "description": "Тире после кавычек, скобочек, пунктуации", "pattern": [ "/([a-zа-яё0-9]+|\\,|\\:|\\)|\\&(ra|ld)quo\\;|\\|\\\"|\\>)(\\040|\\t)(—|\\-|\\&mdash\\;)(\\s|$|\\<)/ui", "/(\\,|\\:|\\)|\\\")(—|\\-|\\&mdash\\;)(\\s|$|\\<)/ui" ], "replacement": [ "\\1 —\\5", "\\1 —\\3" ] }, "mdash_2": { "description": "Тире после переноса строки", "pattern": "/(\\n|\\r|^|\\>)(\\-|\\&mdash\\;)(\\t|\\040)/", "replacement": "\\1— " }, "mdash_3": { "description": "Тире после знаков восклицания, троеточия и прочее", "pattern": "/(\\.|\\!|\\?|\\&hellip\\;)(\\040|\\t|\\ \\;)(\\-|\\&mdash\\;)(\\040|\\t|\\ \\;)/", "replacement": "\\1 — " }, "iz_za_pod": { "description": "Расстановка дефисов между из-за, из-под", "pattern": "/(\\s|\\ \\;|\\>|^)(из)(\\040|\\t|\\ \\;)\\-?(за|под)([\\.\\,\\!\\?\\:\\;]|\\040|\\ \\;)/uie", "replacement": "(( u\" \" if m.group(1) == u\" \" else m.group(1))) + m.group(2)+u\"-\"+m.group(4) + (( u\" \" if m.group(5) == u\" \" else m.group(5)))" }, "to_libo_nibud": { "description": "Автоматическая простановка дефисов в обезличенных местоимениях и междометиях", "cycled": True, "pattern": "/(\\s|^|\\ \\;|\\>)(кто|кем|когда|зачем|почему|как|что|чем|где|чего|кого)\\-?(\\040|\\t|\\ \\;)\\-?(то|либо|нибудь)([\\.\\,\\!\\?\\;]|\\040|\\ \\;|$)/uie", "replacement": "(( u\" \" if m.group(1) == u\" \" else m.group(1))) + m.group(2)+u\"-\"+m.group(4) + (( u\" \" if m.group(5) == u\" \" else m.group(5)))" }, "koe_kak": { "description": "Кое-как, кой-кого, все-таки", "cycled": True, "pattern": [ "/(\\s|^|\\ \\;|\\>)(кое)\\-?(\\040|\\t|\\ \\;)\\-?(как)([\\.\\,\\!\\?\\;]|\\040|\\ \\;|$)/uie", "/(\\s|^|\\ \\;|\\>)(кой)\\-?(\\040|\\t|\\ \\;)\\-?(кого)([\\.\\,\\!\\?\\;]|\\040|\\ \\;|$)/uie", "/(\\s|^|\\ \\;|\\>)(вс[её])\\-?(\\040|\\t|\\ \\;)\\-?(таки)([\\.\\,\\!\\?\\;]|\\040|\\ \\;|$)/uie" ], "replacement": "(( u\" \" if m.group(1) == u\" \" else m.group(1))) + m.group(2)+u\"-\"+m.group(4) + (( u\" \" if m.group(5) == u\" \" else m.group(5)))" }, "ka_de_kas": { "description": "Расстановка дефисов с частицами ка, де, кась", "disabled": True, "pattern": [ "/(\\s|^|\\ \\;|\\>)([а-яё]+)(\\040|\\t|\\ \\;)(ка)([\\.\\,\\!\\?\\;]|\\040|\\ \\;|$)/uie", "/(\\s|^|\\ \\;|\\>)([а-яё]+)(\\040|\\t|\\ \\;)(де)([\\.\\,\\!\\?\\;]|\\040|\\ \\;|$)/uie", "/(\\s|^|\\ \\;|\\>)([а-яё]+)(\\040|\\t|\\ \\;)(кась)([\\.\\,\\!\\?\\;]|\\040|\\ \\;|$)/uie" ], "replacement": "(( u\" \" if m.group(1) == u\" \" else m.group(1))) + m.group(2)+u\"-\"+m.group(4) + (( u\" \" if m.group(5) == u\" \" else m.group(5)))" } } self.rule_order = [ "mdash_symbol_to_html_mdash", "mdash", "mdash_2", "mdash_3", "iz_za_pod", "to_libo_nibud", "koe_kak", "ka_de_kas" ] ####################################################### # EMT_Tret_Symbol ####################################################### class EMT_Tret_Symbol(EMT_Tret): def __init__(self): EMT_Tret.__init__(self) self.title = "Специальные символы" self.rules = { "tm_replace": { "description": "Замена (tm) на символ торговой марки", "pattern": "/([\\040\\t])?\\(tm\\)/i", "replacement": "™" }, "r_sign_replace": { "description": "Замена (R) на символ зарегистрированной торговой марки", "pattern": [ "/(.|^)\\(r\\)(.|$)/ie" ], "replacement": [ "m.group(1)+u\"®\"+m.group(2)" ] }, "copy_replace": { "description": "Замена (c) на символ копирайт", "pattern": [ "/\\((c|с)\\)\\s+/iu", "/\\((c|с)\\)($|\\.|,|!|\\?)/iu" ], "replacement": [ "© ", "©\\2" ] }, "apostrophe": { "description": "Расстановка правильного апострофа в текстах", "pattern": "/(\\s|^|\\>|\\&rsquo\\;)([a-zа-яё]{1,})\'([a-zа-яё]+)/ui", "replacement": "\\1\\2’\\3", "cycled": True }, "degree_f": { "description": "Градусы по Фаренгейту", "pattern": "/([0-9]+)F($|\\s|\\.|\\,|\\;|\\:|\\ \\;|\\?|\\!)/eu", "replacement": "u\"\"+self.tag(m.group(1)+u\" °F\",u\"span\", {u\"class\":u\"nowrap\"}) +m.group(2)" }, "euro_symbol": { "description": "Символ евро", "simple_replace": True, "pattern": "€", "replacement": "€" }, "arrows_symbols": { "description": "Замена стрелок вправо-влево на html коды", "pattern": [ "/\\-\\>/", "/\\<\\-/", "/→/u", "/←/u" ], "replacement": [ "→", "←", "→", "←" ] } } self.rule_order = [ "tm_replace", "r_sign_replace", "copy_replace", "apostrophe", "degree_f", "euro_symbol", "arrows_symbols" ] self.classes = { "nowrap": "word-spacing:nowrap;" } ####################################################### # EMT_Tret_Punctmark ####################################################### class EMT_Tret_Punctmark(EMT_Tret): def __init__(self): EMT_Tret.__init__(self) self.title = "Пунктуация и знаки препинания" self.rules = { "auto_comma": { "description": "Расстановка запятых перед а, но", "pattern": "/([a-zа-яё])(\\s| )(но|а)(\\s| )/iu", "replacement": "\\1,\\2\\3\\4" }, "punctuation_marks_limit": { "description": "Лишние восклицательные, вопросительные знаки и точки", "pattern": "/([\\!\\.\\?]){4,}/", "replacement": "\\1\\1\\1" }, "punctuation_marks_base_limit": { "description": "Лишние запятые, двоеточия, точки с запятой", "pattern": "/([\\,]|[\\:]|[\\;]]){2,}/", "replacement": "\\1" }, "hellip": { "description": "Замена трех точек на знак многоточия", "simple_replace": True, "pattern": "...", "replacement": "…" }, "fix_excl_quest_marks": { "description": "Замена восклицательного и вопросительного знаков местами", "pattern": "/([a-zа-яё0-9])\\!\\?(\\s|$|\\<)/ui", "replacement": "\\1?!\\2" }, "fix_pmarks": { "description": "Замена сдвоенных знаков препинания на одинарные", "pattern": [ "/([^\\!\\?])\\.\\./", "/([a-zа-яё0-9])(\\!|\\.)(\\!|\\.|\\?)(\\s|$|\\<)/ui", "/([a-zа-яё0-9])(\\?)(\\?)(\\s|$|\\<)/ui" ], "replacement": [ "\\1.", "\\1\\2\\4", "\\1\\2\\4" ] }, "fix_brackets": { "description": "Лишние пробелы после открывающей скобочки и перед закрывающей", "pattern": [ "/(\\()(\\040|\\t)+/", "/(\\040|\\t)+(\\))/" ], "replacement": [ "\\1", "\\2" ] }, "fix_brackets_space": { "description": "Пробел перед открывающей скобочкой", "pattern": "/([a-zа-яё])(\\()/iu", "replacement": "\\1 \\2" }, "dot_on_end": { "description": "Точка в конце текста, если её там нет", "disabled": True, "pattern": "/([a-zа-яё0-9])(\\040|\\t|\\ \\;)*$/ui", "replacement": "\\1." } } self.rule_order = [ "auto_comma", "punctuation_marks_limit", "punctuation_marks_base_limit", "hellip", "fix_excl_quest_marks", "fix_pmarks", "fix_brackets", "fix_brackets_space", "dot_on_end" ] ####################################################### # EMT_Tret_Number ####################################################### class EMT_Tret_Number(EMT_Tret): def __init__(self): EMT_Tret.__init__(self) self.title = "Числа, дроби, математические знаки" self.rules = { "minus_between_nums": { "description": "Расстановка знака минус между числами", "pattern": "/(\\d+)\\-(\\d)/i", "replacement": "\\1−\\2" }, "minus_in_numbers_range": { "description": "Расстановка знака минус между диапозоном чисел", "pattern": "/(^|\\s|\\ \\;)(\\&minus\\;|\\-)(\\d+)(\\.\\.\\.|\\&hellip\\;)(\\s|\\ \\;)?(\\+|\\-|\\&minus\\;)?(\\d+)/ie", "replacement": "m.group(1) +u\"−\"+m.group(3) + m.group(4)+m.group(5)+((m.group(6) if m.group(6)==u\"+\" else u\"−\"))+m.group(7)" }, "auto_times_x": { "description": "Замена x на символ × в размерных единицах", "cycled": True, "pattern": "/([^a-zA-Z><]|^)(\\×\\;)?(\\d+)(\\040*)(x|х)(\\040*)(\\d+)([^a-zA-Z><]|$)/u", "replacement": "\\1\\2\\3×\\7\\8" }, "numeric_sub": { "description": "Нижний индекс", "pattern": "/([a-zа-яё0-9])\\_([\\d]{1,3})([^@а-яёa-z0-9]|$)/ieu", "replacement": "m.group(1) + self.tag(self.tag(m.group(2),u\"small\"),u\"sub\") + m.group(3)" }, "numeric_sup": { "description": "Верхний индекс", "pattern": "/([a-zа-яё0-9])\\^([\\d]{1,3})([^а-яёa-z0-9]|$)/ieu", "replacement": "m.group(1) + self.tag(self.tag(m.group(2),u\"small\"),u\"sup\") + m.group(3)" }, "simple_fraction": { "description": "Замена дробей 1/2, 1/4, 3/4 на соответствующие символы", "pattern": [ "/(^|\\D)1\\/(2|4)(\\D)/", "/(^|\\D)3\\/4(\\D)/" ], "replacement": [ "\\1&frac1\\2;\\3", "\\1¾\\2" ] }, "math_chars": { "description": "Математические знаки больше/меньше/плюс минус/неравно", "pattern": [ "/!=/", "/\\<=/", "/([^=]|^)\\>=/", "/~=/", "/\\+-/" ], "replacement": [ "≠", "≤", "\\1≥", "≅", "±" ] }, "thinsp_between_number_triads": { "description": "Объединение триад чисел полупробелом", "pattern": "/([0-9]{1,3}( [0-9]{3}){1,})(.|$)/ue", "replacement": "(( m.group(0) if m.group(3)==u\"-\" else EMT_Lib.str_replace(u\" \",u\" \",m.group(1))+m.group(3)))" }, "thinsp_between_no_and_number": { "description": "Пробел между символом номера и числом", "pattern": "/(№|\\№\\;)(\\s| )*(\\d)/iu", "replacement": "№ \\3" }, "thinsp_between_sect_and_number": { "description": "Пробел между параграфом и числом", "pattern": "/(§|\\§\\;)(\\s| )*(\\d+|[IVX]+|[a-zа-яё]+)/ui", "replacement": "§ \\3" } } self.rule_order = [ "minus_between_nums", "minus_in_numbers_range", "auto_times_x", "numeric_sub", "numeric_sup", "simple_fraction", "math_chars", "thinsp_between_number_triads", "thinsp_between_no_and_number", "thinsp_between_sect_and_number" ] ####################################################### # EMT_Tret_Space ####################################################### class EMT_Tret_Space(EMT_Tret): def __init__(self): EMT_Tret.__init__(self) self.title = "Расстановка и удаление пробелов" self.rules = { "nobr_twosym_abbr": { "description": "Неразрывный перед 2х символьной аббревиатурой", "pattern": "/([a-zA-Zа-яёА-ЯЁ])(\\040|\\t)+([A-ZА-ЯЁ]{2})([\\s\\;\\.\\?\\!\\:\\(\\\"]|\\&(ra|ld)quo\\;|$)/u", "replacement": "\\1 \\3\\4" }, "remove_space_before_punctuationmarks": { "description": "Удаление пробела перед точкой, запятой, двоеточием, точкой с запятой", "pattern": "/((\\040|\\t|\\ \\;)+)([\\,\\:\\.\\;\\?])(\\s+|$)/", "replacement": "\\3\\4" }, "autospace_after_comma": { "description": "Пробел после запятой", "pattern": [ "/(\\040|\\t|\\ \\;)\\,([а-яёa-z0-9])/iu", "/([^0-9])\\,([а-яёa-z0-9])/iu" ], "replacement": [ ", \\2", "\\1, \\2" ] }, "autospace_after_pmarks": { "description": "Пробел после знаков пунктуации, кроме точки", "pattern": "/(\\040|\\t|\\ \\;|^|\\n)([a-zа-яё0-9]+)(\\040|\\t|\\ \\;)?(\\:|\\)|\\,|\\&hellip\\;|(?:\\!|\\?)+)([а-яёa-z])/iu", "replacement": "\\1\\2\\4 \\5" }, "autospace_after_dot": { "description": "Пробел после точки", "pattern": [ "/(\\040|\\t|\\ \\;|^)([a-zа-яё0-9]+)(\\040|\\t|\\ \\;)?\\.([а-яёa-z]{5,})($|[^a-zа-яё])/iue", "/(\\040|\\t|\\ \\;|^)([a-zа-яё0-9]+)\\.([а-яёa-z]{1,4})($|[^a-zа-яё])/iue" ], "replacement": [ "m.group(1)+m.group(2)+u\".\" +(( u\"\" if m.group(5) == u\".\" else u\" \"))+m.group(4)+m.group(5)", "m.group(1)+m.group(2)+u\".\" +(( u\"\" if EMT_Lib.strtolower(m.group(3)) in ( self.domain_zones) else (( u\"\" if m.group(4) == u\".\" else u\" \"))))+ m.group(3)+m.group(4)" ] }, "autospace_after_hellips": { "description": "Пробел после знаков троеточий с вопросительным или восклицательными знаками", "pattern": "/([\\?\\!]\\.\\.)([а-яёa-z])/iu", "replacement": "\\1 \\2" }, "many_spaces_to_one": { "description": "Удаление лишних пробельных символов и табуляций", "pattern": "/(\\040|\\t)+/", "replacement": " " }, "clear_percent": { "description": "Удаление пробела перед символом процента", "pattern": "/(\\d+)([\\t\\040]+)\\%/", "replacement": "\\1%" }, "nbsp_before_open_quote": { "description": "Неразрывный пробел перед открывающей скобкой", "pattern": "/(^|\\040|\\t|>)([a-zа-яё]{1,2})\\040(\\«\\;|\\&bdquo\\;)/u", "replacement": "\\1\\2 \\3" }, "nbsp_before_month": { "description": "Неразрывный пробел в датах перед числом и месяцем", "pattern": "/(\\d)(\\s)+(января|февраля|марта|апреля|мая|июня|июля|августа|сентября|октября|ноября|декабря)([^\\<]|$)/iu", "replacement": "\\1 \\3\\4" }, "spaces_on_end": { "description": "Удаление пробелов в конце текста", "pattern": "/ +$/", "replacement": "" }, "no_space_posle_hellip": { "description": "Отсутстввие пробела после троеточия после открывающей кавычки", "pattern": "/(\\«\\;|\\&bdquo\\;)( |\\ \\;)?\\&hellip\\;( |\\ \\;)?([a-zа-яё])/ui", "replacement": "\\1…\\4" }, "space_posle_goda": { "description": "Пробел после года", "pattern": "/(^|\\040|\\ \\;)([0-9]{3,4})(год([ауе]|ом)?)([^a-zа-яё]|$)/ui", "replacement": "\\1\\2 \\3\\5" } } self.rule_order = [ "nobr_twosym_abbr", "remove_space_before_punctuationmarks", "autospace_after_comma", "autospace_after_pmarks", "autospace_after_dot", "autospace_after_hellips", "many_spaces_to_one", "clear_percent", "nbsp_before_open_quote", "nbsp_before_month", "spaces_on_end", "no_space_posle_hellip", "space_posle_goda" ] self.domain_zones = [ "ru", "ру", "рф", "ком", "орг", "уа", "ua", "uk", "co", "fr", "com", "net", "edu", "gov", "org", "mil", "int", "info", "biz", "info", "name", "pro" ] self.classes = { "nowrap": "word-spacing:nowrap;" } ####################################################### # EMT_Tret_Abbr ####################################################### class EMT_Tret_Abbr(EMT_Tret): def __init__(self): EMT_Tret.__init__(self) self.title = "Сокращения" self.rules = { "nobr_abbreviation": { "description": "Расстановка пробелов перед сокращениями dpi, lpi", "pattern": "/(\\s+|^|\\>)(\\d+)(\\040|\\t)*(dpi|lpi)([\\s\\;\\.\\?\\!\\:\\(]|$)/i", "replacement": "\\1\\2 \\4\\5" }, "nobr_acronym": { "description": "Расстановка пробелов перед сокращениями гл., стр., рис., илл., ст., п.", "pattern": "/(\\s|^|\\>|\\()(гл|стр|рис|илл?|ст|п|с)\\.(\\040|\\t)*(\\d+)(\\ \\;|\\s|\\.|\\,|\\?|\\!|$)/iu", "replacement": "\\1\\2. \\4\\5" }, "nobr_sm_im": { "description": "Расстановка пробелов перед сокращениями см., им.", "pattern": "/(\\s|^|\\>|\\()(см|им)\\.(\\040|\\t)*([а-яё0-9a-z]+)(\\s|\\.|\\,|\\?|\\!|$)/iu", "replacement": "\\1\\2. \\4\\5" }, "nobr_locations": { "description": "Расстановка пробелов в сокращениях г., ул., пер., д.", "pattern": [ "/(\\s|^|\\>)(г|ул|пер|просп|пл|бул|наб|пр|ш|туп)\\.(\\040|\\t)*([а-яё0-9a-z]+)(\\s|\\.|\\,|\\?|\\!|$)/iu", "/(\\s|^|\\>)(б\\-р|пр\\-кт)(\\040|\\t)*([а-яё0-9a-z]+)(\\s|\\.|\\,|\\?|\\!|$)/iu", "/(\\s|^|\\>)(д|кв|эт)\\.(\\040|\\t)*(\\d+)(\\s|\\.|\\,|\\?|\\!|$)/iu" ], "replacement": [ "\\1\\2. \\4\\5", "\\1\\2 \\4\\5", "\\1\\2. \\4\\5" ] }, "nbsp_before_unit": { "description": "Замена символов и привязка сокращений в размерных величинах: м, см, м2…", "pattern": [ "/(\\s|^|\\>|\\ \\;|\\,)(\\d+)( |\\ \\;)?(м|мм|см|дм|км|гм|km|dm|cm|mm)(\\s|\\.|\\!|\\?|\\,|$|\\±\\;|\\;|\\<)/iu", "/(\\s|^|\\>|\\ \\;|\\,)(\\d+)( |\\ \\;)?(м|мм|см|дм|км|гм|km|dm|cm|mm)([32]|³|²)(\\s|\\.|\\!|\\?|\\,|$|\\±\\;|\\;|\\<)/iue" ], "replacement": [ "\\1\\2 \\4\\5", "m.group(1)+m.group(2)+u\" \"+m.group(4)+(( u\"&sup\"+m.group(5)+u\";\" if m.group(5)==u\"3\" or m.group(5)==u\"2\" else m.group(5) ))+m.group(6)" ] }, "nbsp_before_weight_unit": { "description": "Замена символов и привязка сокращений в весовых величинах: г, кг, мг…", "pattern": "/(\\s|^|\\>|\\ \\;|\\,)(\\d+)( |\\ \\;)?(г|кг|мг|т)(\\s|\\.|\\!|\\?|\\,|$|\\ \\;|\\;)/iu", "replacement": "\\1\\2 \\4\\5" }, "nobr_before_unit_volt": { "description": "Установка пробельных символов в сокращении вольт", "pattern": "/(\\d+)([вВ]| В)(\\s|\\.|\\!|\\?|\\,|$)/u", "replacement": "\\1 В\\3" }, "ps_pps": { "description": "Объединение сокращений P.S., P.P.S.", "pattern": "/(^|\\040|\\t|\\>|\\r|\\n)(p\\.\\040?)(p\\.\\040?)?(s\\.)([^\\<])/ie", "replacement": "m.group(1) + self.tag(m.group(2).strip() + u\" \" + (( m.group(3).strip() + u\" \" if m.group(3) else u\"\"))+ m.group(4), u\"span\", {u\"class\" : u\"nowrap\"} )+m.group(5) " }, "nobr_vtch_itd_itp": { "description": "Объединение сокращений и т.д., и т.п., в т.ч.", "cycled": True, "pattern": [ "/(^|\\s|\\ \\;)и( |\\ \\;)т\\.?[ ]?д(\\.|$|\\s|\\ \\;)/ue", "/(^|\\s|\\ \\;)и( |\\ \\;)т\\.?[ ]?п(\\.|$|\\s|\\ \\;)/ue", "/(^|\\s|\\ \\;)в( |\\ \\;)т\\.?[ ]?ч(\\.|$|\\s|\\ \\;)/ue" ], "replacement": [ "m.group(1)+self.tag(u\"и т. д.\", u\"span\", {u\"class\" : u\"nowrap\"})+(( m.group(3) if m.group(3)!=u\".\" else u\"\" ))", "m.group(1)+self.tag(u\"и т. п.\", u\"span\", {u\"class\" : u\"nowrap\"})+(( m.group(3) if m.group(3)!=u\".\" else u\"\" ))", "m.group(1)+self.tag(u\"в т. ч.\", u\"span\", {u\"class\" : u\"nowrap\"})+(( m.group(3) if m.group(3)!=u\".\" else u\"\" ))" ] }, "nbsp_te": { "description": "Обработка т.е.", "pattern": "/(^|\\s|\\ \\;)([тТ])\\.?[ ]?е\\./ue", "replacement": "m.group(1)+self.tag(m.group(2)+u\". е.\", u\"span\", {u\"class\" : u\"nowrap\"})" }, "nbsp_money_abbr": { "description": "Форматирование денежных сокращений (расстановка пробелов и привязка названия валюты к числу)", "pattern": "/(\\d)((\\040|\\ \\;)?(тыс|млн|млрд)\\.?(\\040|\\ \\;)?)?(\\040|\\ \\;)?(" "₽|₽|руб\\.|долл\\.|USD|\\$|евро|€|€|GBP|£|£|₿|₿|у[\\.]? ?е[" "\\.]?)/ieu", "replacement": "m.group(1)+(u\" \"+m.group(4)+(u\".\" if m.group(4)==u\"тыс\" else u\"\") if m.group(4) else u\"\")+u\" \"+(m.group(7) if not re.match(u\"у[\\\\.]? ?е[\\\\.]?\",m.group(7),re.I | re.U) else u\"у.е.\")", "replacement_python": "m.group(1)+(u\" \"+m.group(4)+(u\".\" if m.group(4)==u\"тыс\" else u\"\") if m.group(4) else u\"\")+u\" \"+(m.group(7) if not re.match(u\"у[\\\\.]? ?е[\\\\.]?\",m.group(7),re.I | re.U) else u\"у.е.\")" }, "nbsp_money_abbr_rev": { "description": "Привязка валюты к числу спереди", "pattern": "/(€|€|\\$)\\s?(\\d)/iu", "replacement": "\\1 \\2" }, "nbsp_org_abbr": { "description": "Привязка сокращений форм собственности к названиям организаций", "pattern": "/([^a-zA-Zа-яёА-ЯЁ]|^)(ООО|ЗАО|ОАО|НИИ|ПБОЮЛ) ([a-zA-Zа-яёА-ЯЁ]|\\\"|\\«\\;|\\&bdquo\\;|<)/u", "replacement": "\\1\\2 \\3" }, "nobr_gost": { "description": "Привязка сокращения ГОСТ к номеру", "pattern": [ "/(\\040|\\t|\\ \\;|^)ГОСТ( |\\ \\;)?(\\d+)((\\-|\\&minus\\;|\\&mdash\\;)(\\d+))?(( |\\ \\;)(\\-|\\&mdash\\;))?/ieu", "/(\\040|\\t|\\ \\;|^|\\>)ГОСТ( |\\ \\;)?(\\d+)(\\-|\\&minus\\;|\\&mdash\\;)(\\d+)/ieu" ], "replacement": [ "m.group(1)+self.tag(u\"ГОСТ \"+m.group(3)+((u\"–\"+m.group(6) if (m.group(6)) else u\"\"))+((u\" —\" if (m.group(7)) else u\"\")),u\"span\", {u\"class\":u\"nowrap\"})", "m.group(1)+u\"ГОСТ \"+m.group(3)+u\"–\"+m.group(5)" ] } } self.rule_order = [ "nobr_abbreviation", "nobr_acronym", "nobr_sm_im", "nobr_locations", "nbsp_before_unit", "nbsp_before_weight_unit", "nobr_before_unit_volt", "ps_pps", "nobr_vtch_itd_itp", "nbsp_te", "nbsp_money_abbr", "nbsp_money_abbr_rev", "nbsp_org_abbr", "nobr_gost" ] self.domain_zones = [ "ru", "ру", "com", "ком", "org", "орг", "уа", "ua" ] self.classes = { "nowrap": "word-spacing:nowrap;" } ####################################################### # EMT_Tret_Nobr ####################################################### class EMT_Tret_Nobr(EMT_Tret): def __init__(self): EMT_Tret.__init__(self) self.title = "Неразрывные конструкции" self.rules = { "super_nbsp": { "description": "Привязка союзов и предлогов к написанным после словам", "pattern": "/(\\s|^|\\&(la|bd)quo\\;|\\>|\\(|\\&mdash\\;\\ \\;)([a-zа-яё]{1,2}\\s+)([a-zа-яё]{1,2}\\s+)?([a-zа-яё0-9\\-]{2,}|[0-9])/ieu", "replacement": "m.group(1) + m.group(3).strip() + u\" \" + (( m.group(4).strip() + u\" \" if m.group(4) else u\"\")) + m.group(5)" }, "nbsp_in_the_end": { "description": "Привязка союзов и предлогов к предыдущим словам в случае конца предложения", "pattern": "/([a-zа-яё0-9\\-]{3,}) ([a-zа-яё]{1,2})\\.( [A-ZА-ЯЁ]|$)/u", "replacement": "\\1 \\2.\\3" }, "phone_builder": { "description": "Объединение в неразрывные конструкции номеров телефонов", "pattern": [ "/([^\\d\\+]|^)([\\+]?[0-9]{1,3})( |\\ \\;|\\&thinsp\\;)([0-9]{3,4}|\\([0-9]{3,4}\\))( |\\ \\;|\\&thinsp\\;)([0-9]{2,3})(-|\\&minus\\;)([0-9]{2})(-|\\&minus\\;)([0-9]{2})([^\\d]|$)/e", "/([^\\d\\+]|^)([\\+]?[0-9]{1,3})( |\\ \\;|\\&thinsp\\;)([0-9]{3,4}|[0-9]{3,4})( |\\ \\;|\\&thinsp\\;)([0-9]{2,3})(-|\\&minus\\;)([0-9]{2})(-|\\&minus\\;)([0-9]{2})([^\\d]|$)/e" ], "replacement": [ "m.group(1) +(( m.group(2)+u\" \"+m.group(4)+u\" \"+m.group(6)+u\"-\"+m.group(8)+u\"-\"+m.group(10) if (m.group(1) == u\">\" or m.group(11) == u\"<\") else self.tag(m.group(2)+u\" \"+m.group(4)+u\" \"+m.group(6)+u\"-\"+m.group(8)+u\"-\"+m.group(10), u\"span\", {u\"class\":u\"nowrap\"}) ))+m.group(11)", "m.group(1) +(( m.group(2)+u\" \"+m.group(4)+u\" \"+m.group(6)+u\"-\"+m.group(8)+u\"-\"+m.group(10) if (m.group(1) == u\">\" or m.group(11) == u\"<\") else self.tag(m.group(2)+u\" \"+m.group(4)+u\" \"+m.group(6)+u\"-\"+m.group(8)+u\"-\"+m.group(10), u\"span\", {u\"class\":u\"nowrap\"}) ))+m.group(11)" ] }, "phone_builder_v2": { "description": "Дополнительный формат номеров телефонов", "pattern": "/([^\\d]|^)\\+\\s?([0-9]{1})\\s?\\(([0-9]{3,4})\\)\\s?(\\d{3})(\\d{2})(\\d{2})([^\\d]|$)/ie", "replacement": "m.group(1)+self.tag(u\"+\"+m.group(2)+u\" \"+m.group(3)+u\" \"+m.group(4)+u\"-\"+m.group(5)+u\"-\"+m.group(6), u\"span\", {u\"class\" : u\"nowrap\"})+m.group(7)" }, "ip_address": { "description": "Объединение IP-адресов", "pattern": "/(\\s|\\ \\;|^)(\\d{0,3}\\.\\d{0,3}\\.\\d{0,3}\\.\\d{0,3})/ie", "replacement": "m.group(1) + self.nowrap_ip_address(m.group(2))" }, "dots_for_surname_abbr": { "disabled": True, "description": "Простановка точек к инициалам у фамилии", "pattern": [ "/(\\s|^|\\.|\\,|\\;|\\:|\\?|\\!|\\ \\;)([А-ЯЁ])\\.?(\\s|\\ \\;)?([А-ЯЁ])(\\s|\\ \\;)([А-ЯЁ][а-яё]+)(\\s|$|\\.|\\,|\\;|\\:|\\?|\\!|\\ \\;)/ue", "/(\\s|^|\\.|\\,|\\;|\\:|\\?|\\!|\\ \\;)([А-ЯЁ][а-яё]+)(\\s|\\ \\;)([А-ЯЁ])\\.?(\\s|\\ \\;)?([А-ЯЁ])\\.?(\\s|$|\\.|\\,|\\;|\\:|\\?|\\!|\\ \\;)/ue" ], "replacement": [ "m.group(1)+self.tag(m.group(2)+u\". \"+m.group(4)+u\". \"+m.group(6), u\"span\", {u\"class\" : u\"nowrap\"})+m.group(7)", "m.group(1)+self.tag(m.group(2)+u\" \"+m.group(4)+u\". \"+m.group(6)+u\".\", u\"span\", {u\"class\" : u\"nowrap\"})+m.group(7)" ] }, "spaces_nobr_in_surname_abbr": { "description": "Привязка инициалов к фамилиям", "pattern": [ "/(\\s|^|\\.|\\,|\\;|\\:|\\?|\\!|\\ \\;)([А-ЯЁ])\\.(\\s|\\ \\;)?([А-ЯЁ])\\.(\\s|\\ \\;)?([А-ЯЁ][а-яё]+)(\\s|$|\\.|\\,|\\;|\\:|\\?|\\!|\\ \\;)/ue", "/(\\s|^|\\.|\\,|\\;|\\:|\\?|\\!|\\ \\;)([А-ЯЁ][а-яё]+)(\\s|\\ \\;)([А-ЯЁ])\\.(\\s|\\ \\;)?([А-ЯЁ])\\.(\\s|$|\\.|\\,|\\;|\\:|\\?|\\!|\\ \\;)/ue", "/(\\s|^|\\.|\\,|\\;|\\:|\\?|\\!|\\ \\;)([А-ЯЁ])(\\s|\\ \\;)?([А-ЯЁ])(\\s|\\ \\;)([А-ЯЁ][а-яё]+)(\\s|$|\\.|\\,|\\;|\\:|\\?|\\!|\\ \\;)/ue", "/(\\s|^|\\.|\\,|\\;|\\:|\\?|\\!|\\ \\;)([А-ЯЁ][а-яё]+)(\\s|\\ \\;)([А-ЯЁ])(\\s|\\ \\;)?([А-ЯЁ])(\\s|$|\\.|\\,|\\;|\\:|\\?|\\!|\\ \\;)/ue" ], "replacement": [ "m.group(1)+self.tag(m.group(2)+u\". \"+m.group(4)+u\". \"+m.group(6), u\"span\", {u\"class\" : u\"nowrap\"})+m.group(7)", "m.group(1)+self.tag(m.group(2)+u\" \"+m.group(4)+u\". \"+m.group(6)+u\".\", u\"span\", {u\"class\" : u\"nowrap\"})+m.group(7)", "m.group(1)+self.tag(m.group(2)+(( u\" \" if (m.group(3)) else u\"\" ))+m.group(4)+(( u\" \" if (m.group(5)) else u\"\" ))+m.group(6), u\"span\", {u\"class\" : u\"nowrap\"})+m.group(7)", "m.group(1)+self.tag(m.group(2)+u\" \"+m.group(4)+(( u\" \" if (m.group(5)) else u\"\" ))+m.group(6), u\"span\", {u\"class\" : u\"nowrap\"})+m.group(7)" ] }, "nbsp_before_particle": { "description": "Неразрывный пробел перед частицей", "pattern": "/(\\040|\\t)+(ли|бы|б|же|ж)(\\ \\;|\\.|\\,|\\:|\\;|\\&hellip\\;|\\?|\\s)/iue", "replacement": "u\" \"+m.group(2) + (( u\" \" if m.group(3) == u\" \" else m.group(3)))" }, "nbsp_v_kak_to": { "description": "Неразрывный пробел в как то", "pattern": "/как то\\:/ui", "replacement": "как то:" }, "nbsp_celcius": { "description": "Привязка градусов к числу", "pattern": "/(\\s|^|\\>|\\ \\;)(\\d+)( |\\ \\;)?(°|\\°\\;)(C|С)(\\s|\\.|\\!|\\?|\\,|$|\\ \\;|\\;)/iu", "replacement": "\\1\\2 \\4C\\6" }, "hyphen_nowrap_in_small_words": { "description": "Обрамление пятисимвольных слов разделенных дефисом в неразрывные блоки", "disabled": True, "cycled": True, "pattern": "/(\\ \\;|\\s|\\>|^)([a-zа-яё]{1}\\-[a-zа-яё]{4}|[a-zа-яё]{2}\\-[a-zа-яё]{3}|[a-zа-яё]{3}\\-[a-zа-яё]{2}|[a-zа-яё]{4}\\-[a-zа-яё]{1}|когда\\-то|кое\\-как|кой\\-кого|вс[её]\\-таки|[а-яё]+\\-(кась|ка|де))(\\s|\\.|\\,|\\!|\\?|\\ \\;|\\&hellip\\;|$)/uie", "replacement": "m.group(1) + self.tag(m.group(2), u\"span\", {u\"class\":u\"nowrap\"}) + m.group(4)" }, "hyphen_nowrap": { "description": "Отмена переноса слова с дефисом", "disabled": True, "cycled": True, "pattern": "/(\\ \\;|\\s|\\>|^)([a-zа-яё]+)((\\-([a-zа-яё]+)){1,2})(\\s|\\.|\\,|\\!|\\?|\\ \\;|\\&hellip\\;|$)/uie", "replacement": "m.group(1) + self.tag(m.group(2)+m.group(3), u\"span\", {u\"class\":u\"nowrap\"}) + m.group(6)" } } self.rule_order = [ "super_nbsp", "nbsp_in_the_end", "phone_builder", "phone_builder_v2", "ip_address", "dots_for_surname_abbr", "spaces_nobr_in_surname_abbr", "nbsp_before_particle", "nbsp_v_kak_to", "nbsp_celcius", "hyphen_nowrap_in_small_words", "hyphen_nowrap" ] self.classes = { "nowrap": "word-spacing:nowrap;" } # * Объединение IP-адресов в неразрывные конструкции (IPv4 only) # * # * @param unknown_type $triads # * @return unknown def nowrap_ip_address(self, triads): triad = triads.split('.') add_tag = True for value in triad: value = int(value) if value > 255: add_tag = false break if add_tag: triads = self.tag(triads, 'span', {'class': "nowrap"}) return triads ####################################################### # EMT_Tret_Date ####################################################### class EMT_Tret_Date(EMT_Tret): def __init__(self): EMT_Tret.__init__(self) self.title = "Даты и дни" self.rules = { "years": { "description": "Установка тире и пробельных символов в периодах дат", "pattern": "/(с|по|период|середины|начала|начало|конца|конец|половины|в|между|\\([cс]\\)|\\©\\;)(\\s+|\\ \\;)([\\d]{4})(-|\\&mdash\\;|\\&minus\\;)([\\d]{4})(( |\\ \\;)?(г\\.г\\.|гг\\.|гг|г\\.|г)([^а-яёa-z]))?/eui", "replacement": "m.group(1)+m.group(2)+ (( m.group(3)+m.group(4)+m.group(5) if int(m.group(3))>=int(m.group(5)) else m.group(3)+u\"—\"+m.group(5))) + (( u\" гг.\" if (m.group(6)) else u\"\"))+((m.group(9) if (m.group(9)) else u\"\"))" }, "mdash_month_interval": { "description": "Расстановка тире и объединение в неразрывные периоды месяцев", "disabled": True, "pattern": "/((январ|феврал|сентябр|октябр|ноябр|декабр)([ьяюе]|[её]м)|(апрел|июн|июл)([ьяюе]|ем)|(март|август)([ауе]|ом)?|ма[йяюе]|маем)\\-((январ|феврал|сентябр|октябр|ноябр|декабр)([ьяюе]|[её]м)|(апрел|июн|июл)([ьяюе]|ем)|(март|август)([ауе]|ом)?|ма[йяюе]|маем)/iu", "replacement": "\\1—\\8" }, "nbsp_and_dash_month_interval": { "description": "Расстановка тире и объединение в неразрывные периоды дней", "disabled": True, "pattern": "/([^\\>]|^)(\\d+)(\\-|\\&minus\\;|\\&mdash\\;)(\\d+)( |\\ \\;)(января|февраля|марта|апреля|мая|июня|июля|августа|сентября|октября|ноября|декабря)([^\\<]|$)/ieu", "replacement": "m.group(1)+self.tag(m.group(2)+u\"—\"+m.group(4)+u\" \"+m.group(6),u\"span\", {u\"class\":u\"nowrap\"})+m.group(7)" }, "nobr_year_in_date": { "description": "Привязка года к дате", "pattern": [ "/(\\s|\\ \\;)([0-9]{2}\\.[0-9]{2}\\.([0-9]{2})?[0-9]{2})(\\s|\\ \\;)?г(\\.|\\s|\\ \\;)/eiu", "/(\\s|\\ \\;)([0-9]{2}\\.[0-9]{2}\\.([0-9]{2})?[0-9]{2})(\\s|\\ \\;|\\.(\\s|\\ \\;|$)|$)/eiu" ], "replacement": [ "m.group(1)+self.tag(m.group(2)+u\" г.\",u\"span\", {u\"class\":u\"nowrap\"})+((u\"\" if m.group(5)==u\".\" else u\" \"))", "m.group(1)+self.tag(m.group(2),u\"span\", {u\"class\":u\"nowrap\"})+m.group(4)" ] }, "space_posle_goda": { "description": "Пробел после года", "pattern": "/(^|\\040|\\ \\;)([0-9]{3,4})(год([ауе]|ом)?)([^a-zа-яё]|$)/ui", "replacement": "\\1\\2 \\3\\5" }, "nbsp_posle_goda_abbr": { "description": "Пробел после года", "pattern": "/(^|\\040|\\ \\;|\\\"|\\«\\;)([0-9]{3,4})[ ]?(г\\.)([^a-zа-яё]|$)/ui", "replacement": "\\1\\2 \\3\\4" } } self.rule_order = [ "years", "mdash_month_interval", "nbsp_and_dash_month_interval", "nobr_year_in_date", "space_posle_goda", "nbsp_posle_goda_abbr" ] self.classes = { "nowrap": "word-spacing:nowrap;" } ####################################################### # EMT_Tret_OptAlign ####################################################### class EMT_Tret_OptAlign(EMT_Tret): def __init__(self): EMT_Tret.__init__(self) self.title = "Оптическое выравнивание" self.rules = { "oa_oquote": { "description": "Оптическое выравнивание открывающей кавычки", "pattern": [ "/([a-zа-яё\\-]{3,})(\\040|\\ \\;|\\t)(\\«\\;)/uie", "/(\\n|\\r|^)(\\«\\;)/ei" ], "replacement": [ "m.group(1) + self.tag(m.group(2), u\"span\", {u\"class\":u\"oa_oqoute_sp_s\"}) + self.tag(m.group(3), u\"span\", {u\"class\":u\"oa_oqoute_sp_q\"})", "m.group(1) + self.tag(m.group(2), u\"span\", {u\"class\":u\"oa_oquote_nl\"})" ] }, "oa_oquote_extra": { "description": "Оптическое выравнивание кавычки", "function": "oaquote_extra" }, "oa_obracket_coma": { "description": "Оптическое выравнивание для пунктуации (скобка)", "pattern": [ "/(\\040|\\ \\;|\\t)\\(/ei", "/(\\n|\\r|^)\\(/ei" ], "replacement": [ "self.tag(m.group(1), u\"span\", {u\"class\":u\"oa_obracket_sp_s\"}) + self.tag(u\"(\", u\"span\", {u\"class\":u\"oa_obracket_sp_b\"})", "m.group(1) + self.tag(u\"(\", u\"span\", {u\"class\":u\"oa_obracket_nl_b\"})" ] } } self.rule_order = [ "oa_oquote", "oa_oquote_extra", "oa_obracket_coma" ] self.classes = { "oa_obracket_sp_s": "margin-right:0.3em;", "oa_obracket_sp_b": "margin-left:-0.3em;", "oa_obracket_nl_b": "margin-left:-0.3em;", "oa_comma_b": "margin-right:-0.2em;", "oa_comma_e": "margin-left:0.2em;", "oa_oquote_nl": "margin-left:-0.44em;", "oa_oqoute_sp_s": "margin-right:0.44em;", "oa_oqoute_sp_q": "margin-left:-0.44em;" } ####################################################### # EMT_Tret_Etc ####################################################### class EMT_Tret_Etc(EMT_Tret): def __init__(self): EMT_Tret.__init__(self) self.title = "Прочее" self.rules = { "acute_accent": { "description": "Акцент", "pattern": "/(у|е|ы|а|о|э|я|и|ю|ё)\\`(\\w)/i", "replacement": "\\1́\\2" }, "word_sup": { "description": "Надстрочный текст после символа ^", "pattern": "/((\\s|\\ \\;|^)+)\\^([a-zа-яё0-9\\.\\:\\,\\-]+)(\\s|\\ \\;|$|\\.$)/ieu", "replacement": "u\"\" + self.tag(self.tag(m.group(3),u\"small\"),u\"sup\") + m.group(4)" }, "century_period": { "description": "Тире между диапазоном веков", "pattern": "/(\\040|\\t|\\ \\;|^)([XIV]{1,5})(-|\\&mdash\\;)([XIV]{1,5})(( |\\ \\;)?(в\\.в\\.|вв\\.|вв|в\\.|в))/eu", "replacement": "m.group(1) +self.tag(m.group(2)+u\"—\"+m.group(4)+u\" вв.\",u\"span\", {u\"class\":u\"nowrap\"})" }, "time_interval": { "description": "Тире и отмена переноса между диапазоном времени", "pattern": "/([^\\d\\>]|^)([\\d]{1,2}\\:[\\d]{2})(-|\\&mdash\\;|\\&minus\\;)([\\d]{1,2}\\:[\\d]{2})([^\\d\\<]|$)/eui", "replacement": "m.group(1) + self.tag(m.group(2)+u\"—\"+m.group(4),u\"span\", {u\"class\":u\"nowrap\"})+m.group(5)" }, "split_number_to_triads": { "description": "Разбиение числа на триады", "pattern": "/([^a-zA-Z0-9<\\)]|^)([0-9]{5,})([^a-zA-Z>\\(]|$)/eu", "replacement": "m.group(1)+EMT_Lib.str_replace(u\" \",u\" \",EMT_Lib.split_number(m.group(2)))+m.group(3) " }, "expand_no_nbsp_in_nobr": { "description": "Удаление nbsp в nobr/nowrap тэгах", "function": "remove_nbsp" }, "nobr_to_nbsp": { "description": "Преобразование nobr в nbsp", "disabled": True, "function": "nobr_to_nbsp" } } self.rule_order = [ "acute_accent", "word_sup", "century_period", "time_interval", "split_number_to_triads", "expand_no_nbsp_in_nobr", "nobr_to_nbsp" ] self.classes = { "nowrap": "word-spacing:nowrap;" } def remove_nbsp(self): the_tag = self.tag("###", 'span', {'class': "nowrap"}) arr = the_tag.split("###") b = re.escape(arr[0]) e = re.escape(arr[1]) match = '/(^|[^a-zа-яё])([a-zа-яё]+)\ \;(' + b + ')/iu' p = EMT_Lib.parse_preg_pattern(match) while True: self._text = EMT_Lib.preg_replace(match, "\\1\\3\\2 ", self._text) if not (re.match(p['pattern'], self._text, p['flags'])): break match = '/(' + e + ')\ \;([a-zа-яё]+)($|[^a-zа-яё])/iu' p = EMT_Lib.parse_preg_pattern(match) while True: self._text = EMT_Lib.preg_replace(match, " \\2\\1\\3", self._text) if not (re.match(p['pattern'], self._text, p['flags'])): break self._text = EMT_Lib.preg_replace('/' + b + '.*?' + e + '/iue', 'EMT_Lib.str_replace(" "," ",m.group(0))', self._text) def nobr_to_nbsp(self): the_tag = self.tag("###", 'span', {'class': "nowrap"}) arr = the_tag.split("###") b = re.escape(arr[0]) e = re.escape(arr[1]) self._text = EMT_Lib.preg_replace('/' + b + '(.*?)' + e + '/iue', 'EMT_Lib.str_replace(" "," ",m.group(1))', self._text) ####################################################### # EMT_Tret_Text ####################################################### class EMT_Tret_Text(EMT_Tret): def __init__(self): EMT_Tret.__init__(self) self.title = "Текст и абзацы" self.rules = { "auto_links": { "description": "Выделение ссылок из текста", "pattern": "/(\\s|^)(http|ftp|mailto|https)(:\\/\\/)([^\\s\\,\\!\\<]{4,})(\\s|\\.|\\,|\\!|\\?|\\<|$)/ieu", "replacement": "m.group(1) + self.tag(((EMT_Lib.substr(m.group(4),0,-1) if EMT_Lib.substr(m.group(4),-1)==u\".\" else m.group(4))), u\"a\", {u\"href\" : m.group(2)+m.group(3)+((EMT_Lib.substr(m.group(4),0,-1) if EMT_Lib.substr(m.group(4),-1)==u\".\" else m.group(4)))}) + ((u\".\" if EMT_Lib.substr(m.group(4),-1)==u\".\" else u\"\")) +m.group(5)" }, "email": { "description": "Выделение эл. почты из текста", "pattern": "/(\\s|^|\\ \\;|\\()([a-z0-9\\-\\_\\.]{2,})\\@([a-z0-9\\-\\.]{2,})\\.([a-z]{2,6})(\\)|\\s|\\.|\\,|\\!|\\?|$|\\<)/e", "replacement": "m.group(1) + self.tag(m.group(2)+u\"@\"+m.group(3)+u\".\"+m.group(4), u\"a\", {u\"href\" : u\"mailto:\"+m.group(2)+u\"@\"+m.group(3)+u\".\"+m.group(4)}) + m.group(5)" }, "no_repeat_words": { "description": "Удаление повторяющихся слов", "disabled": True, "pattern": [ "/([а-яё]{3,})( |\\t|\\ \\;)\\1/iu", "/(\\s|\\ \\;|^|\\.|\\!|\\?)(([А-ЯЁ])([а-яё]{2,}))( |\\t|\\ \\;)(([а-яё])\\4)/eu" ], "replacement": [ "\\1", "m.group(1) + (( m.group(2) if m.group(7) == EMT_Lib.strtolower(m.group(3)) else m.group(" "2)+m.group(5)+m.group(6) ))" ] }, "paragraphs": { "description": "Простановка параграфов", "function": "build_paragraphs" }, "breakline": { "description": "Простановка переносов строк", "function": "build_brs" } } self.rule_order = [ "auto_links", "email", "no_repeat_words", "paragraphs", "breakline" ] self.classes = { "nowrap": "word-spacing:nowrap;" } def do_paragraphs(self, text): text = EMT_Lib.str_replace("\r\n", "\n", text) text = EMT_Lib.str_replace("\r", "\n", text) text = '<' + BASE64_PARAGRAPH_TAG + '>' + text.strip() + '' text = self.preg_replace('/([\040\t]+)?(\n)+([\040\t]*)(\n)+/e', '("" if m.group(1) is None else m.group(1))+"" ' '+ EMT_Lib.iblock(m.group(2)+m.group(3))+u"<" +BASE64_PARAGRAPH_TAG + ">"', text) text = self.preg_replace( '/\<' + BASE64_PARAGRAPH_TAG + '\>(' + INTERNAL_BLOCK_OPEN + '[a-zA-Z0-9\/=]+?' + INTERNAL_BLOCK_CLOSE + ')?\<\/' + BASE64_PARAGRAPH_TAG + '\>/s', "", text ) return text def build_paragraphs(self): r = self._text.find('<' + BASE64_PARAGRAPH_TAG + '>') p = self._text.rfind('') if (r != -1) and (p != -1): beg = EMT_Lib.substr(self._text, 0, r) end = EMT_Lib.substr(self._text, p + len('')) self._text = (self.do_paragraphs( beg) + "\n" if beg.strip() else "") + '<' + BASE64_PARAGRAPH_TAG + '>'\ + EMT_Lib.substr(self._text, r + len('<' + BASE64_PARAGRAPH_TAG + '>'), p - (r + len('<' + BASE64_PARAGRAPH_TAG + '>'))) + '' + ("\n" + self.do_paragraphs(end) if end.strip() else "") else: self._text = self.do_paragraphs(self._text) def build_brs(self): self._text = self.preg_replace( '/(\<\/' + BASE64_PARAGRAPH_TAG + '\>)([\r\n \t]+)(\<' + BASE64_PARAGRAPH_TAG + '\>)/mse', 'm.group(1)+EMT_Lib.iblock(m.group(2))+m.group(3)', self._text) if not re.match('\<' + BASE64_BREAKLINE_TAG + '\>', self._text): self._text = EMT_Lib.str_replace("\r\n", "\n", self._text) self._text = EMT_Lib.str_replace("\r", "\n", self._text) self._text = self.preg_replace('/(\n)/e', '"<" + BASE64_BREAKLINE_TAG + ">\\n"', self._text) # /** # * Evgeny Muravjev Typograph, http://mdash.ru # * Version: 3.5 Gold Master # * Release Date: July 2, 2015 # * Authors: Evgeny Muravjev & Alexander Drutsa # */ # /** # * Основной класс типографа Евгения Муравьёва # * реализует основные методы запуска и рабыоты типографа # * # */ class EMT_Base: def __init__(self): self._text = "" self.inited = False # /** # * Список Трэтов, которые надо применить к типогрфированию # * # * @var array # */ self.trets = [] self.trets_index = [] self.tret_objects = {} self.ok = False self.debug_enabled = False self.logging = False self.logs = [] self.errors = [] self.debug_info = [] self.use_layout = False self.class_layout_prefix = False self.use_layout_set = False self.disable_notg_replace = False self.remove_notg = False self.settings = {} self._safe_blocks = [] def log(self, xstr, data=None): if not self.logging: return self.logs.append({'class': '', 'info': xstr, 'data': data}) def tret_log(self, tret, xstr, data=None): self.logs.append({'class': tret, 'info': xstr, 'data': data}) def error(self, info, data=None): self.errors.append({'class': '', 'info': info, 'data': data}) self.log("ERROR " + info, data) def tret_error(self, tret, info, data=None): self.errors.append({'class': tret, 'info': info, 'data': data}) def debug(self, xclass, place, after_text, after_text_raw=""): if not self.debug_enabled: return if isinstance(xclass, str): classname = xclass else: classname = xclass.__class__.__name__ self.debug_info.append({ 'tret': False if xclass == self else True, 'class': classname, 'place': place, 'text': after_text, 'text_raw': after_text_raw, }) # /** # * Включить режим отладки, чтобы посмотреть последовательность вызовов # * третов и правил после # * # */ def debug_on(self): self.debug_enabled = True # /** # * Включить режим отладки, чтобы посмотреть последовательность вызовов # * третов и правил после # * # */ def log_on(self): self.logging = True # /** # * Добавление защищенного блока # * # * # * Jare_Typograph_Tool::addCustomBlocks('', ''); # * Jare_Typograph_Tool::addCustomBlocks('\', '\<\/span\>', True); # * # * # * @param string $id идентификатор # * @param string $open начало блока # * @param string $close конец защищенного блока # * @param string $tag тэг # * @return void # */ def _add_safe_block(self, xid, xopen, close, tag): self._safe_blocks.append({ 'id': xid, 'tag': tag, 'open': xopen, 'close': close }) # /** # * Список защищенных блоков # * # * @return array # */ def get_all_safe_blocks(self): return self._safe_blocks # /** # * Удаленного блока по его номеру ключа # * # * @param string $id идентификатор защищённого блока # * @return void # */ def remove_safe_block(self, xid): i = 0 for x in self._safe_blocks: if x['id'] == xid: break i += 1 if i == len(self._safe_blocks): return del self._safe_blocks[i] # /** # * Добавление защищенного блока # * # * @param string $tag тэг, который должен быть защищён # * @return void # */ def add_safe_tag(self, tag): xopen = re.escape("<") + tag + "[^>]*?" + re.escape(">") close = re.escape("") self._add_safe_block(tag, xopen, close, tag) return True # /** # * Добавление защищенного блока # * # * @param string $open начало блока # * @param string $close конец защищенного блока # * @param bool $quoted специальные символы в начале и конце блока экранированы # * @return void # */ def add_safe_block(self, xid, xopen, close, quoted=False): xopen = xopen.strip() close = close.strip() if xopen == "" or close == "": return False if not quoted: xopen = re.escape(xopen) close = re.escape(close) self._add_safe_block(xid, xopen, close, "") return True # /** # * Сохранение содержимого защищенных блоков # * # * @param string $text # * @param bool $safe если True, то содержимое блоков будет сохранено, иначе - раскодировано. # * @return string # */ def safe_blocks(self, text, way, show=True): if len(self._safe_blocks): safe_type = "EMT_Lib.encrypt_tag(m.group(2))" if True == way else "stripslashes(EMT_Lib.decrypt_tag(m.group(2)))" sel_fb_locks = self._safe_blocks if not way: sel_fb_locks.reverse() def safereplace(m): return m.group(1) + ( EMT_Lib.encrypt_tag(m.group(2)) if True == way else EMT_Lib.decrypt_tag(m.group(2)).replace("\\n", "\n").replace( "\\r", "\n").replace("\\", "")) + m.group(3) for idx in sel_fb_locks: block = idx # text = EMT_Lib.preg_replace(u"/("+block['open']+u")(.+?)("+block['close']+u")/ue", 'm.group(1)+' + safeType + '+m.group(3)', text) text = re.sub("(" + block['open'] + ")(.+?)(" + block['close'] + ")", safereplace, text, 0, re.U) return text # /** # * Декодирование блоков, которые были скрыты в момент типографирования # * # * @param string $text # * @return string # */ def decode_internal_blocks(self, text): return EMT_Lib.decode_internal_blocks(text) def create_object(self, tret): # если класса нет, попытаемся его прогрузить, например, если стандартный try: obj = globals()[tret]() obj.EMT = self obj.logging = self.logging return obj except: self.error("Класс " + tret + " не найден. Пожалуйста, подгрузите нужный файл.") return None def get_short_tret(self, tretname): m = re.match('^EMT_Tret_([a-zA-Z0-9_]+)$', tretname) if m: return m.group(1) return tretname def _init(self): for tret in self.trets: if tret in self.tret_objects: continue obj = self.create_object(tret) if obj == None: continue self.tret_objects[tret] = obj if not self.inited: self.add_safe_tag('pre') self.add_safe_tag('script') self.add_safe_tag('style') self.add_safe_tag('notg') self.add_safe_tag('code') # self.add_safe_tag('kbd') self.add_safe_block('span-notg', '', '') self.inited = True # /** # * Инициализация класса. Используется, чтобы задать список третов или # * список защищённых блоков, которые можно использовать. # * Также здесь можно отменить защищённые блоки по умолчанию # * # */ def init(self): return # /** # * Добавить Трэт, # * # * @param mixed $class - имя класса трета, или сам объект # * @param string $altname - альтернативное имя, если хотим например иметь два одинаковых терта в обработке # * @return unknown # */ def add_tret(self, xclass, altname=False): if isinstance(xclass, str): obj = self.create_object(xclass) if obj is None: return False self.tret_objects[altname if altname else xclass] = obj self.trets.append(altname if altname else xclass) return True try: if not issubclass(xclass, EMT_Tret): self.error("You are adding Tret that doesn't inherit base class EMT_Tret", xclass.__class__.__name__) return False xclass.EMT = self xclass.logging = self.logging self.tret_objects[altname if altname else xclass.__class__.__name__] = xclass self.trets.append(altname if altname else xclass.__class__.__name__) return True except: self.error("Чтобы добавить трэт необходимо передать имя или объект") return False # /** # * Получаем ТРЕТ по идентификатору, т.е. заданию класса # * # * @param unknown_type $name # */ def get_tret(self, name): if name in self.tret_objects: return self.tret_objects[name] for tret in self.trets: if tret == name: self._init() return self.tret_objects[name] if self.get_short_tret(tret) == name: self._init() return self.tret_objects[tret] self.error("Трэт с идентификатором " + name + " не найден") return False # /** # * Задаём текст для применения типографа # * # * @param string $text # */ def set_text(self, text): self._text = text # /** # * Запустить типограф на выполнение # * # */ def apply(self, trets=None): self.ok = False self.init() self._init() atrets = self.trets if isinstance(trets, str): atrets = [trets] elif isinstance(trets, (list, tuple)): atrets = trets self.debug(self, 'init', self._text) self._text = self.safe_blocks(self._text, True) self.debug(self, 'safe_blocks', self._text) self._text = EMT_Lib.safe_tag_chars(self._text, True) self.debug(self, 'safe_tag_chars', self._text) self._text = EMT_Lib.clear_special_chars(self._text) self.debug(self, 'clear_special_chars', self._text) for tret in atrets: # если установлен режим разметки тэгов, то выставим его if self.use_layout_set: self.tret_objects[tret].set_tag_layout_ifnotset(self.use_layout) if self.class_layout_prefix: self.tret_objects[tret].set_class_layout_prefix(self.class_layout_prefix) # включаем, если нужно if self.debug_enabled: self.tret_objects[tret].debug_on() if self.logging: self.tret_objects[tret].logging = True # применяем трэт self.tret_objects[tret].set_text(self._text) self.tret_objects[tret].apply() self._text = self.tret_objects[tret]._text # соберём ошибки если таковые есть if len(self.tret_objects[tret].errors) > 0: for err in self.tret_objects[tret].errors: self.tret_error(tret, err['info'], err['data']) # логгирование if self.logging: if len(self.tret_objects[tret].logs) > 0: for log in self.tret_objects[tret].logs: self.tret_log(tret, log['info'], log['data']) # отладка if self.debug_enabled: for di in self.tret_objects[tret].debug_info: unsafetext = di['text'] unsafetext = EMT_Lib.safe_tag_chars(unsafetext, False) unsafetext = self.safe_blocks(unsafetext, False) self.debug(tret, di['place'], unsafetext, di['text']) self._text = self.decode_internal_blocks(self._text) self.debug(self, 'decode_internal_blocks', self._text) if self.is_on('dounicode'): self._text = EMT_Lib.convert_html_entities_to_unicode(self._text) self._text = EMT_Lib.safe_tag_chars(self._text, False) self.debug(self, 'unsafe_tag_chars', self._text) self._text = self.safe_blocks(self._text, False) self.debug(self, 'unsafe_blocks', self._text) if not self.disable_notg_replace: repl = ['', ''] if self.remove_notg: repl = "" self._text = EMT_Lib.str_replace(['', ''], repl, self._text) self._text = self._text.strip() self.ok = len(self.errors) == 0 self._text = self._text.replace("b'Cg=='", "") # КОСТЫЛЬ от Erjemin return self._text # /** # * Получить содержимое при использовании классов # * # * @param bool $list False - вернуть в виде строки для style или как массив # * @param bool $compact не выводить пустые классы # * @return string|array # */ def get_style(self, xlist=False, compact=False): self._init() res = {} for tret in self.trets: arr = self.tret_objects[tret].classes if not isinstance(arr, (list, tuple, dict)): continue for classname in arr: xstr = arr[classname] if compact and not xstr: continue z = classname if classname in self.tret_objects[tret].class_names: z = self.tret_objects[tret].class_names[classname] clsname = (self.class_layout_prefix if self.class_layout_prefix else "") + z res[clsname] = xstr if xlist: return res xstr = "" for k in res: v = res[k] xstr = xstr + "." + k + " { " + v + " }\n" return xstr # /** # * Установить режим разметки, # * EMT_Lib::LAYOUT_STYLE - с помощью стилей # * EMT_Lib::LAYOUT_CLASS - с помощью классов # * EMT_Lib::LAYOUT_STYLE|EMT_Lib::LAYOUT_CLASS - оба метода # * # * @param int $layout # */ def set_tag_layout(self, layout=LAYOUT_STYLE): self.use_layout = layout self.use_layout_set = True # /** # * Установить префикс для классов # * # * @param string|bool $prefix если True то префикс 'emt_', иначе то, что передали # */ def set_class_layout_prefix(self, prefix): self.class_layout_prefix = prefix if isinstance(prefix, str) else "emt_" # /** # * Включить/отключить правила, согласно карте # * Формат карты: # * 'Название трэта 1' => array ( 'правило1', 'правило2' , ... ) # * 'Название трэта 2' => array ( 'правило1', 'правило2' , ... ) # * # * @param array $map # * @param boolean $disable если ложно, то $map соответствует тем правилам, которые надо включить # * иначе это список правил, которые надо выключить # * @param boolean $strict строго, т.е. те которые не в списку будут тоже обработаны # */ def set_enable_map(self, xmap, disable=False, xstrict=True): if not isinstance(xmap, (list, tuple, dict)): return trets = [] for tret in xmap: xlist = xmap[tret] tretx = self.get_tret(tret) if not tretx: self.log("Трэт " + tret + " не найден при применении карты включаемых правил") continue trets.append(tretx) if isinstance(xlist, bool) and xlist: # все tretx.activate([], not disable, True) elif isinstance(xlist, str): tretx.activate([xlist], disable, xstrict) elif isinstance(xlist, (list, tuple)): tretx.activate(xlist, disable, xstrict) if xstrict: for tret in self.trets: if self.tret_objects[tret] in trets: continue self.tret_objects[tret].activate([], disable, True) # /** # * Установлена ли настройка # * # * @param string $key # */ def is_on(self, key): if key not in self.settings: return False kk = self.settings[key] if isinstance(kk, str) and kk.lower() == "on": return True if isinstance(kk, str) and kk == "1": return True if isinstance(kk, bool) and kk: return True if isinstance(kk, int) and kk == 1: return True return False # /** # * Установить настройку # * # * @param mixed $selector # * @param string $setting # * @param mixed $value # */ def doset(self, selector, key, value): tret_pattern = False rule_pattern = False # if(($selector === False) || ($selector === null) || ($selector === False) || ($selector === "*")) $type = 0 if isinstance(selector, str): if selector.find(".") == -1: tret_pattern = selector else: pa = selector.split(".") tret_pattern = pa[0] pa.pop(0) rule_pattern = ".".join(pa) tret_pattern = EMT_Lib.process_selector_pattern(tret_pattern) rule_pattern = EMT_Lib.process_selector_pattern(rule_pattern) if selector == "*": self.settings[key] = value for tret in self.trets: t1 = self.get_short_tret(tret) if not EMT_Lib.test_pattern(tret_pattern, t1): if not EMT_Lib.test_pattern(tret_pattern, tret): continue tret_obj = self.get_tret(tret) if key == "active": for rule_name in tret_obj.rules: if not EMT_Lib.test_pattern(rule_pattern, rule_name): continue is_on = False is_off = False if isinstance(value, str) and value.lower() == "on": is_on = True elif isinstance(value, str) and value == "1": is_on = True elif isinstance(value, bool) and value: is_on = True elif isinstance(value, int) and value == 1: is_on = True if isinstance(value, str) and value.lower() == "off": is_off = True elif isinstance(value, str) and value == "0": is_off = True elif isinstance(value, bool) and not value: is_off = True elif isinstance(value, int) and value == 0: is_off = True if is_on: tret_obj.enable_rule(rule_name) if is_off: tret_obj.disable_rule(rule_name) else: if isinstance(rule_pattern, bool) and not rule_pattern: tret_obj.set(key, value) else: for rule_name in tret_obj.rules: if not EMT_Lib.test_pattern(rule_pattern, rule_name): continue tret_obj.set_rule(rule_name, key, value) # /** # * Установить настройки для тертов и правил # * 1. если селектор является массивом, то тогда утсановка правил будет выполнена для каждого # * элемента этого массива, как отдельного селектора. # * 2. Если $key не является массивом, то эта настрока будет проставлена согласно селектору # * 3. Если $key массив - то будет задана группа настроек # * - если $value массив , то настройки определяются по ключам из массива $key, а значения из $value # * - иначе, $key содержит ключ-значение как массив # * 4. $exact_match - если true тогда array selector будет соответсвовать array $key, а не произведению массивов # * # * @param mixed $selector # * @param mixed $key # * @param mixed $value # * @param mixed $exact_match # */ def set(self, selector, key, value=False, exact_match=False): if exact_match and isinstance(selector, (list, tuple, set)) \ and isinstance(key, (list, tuple, dict, set)) \ and len(selector) == len(key): ind = 0 for xx in key: if isinstance(key, dict): x = xx y = key[x] else: x = ind y = xx if isinstance(value, dict): kk = y vv = value[x] else: kk = y if value else x vv = value if value else y self.set(selector[ind], kk, vv) ind += 1 return if isinstance(selector, (list, tuple, set)): for val in selector: self.set(val, key, value) return if isinstance(key, (list, tuple, dict, set)): ind = 0 for xx in key: if isinstance(key, dict): x = xx y = key[x] else: x = ind y = xx if isinstance(value, dict): kk = y vv = value[x] else: kk = y if value else x vv = value if value else y self.set(selector, kk, vv) ind += 1 return self.doset(selector, key, value) # /** # * Возвращает список текущих третов, которые установлены # * # */ def get_trets_list(self): return self.trets # /** # * Установка одной мета-настройки # * # * @param string $name # * @param mixed $value # */ def do_setup(self, name, value): return # /** # * Установить настройки # * # * @param array $setup_map # */ def setup(self, setup_map): if not isinstance(setup_map, dict): return if 'map' in setup_map or 'maps' in setup_map: # if setup_map.has_key('map'): # ret['map'] = test['params']['map'] # ret['disable'] = test['params']['map_disable'] # ret['strict'] = test['params']['map_strict'] # test['params']['maps'] = [ret] # del setup_map['map'] # del setup_map['map_disable'] # del setup_map['map_strict'] if 'maps' in setup_map: for xmap in setup_map['maps']: self.set_enable_map(xmap['map'], xmap['disable'] if 'disable' in xmap else False, xmap['strict'] if 'strict' in xmap else False ) del setup_map['maps'] for k in setup_map: v = setup_map[k] self.do_setup(k, v) class EMTypograph(EMT_Base): def __init__(self): EMT_Base.__init__(self) self.trets = ['EMT_Tret_Quote', 'EMT_Tret_Dash', 'EMT_Tret_Symbol', 'EMT_Tret_Punctmark', 'EMT_Tret_Number', 'EMT_Tret_Space', 'EMT_Tret_Abbr', 'EMT_Tret_Nobr', 'EMT_Tret_Date', 'EMT_Tret_OptAlign', 'EMT_Tret_Etc', 'EMT_Tret_Text'] self.group_list = { 'Quote': True, 'Dash': True, 'Nobr': True, 'Symbol': True, 'Punctmark': True, 'Number': True, 'Date': True, 'Space': True, 'Abbr': True, 'OptAlign': True, 'Text': True, 'Etc': True, } self.all_options = { 'Quote.quotes': {'description': 'Расстановка «кавычек-елочек» первого уровня', 'selector': "Quote.*quote"}, 'Quote.quotation': {'description': 'Внутренние кавычки-лапки', 'selector': "Quote", 'setting': 'no_bdquotes', 'reversed': True}, 'Dash.to_libo_nibud': 'direct', 'Dash.iz_za_pod': 'direct', 'Dash.ka_de_kas': 'direct', 'Nobr.super_nbsp': 'direct', 'Nobr.nbsp_in_the_end': 'direct', 'Nobr.phone_builder': 'direct', 'Nobr.phone_builder_v2': 'direct', 'Nobr.ip_address': 'direct', 'Nobr.spaces_nobr_in_surname_abbr': 'direct', 'Nobr.dots_for_surname_abbr': 'direct', 'Nobr.nbsp_celcius': 'direct', 'Nobr.hyphen_nowrap_in_small_words': 'direct', 'Nobr.hyphen_nowrap': 'direct', 'Nobr.nowrap': {'description': 'Nobr (по умолчанию) & nowrap', 'disabled': True, 'selector': '*', 'setting': 'nowrap'}, 'Symbol.tm_replace': 'direct', 'Symbol.r_sign_replace': 'direct', 'Symbol.copy_replace': 'direct', 'Symbol.apostrophe': 'direct', 'Symbol.degree_f': 'direct', 'Symbol.arrows_symbols': 'direct', 'Symbol.no_inches': {'description': 'Расстановка дюйма после числа', 'selector': "Quote", 'setting': 'no_inches', 'reversed': True}, 'Punctmark.auto_comma': 'direct', 'Punctmark.hellip': 'direct', 'Punctmark.fix_pmarks': 'direct', 'Punctmark.fix_excl_quest_marks': 'direct', 'Punctmark.dot_on_end': 'direct', 'Number.minus_between_nums': 'direct', 'Number.minus_in_numbers_range': 'direct', 'Number.auto_times_x': 'direct', 'Number.simple_fraction': 'direct', 'Number.math_chars': 'direct', 'Number.split_number_to_triads': 'direct', 'Number.thinsp_between_number_triads': 'direct', 'Number.thinsp_between_no_and_number': 'direct', 'Number.thinsp_between_sect_and_number': 'direct', 'Date.years': 'direct', 'Date.mdash_month_interval': 'direct', 'Date.nbsp_and_dash_month_interval': 'direct', 'Date.nobr_year_in_date': 'direct', 'Space.many_spaces_to_one': 'direct', 'Space.clear_percent': 'direct', 'Space.clear_before_after_punct': { 'description': 'Удаление пробелов перед и после знаков препинания в предложении', 'selector': 'Space.remove_space_before_punctuationmarks'}, 'Space.autospace_after': {'description': 'Расстановка пробелов после знаков препинания', 'selector': 'Space.autospace_after_*'}, 'Space.bracket_fix': { 'description': 'Удаление пробелов внутри скобок, а также расстановка пробела перед скобками', 'selector': ['Space.nbsp_before_open_quote', 'Punctmark.fix_brackets'] }, 'Abbr.nbsp_money_abbr': { 'description': 'Форматирование денежных сокращений (расстановка пробелов и привязка названия валюты к числу)', 'selector': ['Abbr.nbsp_money_abbr', 'Abbr.nbsp_money_abbr_rev'] }, 'Abbr.nobr_vtch_itd_itp': 'direct', 'Abbr.nobr_sm_im': 'direct', 'Abbr.nobr_acronym': 'direct', 'Abbr.nobr_locations': 'direct', 'Abbr.nobr_abbreviation': 'direct', 'Abbr.ps_pps': 'direct', 'Abbr.nbsp_org_abbr': 'direct', 'Abbr.nobr_gost': 'direct', 'Abbr.nobr_before_unit_volt': 'direct', 'Abbr.nbsp_before_unit': 'direct', 'OptAlign.all': {'description': 'Inline стили или CSS', 'hide': True, 'selector': 'OptAlign.*'}, 'OptAlign.oa_oquote': 'direct', 'OptAlign.oa_obracket_coma': 'direct', 'OptAlign.layout': {'description': 'Inline стили или CSS'}, 'Text.paragraphs': 'direct', 'Text.auto_links': 'direct', 'Text.email': 'direct', 'Text.breakline': 'direct', 'Text.no_repeat_words': 'direct', # 'Etc.no_nbsp_in_nobr' : 'direct', 'Etc.unicode_convert': {'description': 'Преобразовывать html-сущности в юникод', 'selector': ['*', 'Etc.nobr_to_nbsp'], 'setting': ['dounicode', 'active'], 'exact_selector': True, 'disabled': True}, 'Etc.nobr_to_nbsp': 'direct', } # /** # * Получить список имеющихся опций # * # * @return array # * all - полный список # * group - сгруппрованный по группам # */ def get_options_list(self): arr = {'all': []} by_group = {} for opt in self.all_options: arr['all'][opt] = self.get_option_info(opt) x = opt.split(".") by_group[x[0]].append(opt) arr['group'] = [] for group in self.group_list: g_info = self.group_list[group] if isinstance(g_info, bool) and g_info: tret = self.get_tret(group) if tret: info['title'] = self.title else: info['title'] = "Не определено" else: info = g_info info['name'] = group info['options'] = [] if isinstance(by_group[group], (list, tuple)): for opt in by_group[group]: info['options'].append(opt) arr['group'].append(info) return arr # /** # * Получить информацию о настройке # * # * @param string $key # * @return array|False # */ def get_option_info(self, key): if key not in self.all_options: return False if isinstance(self.all_options[key], (list, tuple, dict)): return self.all_options[key] if self.all_options[key] == "direct" or self.all_options[key] == "reverse": pa = key.split(".") tret_pattern = pa[0] tret = self.get_tret(tret_pattern) if not tret: return False if pa[1] not in tret.rules: return False array = tret.rules[pa[1]] array['way'] = self.all_options[key] return array return False # /** # * Установка одной мета-настройки # * # * @param string $name # * @param mixed $value # */ def do_setup(self, name, value): if name not in self.all_options: return # эта настройка связана с правилом ядра if isinstance(self.all_options[name], str): self.set(name, "active", value) return if isinstance(self.all_options[name], dict): if 'selector' in self.all_options[name]: setting_name = "active" if 'setting' in self.all_options[name]: setting_name = self.all_options[name]['setting'] self.set(self.all_options[name]['selector'], setting_name, value, self.all_options[name].get('exact_selector')) if name == "OptAlign.layout": if value == "style": self.set_tag_layout(LAYOUT_STYLE) if value == "class": self.set_tag_layout(LAYOUT_CLASS) # /** # * Запустить типограф со стандартными параметрами # * # * @param string $text # * @param array $options # * @return string # */ def fast_apply(self, text, options=None): if isinstance(options, dict): self.setup(options) self.set_text(text) return self.apply() # EMT = EMTypograph() # EMT.debug_enabled = True # EMT.logging = True # print EMT.fast_apply("the (tm) x") # print EMT.debug_info # print EMT.logs