# vim: fileencoding=utf-8 # from collections import OrderedDict from jinja2 import Environment, PackageLoader from pprint import pprint from copy import copy try: from lxml.etree.ElementTree import fromstring except ImportError: from xml.etree.ElementTree import fromstring class FormatError(Exception): pass class BaseFormat(): FORMAT = 'none' def __init__(self, processing_methods): self._processing_methods = processing_methods self._document_dictionary = OrderedDict() self._item_to_add = OrderedDict() self.TEMPLATES_DIRECTORY = 'templates' self._fatal_error_flag = False self._ready_to_update = False self._match = False self._need_finish = False self._comments_processing = False self._join_before = False self._join_before_in_areas = False # для отладки. self._line_timer = 0 def _lines_to_dictionary(self, document_lines): # print('Lines processing...') for line in document_lines: # print(self._line_timer, '\t', line) for processing_method in self._processing_methods: processing_method(line) if self._fatal_error_flag: # Действия если файл невозможно разобрать. self._document_dictionary = OrderedDict() raise FormatError('line {} is not correct.'. format(self._line_timer)) if self._is_match(): if self._is_ready_to_update(): self._document_dictionary.update(self._item_to_add) break else: # Действия если не удалось разобрать строку. self._document_dictionary = OrderedDict() raise FormatError('line {} is not correct.'. format(self._line_timer)) self._line_timer += 1 if self._need_finish: self._finish_method() def _parse_xml_to_dictionary(self, xml_document_text): root = fromstring(xml_document_text) self._document_dictionary = self._processing_methods[root.tag](root) def print_dictionary(self): pprint(self._document_dictionary) def join_template(self, template): self._join(self._document_dictionary, template._document_dictionary, self._join_before) def _get_list_of_logic_lines(self, text): list_of_lines = [] lines_to_join = [] for line in text.splitlines(): line = line.strip() if line == '': continue if not line.endswith("\\"): lines_to_join.append(line) joined_line = "".join(lines_to_join) list_of_lines.append(joined_line) lines_to_join = [] else: lines_to_join.append(line[:-1]) return list_of_lines def _join(self, original, template, join_before): if template == OrderedDict(): return if join_before: forwarded_items = OrderedDict() for key_value in template: if key_value[0] == '!': # Удаление соответствующего элемента из original. # Сначала получаем ключ без символа действия. if isinstance(key_value, tuple): item_to_delete = ('',) + key_value[1:] elif isinstance(key_value, str): item_to_delete = key_value[1:] # Удаляем соответствующий элемент, если он есть в оригинале. if item_to_delete in original.keys(): del(original[item_to_delete]) elif key_value[0] == '-': # Замена соответствующего элемента из original. # Сначала получаем ключ без символа действия. if isinstance(key_value, tuple): item_to_replace = ('',) + key_value[1:] elif isinstance(key_value, str): item_to_replace = key_value[1:] # Если соответствующего элемента нет в оригинале -- пропускаем. if item_to_replace not in original.keys(): continue # Если секция для замены в шаблоне пустая -- удаляем # соответствующую секцию. if isinstance(template[key_value], dict) and\ template[key_value] == OrderedDict(): original.pop(item_to_replace) continue # Если символ замены стоит перед параметром, а не перед # секцией -- просто заменяем значение параметра. if not isinstance(template[key_value], dict): original[item_to_replace] = template[key_value] continue # Если обработка комментариев включена -- сохраняем # комментарии к заменяемой секции. if self._comments_processing: if '#' in original[item_to_replace]: replacement = OrderedDict({'#': original[item_to_replace] ['#']} ) # накладываем словарь шаблона на пустой словарь, чтобы # выполнить все управляющие элементы, которые # могут туда попасть. self._join(replacement, template[key_value], self._join_before_in_areas) else: replacement = OrderedDict() self._join(replacement, template[key_value], self._join_before_in_areas) # Если после наложения шаблона словарь замены оказался # пустым -- удаляем соотвествующий элемент в оригинале. if (replacement == OrderedDict() or replacement.keys() == {'#'}): del(original[item_to_replace]) else: original[item_to_replace] = replacement else: original[item_to_replace] = OrderedDict() self._join(original[item_to_replace], template[key_value], self._join_before_in_areas) if (original[item_to_replace] == OrderedDict() or original[item_to_replace].keys() == {'#'}): del(original[item_to_replace]) elif key_value not in original.keys(): if isinstance(template[key_value], dict): dictionary_to_add = OrderedDict() self._join(dictionary_to_add, template[key_value], self._join_before_in_areas) if dictionary_to_add != OrderedDict(): if not join_before: original[key_value] = dictionary_to_add else: forwarded_items[key_value] = dictionary_to_add else: if not join_before: original[key_value] = template[key_value] else: forwarded_items[key_value] = template[key_value] else: if isinstance(original[key_value], dict) and \ isinstance(template[key_value], dict): self._join(original[key_value], template[key_value], self._join_before_in_areas) else: if self._comments_processing: original[key_value][-1] = template[key_value][-1] else: original[key_value] = template[key_value] if join_before: for key_value in reversed(forwarded_items.keys()): original[key_value] = forwarded_items[key_value] original.move_to_end(key_value, last=False) def make_template(self, template): full_diff, set_to_check = self.compare_dictionaries( self._document_dictionary, template._document_dictionary ) template_object = copy(self) template_object._document_dictionary = full_diff return template_object def compare_dictionaries(self, dict_1, dict_2): to_remove_dictionary = OrderedDict() to_add_dictionary = OrderedDict() to_replace_dictionary = OrderedDict() unchanged_set = set() to_remove = dict_1.keys() - dict_2.keys() if '#' in to_remove: to_remove.remove('#') for key in dict_1: if key in to_remove: if isinstance(key, tuple): new_key = ('!', *key[1:]) else: new_key = '!{}'.format(key) if isinstance(dict_1[key], dict): to_remove_dictionary.update({new_key: dict_1[key]}) else: if self._comments_processing: to_remove_dictionary.update({new_key: [dict_1[key][-1]]}) else: to_remove_dictionary.update({new_key: dict_1[key]}) to_add = dict_2.keys() - dict_1.keys() if '#' in to_add: to_add.remove('#') for key in dict_2: if key in to_add: if isinstance(dict_2[key], dict): section = dict_2[key].copy() if '#' in section: section.remove('#') to_add_dictionary.update({key: section}) else: if self._comments_processing: to_add_dictionary.update({key: [dict_2[key][-1]]}) else: to_add_dictionary.update({key: dict_2[key]}) intersect = dict_1.keys() & dict_2.keys() for key in intersect: if (isinstance(dict_1[key], dict) and isinstance(dict_2[key], dict) and dict_1[key] != dict_2[key]): diff, set_to_check = self.compare_dictionaries(dict_1[key], dict_2[key]) if set_to_check: to_add_dictionary.update({key: diff}) else: if isinstance(key, tuple): new_key = ('-', *key[1:]) else: new_key = '-{}'.format(key) to_replace_dictionary.update({new_key: dict_2[key]}) elif dict_1[key] != dict_2[key]: if self._comments_processing: to_add_dictionary.update({key: [dict_2[key][-1]]}) else: to_add_dictionary.update({key: dict_2[key]}) else: unchanged_set.add(key) full_diff = OrderedDict() full_diff.update(**to_remove_dictionary, **to_replace_dictionary, **to_add_dictionary) return full_diff, unchanged_set @property def document_text(self): file_loader = PackageLoader('calculate.templates.format', self.TEMPLATES_DIRECTORY) formats_environment = Environment(loader=file_loader, trim_blocks=True, lstrip_blocks=True) formats_environment.globals.update(zip=zip) formats_environment.add_extension('jinja2.ext.do') template = formats_environment.get_template(self.FORMAT) document_text = template.render( document_dictionary=self._document_dictionary ) return document_text def _finish_method(self): pass def _is_ready_to_update(self): is_ready, self._ready_to_update = self._ready_to_update, False return is_ready def _is_match(self): is_match, self._match = self._match, False return is_match