|
|
|
|
# vim: fileencoding=utf-8
|
|
|
|
|
#
|
|
|
|
|
from .base_format import Format
|
|
|
|
|
from ..template_engine import ParametersContainer
|
|
|
|
|
from pyparsing import originalTextFor, Literal, Word, printables, OneOrMore,\
|
|
|
|
|
Optional
|
|
|
|
|
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 XMLXfceFormat(Format):
|
|
|
|
|
FORMAT = 'xml_xfce'
|
|
|
|
|
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({'channel': self._channel,
|
|
|
|
|
'property': self._property,
|
|
|
|
|
'value': self._value,
|
|
|
|
|
'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()
|
|
|
|
|
if document_text == '':
|
|
|
|
|
self._document_dictionary = OrderedDict()
|
|
|
|
|
else:
|
|
|
|
|
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._ELEMENT_ATTRIBUTES = ('tag', 'name', 'type', 'value')
|
|
|
|
|
cls._CHANNEL_ATTRIBUTES = ('tag', 'name', 'version')
|
|
|
|
|
cls._VALUE_ATTRIBUTES = ('tag', 'type', 'value')
|
|
|
|
|
|
|
|
|
|
def _property(self, xml_element):
|
|
|
|
|
'''Метод для парсинга тега property.'''
|
|
|
|
|
try:
|
|
|
|
|
parsing_result = self._node_name.parseString(
|
|
|
|
|
xml_element.attrib['name']
|
|
|
|
|
)
|
|
|
|
|
element_name = (parsing_result.action,
|
|
|
|
|
xml_element.tag,
|
|
|
|
|
parsing_result.name,
|
|
|
|
|
xml_element.attrib['type'])
|
|
|
|
|
except Exception:
|
|
|
|
|
# Какая-то обработка ошибки.
|
|
|
|
|
return OrderedDict()
|
|
|
|
|
|
|
|
|
|
if xml_element.attrib['type'] == 'empty':
|
|
|
|
|
output = OrderedDict()
|
|
|
|
|
for child in xml_element:
|
|
|
|
|
child_value = self._processing_methods.get(child.tag,
|
|
|
|
|
self._unknown
|
|
|
|
|
)(child)
|
|
|
|
|
output.update(child_value)
|
|
|
|
|
|
|
|
|
|
elif xml_element.attrib['type'] == 'array':
|
|
|
|
|
output = []
|
|
|
|
|
for child in xml_element:
|
|
|
|
|
child_value = self._processing_methods.get(child.tag,
|
|
|
|
|
self._unknown
|
|
|
|
|
)(child)
|
|
|
|
|
output.append(child_value)
|
|
|
|
|
else:
|
|
|
|
|
try:
|
|
|
|
|
output = xml_element.attrib['value']
|
|
|
|
|
except KeyError:
|
|
|
|
|
# Какая-то обработка ошибки.
|
|
|
|
|
return OrderedDict()
|
|
|
|
|
|
|
|
|
|
return OrderedDict({element_name: output})
|
|
|
|
|
|
|
|
|
|
def _value(self, xml_element):
|
|
|
|
|
'''Метод для парсинга тега value.'''
|
|
|
|
|
try:
|
|
|
|
|
value = (xml_element.tag,
|
|
|
|
|
xml_element.attrib['type'],
|
|
|
|
|
xml_element.attrib['value'])
|
|
|
|
|
except KeyError:
|
|
|
|
|
# Какая-то обработка ошибки.
|
|
|
|
|
return ('',)
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
def _channel(self, xml_element):
|
|
|
|
|
'''Метод для парсинга тега channel.'''
|
|
|
|
|
output_dictionary = OrderedDict()
|
|
|
|
|
try:
|
|
|
|
|
parsing_result = self._node_name.parseString(
|
|
|
|
|
xml_element.attrib['name']
|
|
|
|
|
)
|
|
|
|
|
element_name = (parsing_result.action,
|
|
|
|
|
xml_element.tag,
|
|
|
|
|
parsing_result.name,
|
|
|
|
|
xml_element.attrib['version'])
|
|
|
|
|
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 _unknown(self, xml_element):
|
|
|
|
|
# Действия если элемент неизвестен.
|
|
|
|
|
element_name = ('', xml_element.tag)
|
|
|
|
|
return OrderedDict({element_name: 'Unknown element'})
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def document_text(self):
|
|
|
|
|
'''Метод для получения исходного текста документа. Использует
|
|
|
|
|
рекурсивный метод _build_section.'''
|
|
|
|
|
channel = next(iter(self._document_dictionary.keys()))
|
|
|
|
|
channel_head = OrderedDict(
|
|
|
|
|
{key: value for key, value in
|
|
|
|
|
zip(self._CHANNEL_ATTRIBUTES, channel[1:])}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
root = Element(channel_head.pop('tag'), **channel_head)
|
|
|
|
|
|
|
|
|
|
self._build_section(root, self._document_dictionary[channel])
|
|
|
|
|
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_head = OrderedDict({key: value for key, value in
|
|
|
|
|
zip(self._ELEMENT_ATTRIBUTES,
|
|
|
|
|
dict_element[1:])})
|
|
|
|
|
|
|
|
|
|
if element_head['type'] == 'empty':
|
|
|
|
|
include_element = SubElement(current_element,
|
|
|
|
|
element_head.pop('tag'),
|
|
|
|
|
**element_head)
|
|
|
|
|
self._build_section(include_element,
|
|
|
|
|
dictionary[dict_element])
|
|
|
|
|
|
|
|
|
|
elif element_head['type'] == 'array':
|
|
|
|
|
include_element = SubElement(current_element,
|
|
|
|
|
element_head.pop('tag'),
|
|
|
|
|
**element_head)
|
|
|
|
|
|
|
|
|
|
for list_element in dictionary[dict_element]:
|
|
|
|
|
list_element_head = OrderedDict(
|
|
|
|
|
{key: value for key, value in
|
|
|
|
|
zip(self._VALUE_ATTRIBUTES,
|
|
|
|
|
list_element)}
|
|
|
|
|
)
|
|
|
|
|
SubElement(include_element,
|
|
|
|
|
list_element_head.pop('tag'),
|
|
|
|
|
**list_element_head)
|
|
|
|
|
else:
|
|
|
|
|
SubElement(current_element,
|
|
|
|
|
element_head.pop('tag'),
|
|
|
|
|
**element_head,
|
|
|
|
|
value=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
|