# vim: fileencoding=utf-8 # from pyparsing import originalTextFor, Literal, Word, printables, OneOrMore,\ Optional from .base_format import Format, FormatError from ..template_engine import ParametersContainer from collections import OrderedDict try: from lxml.etree import Element, SubElement, ElementTree, tostring except ImportError: from xml.etree.ElementTree import Element, SubElement, ElementTree, \ tostring from pprint import pprint class XMLGConfFormat(Format): FORMAT = 'xml_gconf' EXECUTABLE = False _initialized = False comment_symbol = 'xml' def __new__(cls, *args, **kwargs): if not cls._initialized: cls._initialize_parser() return super().__new__(cls) def __init__(self, document_text: str, template_path, ignore_comments=False, join_before=False, add_header=False, already_changed=False, parameters=ParametersContainer()): processing_methods = OrderedDict({'gconf': self._gconf, 'entry': self._entry, 'dir': self._dir, 'stringvalue': self._stringvalue, 'default': self._default, 'local_schema': self._local_schema, 'li': self._li, 'longdesc': self._longdesc, 'unknown': self._unknown}) super().__init__(processing_methods) self._initialize_parser() self._join_before = join_before if not document_text.strip(): document_text = ("" "") if add_header and not ignore_comments: self.header, document_text = self._get_header_and_document_text( document_text, template_path, already_changed=already_changed) else: self.header = "\n" document_text = document_text.strip() self._parse_xml_to_dictionary(document_text) @classmethod def _initialize_parser(cls): '''Метод для инициализации парсерa.''' action_symbols = (Literal('!') | Literal('-')) name = originalTextFor(OneOrMore(Word(printables))) cls._node_name = Optional(action_symbols)('action') + name('name') cls._initialized = True def _entry(self, xml_element): '''Метод для парсинга тега entry.''' try: element_items = OrderedDict(xml_element.attrib) name = element_items.pop('name') parsing_result = self._node_name.parseString(name) if 'value' in element_items: entry_value = element_items.pop('value') elif 'ltype' in element_items: entry_value = [] for child in xml_element: item_to_add = self._processing_methods.get(child.tag, self._unknown )(child) entry_value.append(item_to_add) else: entry_value = OrderedDict() for child in xml_element: item_to_add = self._processing_methods.get(child.tag, self._unknown )(child) entry_value.update(item_to_add) element_name = (parsing_result.action, xml_element.tag, ('name', parsing_result.name), *element_items.items()) return OrderedDict({element_name: entry_value}) except Exception as error: raise FormatError("Can not parse template tag 'entry':" " {str(error)}.") def _gconf(self, xml_element): '''Метод для парсинга тега gconf.''' output_dictionary = OrderedDict() element_name = ('', xml_element.tag) for child in xml_element: item_to_add = self._processing_methods.get(child.tag, self._unknown)(child) output_dictionary.update(item_to_add) return OrderedDict({element_name: output_dictionary}) def _dir(self, xml_element): '''Метод для парсинга тега dir.''' output_dictionary = OrderedDict() try: parsing_result = self._node_name.parseString( xml_element.attrib['name'] ) element_name = (parsing_result.action, xml_element.tag, ('name', parsing_result.name)) except Exception as error: raise FormatError("Can not parse template tag 'dir':" f" {str(error)}.") for child in xml_element: item_to_add = self._processing_methods.get(child.tag, self._unknown)(child) output_dictionary.update(item_to_add) return OrderedDict({element_name: output_dictionary}) def _longdesc(self, xml_element): '''Метод для парсинга тега longdesc.''' element_name = ('', 'longdesc') description = xml_element.text if description is not None: return OrderedDict({element_name: description}) else: # Пустая строка. Возможно ошибка. # Какая-то обработка ошибки. return OrderedDict({element_name: ''}) def _local_schema(self, xml_element): '''Метод для парсинга тега local_schema.''' output_dictionary = OrderedDict() try: element_name = ('', xml_element.tag, *xml_element.items()) except Exception as error: raise FormatError("Can not parse template tag 'local_schema':" " {str(error)}.") for child in xml_element: item_to_add = self._processing_methods.get(child.tag, self._unknown)(child) output_dictionary.update(item_to_add) return OrderedDict({element_name: output_dictionary}) def _stringvalue(self, xml_element): '''Метод для парсинга тега stringvalue.''' value = xml_element.text element_name = ('', xml_element.tag) if value is not None: return OrderedDict({element_name: value}) else: # Пустая строка. Возможно ошибка. # Какая-то обработка ошибки. return OrderedDict({element_name: ''}) def _default(self, xml_element): element_name = ('', xml_element.tag, *xml_element.items()) element_items = {name: value for name, value in xml_element.items()} if element_items['type'] == 'list': default_value = [] for child in xml_element: item_to_add = self._processing_methods.get(child.tag, self._unknown )(child) default_value.append(item_to_add) else: default_value = OrderedDict() for child in xml_element: item_to_add = self._processing_methods.get( child.tag, self._unknown)(child) default_value.update(item_to_add) return OrderedDict({element_name: default_value}) def _li(self, xml_element): '''Метод для разбора элементов с тегом li.''' child = next(iter(xml_element)) list_element = self._processing_methods.get(child.tag, self._unknown)(child) # Единственным возможным типом списковых значений пока является string. string_value = next(iter(list_element.values())) return string_value def _unknown(self, xml_element): # Действия если элемент неизвестен. element_name = ('', Element.tag) return OrderedDict({element_name: 'Unknown element'}) @property def document_text(self): '''Метод для получения исходного текста документа. Использует рекурсивный метод _build_section.''' gconf_header = next(iter(self._document_dictionary)) root = Element('gconf') self._build_section(root, self._document_dictionary[gconf_header]) document_tree = ElementTree(root) xml_document = tostring(document_tree, encoding="UTF-8", pretty_print=True).decode() return '{}{}'.format(self.header, xml_document) def _build_section(self, current_element, dictionary): '''Метод для перевода словаря xml-документа обратно в текст документа. Для этого рекурсивно строит дерево xml-документа пригодное работы lxml. ''' for dict_element in dictionary.keys(): element_tag = dict_element[1] element_attributes = OrderedDict({key: value for key, value in dict_element[2:]}) if element_tag == 'dir' or element_tag == 'local_schema': include_element = SubElement(current_element, element_tag, **element_attributes) self._build_section(include_element, dictionary[dict_element]) elif element_tag == 'entry' or element_tag == 'default': if isinstance(dictionary[dict_element], OrderedDict): include_element = SubElement(current_element, element_tag, **element_attributes) self._build_section(include_element, dictionary[dict_element]) elif 'ltype' in element_attributes: if element_attributes['ltype'] == 'string': entry_element = SubElement(current_element, element_tag, **element_attributes) for value in dictionary[dict_element]: list_element = SubElement(entry_element, 'li', type='string') include_element = SubElement(list_element, 'stringvalue') include_element.text = value else: include_element = SubElement(current_element, element_tag, **element_attributes, value=dictionary[dict_element] ) elif element_tag == 'longdesc' or element_tag == 'stringvalue': include_element = SubElement(current_element, element_tag) include_element.text = dictionary[dict_element]