# vim: fileencoding=utf-8 # from .base_format import Format, FormatError from ..template_engine import ParametersContainer from collections import OrderedDict import re try: from lxml.etree.ElementTree import fromstring except ImportError: from xml.etree.ElementTree import fromstring class RegexFormat(Format): FORMAT = 'regex' EXECUTABLE = False FORMAT_PARAMETERS = {'multiline', 'dotall', 'comment'} comment_symbol = '#' def __init__(self, document_text: str, template_path, ignore_comments=False, join_before=False, add_header=False, already_changed=False, parameters=ParametersContainer(), **kwargs): processing_methods = OrderedDict() super().__init__(processing_methods) self._multiline_flag = parameters.multiline self._dotall_flag = parameters.dotall if parameters.comment is not False: self.comment_symbol = parameters.comment self._parsed_patch = None if add_header and not ignore_comments: self.header, self._document_text, self.shebang =\ self._get_header_and_document_text( document_text, template_path, already_changed=already_changed, check_shebang=True) else: self.header = '' self.shebang = '' self._document_text = document_text self._FLAG_VALUES = {'True': True, 'False': False, 'true': True, 'false': False, '1': True, '0': False} self._XML_ROOT_LINE = '\ {0}' def _parse_patch(self, patch_text): '''Метод, составляющий из текста шаблона xml документ и разбирающий его с помощью lxml.''' xml_patch = self._XML_ROOT_LINE.format(patch_text.strip()) try: self._parsed_patch = fromstring(xml_patch) except Exception: raise FormatError('can not parse patch document') def join_template(self, template): tmp_multiline = template._multiline_flag tmp_dotall = template._dotall_flag template._multiline_flag = self._multiline_flag template._dotall_flag = self._dotall_flag self._document_text = template._join(self._document_text) template._multiline_flag = tmp_multiline template._dotall_flag = tmp_dotall def _join(self, input_text): '''Метод для запуска наложения патча.''' self._parse_patch(self._document_text) if not input_text.strip() == '': self._document_to_patch = input_text else: return input_text if not self._patch_document(input_text): raise FormatError('Error: Can not run patch.') else: after_patch = self._document_to_patch self._document_to_patch = '' return after_patch def _patch_document(self, document_to_patch): '''Метод, обходящий теги шаблона и использующий указанные в нем регулярные выражения.''' patch_iterator = self._parsed_patch.iter() PATCH_DOCUMENT_TAGS = ('reg', 'text') patch_element = next(patch_iterator, False) if not patch_element or not patch_element.tag == 'patch': raise FormatError('incorrect text of the template') while True: for patch_tag in PATCH_DOCUMENT_TAGS: patch_element = next(patch_iterator, None) # Предупреждение если отсутствует последний bla if patch_element is None: if patch_tag == 'text': raise FormatError('last Text ' 'object is missed.') else: break if patch_element.tag == patch_tag: # Если тег пустой -- это ошибка. if patch_element.text is not None: element_text = patch_element.text if patch_tag == "reg" and element_text.strip() == '': raise FormatError( ("Error: Incorrect text of the " "template: <{0}>%s").format( patch_tag )) elif patch_tag == "reg": raise FormatError("Error: Incorrect text of the " "template: <{0}>".format( patch_tag)) else: element_text = '' if patch_tag == 'reg': dotall = patch_element.attrib.get('dotall', False) regex_flags = 0 if 'multiline' in patch_element.attrib: multiline = patch_element.attrib['multiline'] if multiline not in self._FLAG_VALUES: raise FormatError('invalid multiline value') else: multiline = self._FLAG_VALUES[multiline] # Если глобально флаг MULTILINE включен, но в # атрибутах тэга этот флаг присутствует со # значением False -- для этого регулярного # выражения этот флаг также будет False. multiline_global = self._multiline_flag & multiline else: multiline = False multiline_global = self._multiline_flag if multiline_global or multiline: regex_flags |= re.MULTILINE if 'dotall' in patch_element.attrib: dotall = patch_element.attrib['dotall'] if dotall not in self._FLAG_VALUES: raise FormatError('invalid dotall value') else: dotall = self._FLAG_VALUES[dotall] # Если глобально флаг DOTALL включен, но в # атрибутах тэга этот флаг присутствует со # значением False -- для этого регулярного # выражения этот флаг также будет False. dotall_global = self._dotall_flag & dotall else: dotall = False dotall_global = self._dotall_flag if dotall_global or dotall: regex_flags |= re.DOTALL regex_expression = re.compile(element_text, regex_flags) else: text_for_replace = element_text else: if patch_element.tag in PATCH_DOCUMENT_TAGS: error_message = '<{0}> is expected, <{1}> instead.'.\ format(patch_tag, patch_element.tag) else: error_message = 'unknown tag: {0}'.format( patch_element.tag ) raise ("incorrect text of the template: {}".format( error_message)) else: self._document_to_patch = re.sub(regex_expression, text_for_replace, self._document_to_patch) continue return True @property def document_text(self): return '{}{}{}'.format(self.shebang, self.header, self._document_text)