|
|
# vim: fileencoding=utf-8
|
|
|
#
|
|
|
from pyparsing import originalTextFor, Literal, Word, printables, OneOrMore,\
|
|
|
Optional
|
|
|
from .base_format import Format
|
|
|
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
|
|
|
import re
|
|
|
|
|
|
|
|
|
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()):
|
|
|
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 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()
|
|
|
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:
|
|
|
# Какая-то обработка ошибки.
|
|
|
return OrderedDict()
|
|
|
|
|
|
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:
|
|
|
# Какая-то обработка ошибки.
|
|
|
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.'''
|
|
|
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:
|
|
|
# Какая-то обработка ошибки.
|
|
|
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.'''
|
|
|
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'''
|
|
|
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.'''
|
|
|
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'\
|
|
|
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
|