You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

293 lines
13 KiB

4 years ago
# vim: fileencoding=utf-8
#
from pyparsing import originalTextFor, Literal, Word, printables, OneOrMore,\
Optional
from .base_format import Format
from ..template_engine import ParametersContainer
4 years ago
from collections import OrderedDict
try:
from lxml.etree import Element, SubElement, ElementTree, tostring
except ImportError:
from xml.etree.ElementTree import Element, SubElement, ElementTree, \
tostring
import re
4 years ago
class XMLGConfFormat(Format):
FORMAT = 'xml_gconf'
EXECUTABLE = False
_initialized = False
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()):
4 years ago
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
4 years ago
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 = "<?xml version='1.0' encoding='UTF-8'?>\n"
document_text = document_text.strip()
4 years ago
self._parse_xml_to_dictionary(document_text)
@classmethod
def _initialize_parser(cls):
'''Метод для инициализации парсерa.'''
4 years ago
action_symbols = (Literal('!') | Literal('-'))
name = originalTextFor(OneOrMore(Word(printables)))
cls._node_name = Optional(action_symbols)('action') + name('name')
cls._initialized = True
4 years ago
def _entry(self, xml_element):
'''Метод для парсинга тега entry.'''
4 years ago
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:
# Какая-то обработка ошибки.
return OrderedDict()
def _gconf(self, xml_element):
'''Метод для парсинга тега gconf.'''
4 years ago
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.'''
4 years ago
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:
# Какая-то обработка ошибки.
return OrderedDict()
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.'''
4 years ago
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.'''
4 years ago
output_dictionary = OrderedDict()
try:
element_name = ('', xml_element.tag,
*xml_element.items())
except Exception:
# Какая-то обработка ошибки.
return OrderedDict()
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.'''
4 years ago
element_name = ('', 'stringvalue')
value = xml_element.text
if value is not None:
return OrderedDict({element_name: value})
else:
# Пустая строка. Возможно ошибка.
# Какая-то обработка ошибки.
return OrderedDict({element_name: ''})
def _default(self, xml_element):
'''Уже не акутальный метод, вместо него теперь _unknown'''
4 years ago
output_dictionary = OrderedDict()
element_name = ('', xml_element.tag, *xml_element.items())
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 _li(self, xml_element):
'''Метод для разбора элементов с тегом li.'''
4 years ago
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.'''
4 years ago
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)
4 years ago
def _build_section(self, current_element, dictionary):
'''Метод для перевода словаря xml-документа обратно в текст документа.
Для этого рекурсивно строит дерево xml-документа пригодное работы lxml.
'''
4 years ago
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'\
or element_tag == 'default':
include_element = SubElement(current_element,
element_tag,
**element_attributes)
self._build_section(include_element,
dictionary[dict_element])
elif element_tag == 'entry':
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]
def _get_header_and_document_text(self, input_text,
template_path,
already_changed=False):
'''Метод для создания заголовка измененного файла и удаления его из
текста исходного файла.'''
header_pattern = (r'<!--' + r'-' * 76 + r'\n' +
r'\s*Modified by Calculate Utilities [\d\w\.]*\n' +
r'\s*Processed template files:\n' +
r'\s*(?P<template_paths>(\s*[/\w\d\-_\.]*\n)+)' +
r'-' * 77 + r'-->\n?')
template_paths = []
if already_changed:
header_regex = re.compile(header_pattern)
parsing_result = header_regex.search(input_text)
template_paths.extend(parsing_result.groupdict()[
'template_paths'].strip().split('\n'))
template_paths.append(template_path)
header = ("<?xml version='1.0' encoding='UTF-8'?>\n" +
'<!--' + '-' * 76 + '\n' +
'Modified by Calculate Utilities {}\n' +
'Processed template files:\n' +
'\n'.join(template_paths) + '\n' +
'-' * 77 + '-->\n').format(self.CALCULATE_VERSION)
document_text = re.sub(header_pattern, '', input_text)
return header, document_text