|
|
# vim: fileencoding=utf-8
|
|
|
#
|
|
|
from .base_format import BaseFormat, FormatError
|
|
|
from ..template_engine import ParametersContainer
|
|
|
from collections import OrderedDict
|
|
|
import re
|
|
|
try:
|
|
|
from lxml.etree.ElementTree import fromstring
|
|
|
except ImportError:
|
|
|
from xml.etree.ElementTree import fromstring
|
|
|
|
|
|
|
|
|
class PatchFormat(BaseFormat):
|
|
|
FORMAT = 'patch'
|
|
|
EXECUTABLE = False
|
|
|
FORMAT_PARAMETERS = {'multiline', 'dotall', 'comment'}
|
|
|
|
|
|
def __init__(self, document_text: str,
|
|
|
ignore_comments=False,
|
|
|
join_before=False,
|
|
|
parameters=ParametersContainer()):
|
|
|
processing_methods = OrderedDict()
|
|
|
super().__init__(processing_methods)
|
|
|
|
|
|
self.changed_files = dict()
|
|
|
|
|
|
self._multiline_flag = parameters.multiline
|
|
|
self._dotall_flag = parameters.dotall
|
|
|
self._parsed_patch = None
|
|
|
|
|
|
self._document_text = document_text
|
|
|
self._FLAG_VALUES = {'True': True,
|
|
|
'False': False,
|
|
|
'true': True,
|
|
|
'false': False,
|
|
|
'1': True,
|
|
|
'0': False}
|
|
|
|
|
|
self._XML_ROOT_LINE = '<?xml version="1.0" encoding="utf-8"?>\
|
|
|
<patch>{0}</patch>'
|
|
|
|
|
|
def _parse_patch(self, patch_text):
|
|
|
'''Метод, составляющий из текста шаблона xml документ и разбирающий его
|
|
|
с помощью lxml.'''
|
|
|
xml_patch = self._XML_ROOT_LINE.format(patch_text.strip())
|
|
|
try:
|
|
|
self._parsed_patch = fromstring(xml_patch)
|
|
|
except Exception:
|
|
|
raise FormatError('can not parse patch document')
|
|
|
|
|
|
def join_template(self, template):
|
|
|
tmp_multiline = template._multiline_flag
|
|
|
tmp_dotall = template._dotall_flag
|
|
|
|
|
|
template._multiline_flag = self._multiline_flag
|
|
|
template._dotall_flag = self._dotall_flag
|
|
|
|
|
|
self._document_text = template._join(self._document_text)
|
|
|
|
|
|
template._multiline_flag = tmp_multiline
|
|
|
template._dotall_flag = tmp_dotall
|
|
|
|
|
|
def _join(self, input_text):
|
|
|
'''Метод для запуска наложения патча.'''
|
|
|
self._parse_patch(self._document_text)
|
|
|
|
|
|
if not input_text.strip() == '':
|
|
|
self._document_to_patch = input_text
|
|
|
else:
|
|
|
return input_text
|
|
|
|
|
|
if not self._patch_document(input_text):
|
|
|
raise FormatError('Error: Can not run patch.')
|
|
|
else:
|
|
|
after_patch = self._document_to_patch
|
|
|
self._document_to_patch = ''
|
|
|
|
|
|
return after_patch
|
|
|
|
|
|
def _patch_document(self, document_to_patch):
|
|
|
'''Метод, обходящий теги шаблона и использующий указанные в нем
|
|
|
регулярные выражения.'''
|
|
|
patch_iterator = self._parsed_patch.getiterator()
|
|
|
PATCH_DOCUMENT_TAGS = ('reg', 'text')
|
|
|
|
|
|
patch_element = next(patch_iterator, False)
|
|
|
|
|
|
if not patch_element or not patch_element.tag == 'patch':
|
|
|
raise FormatError('incorrect text of the template')
|
|
|
|
|
|
while True:
|
|
|
for patch_tag in PATCH_DOCUMENT_TAGS:
|
|
|
patch_element = next(patch_iterator, None)
|
|
|
|
|
|
if patch_element is None:
|
|
|
if patch_tag == 'text':
|
|
|
raise FormatError('last <text>Text</text> '
|
|
|
'object is missed.')
|
|
|
else:
|
|
|
break
|
|
|
|
|
|
if patch_element.tag == patch_tag:
|
|
|
if patch_element.text is not None:
|
|
|
element_text = patch_element.text.strip()
|
|
|
if element_text == '':
|
|
|
raise FormatError(
|
|
|
("Error: Incorrect text of the "
|
|
|
"template: <{0}>%s</{0}>").format(
|
|
|
patch_tag
|
|
|
))
|
|
|
else:
|
|
|
raise FormatError("Error: Incorrect text of the "
|
|
|
"template: <{0}></{0}>").format(
|
|
|
patch_tag
|
|
|
)
|
|
|
|
|
|
if patch_tag == 'reg':
|
|
|
dotall = patch_element.attrib.get('dotall', False)
|
|
|
regex_flags = 0
|
|
|
|
|
|
if 'multiline' in patch_element.attrib:
|
|
|
multiline = patch_element.attrib['multiline']
|
|
|
if multiline not in self._FLAG_VALUES:
|
|
|
raise FormatError('invalid multiline value')
|
|
|
else:
|
|
|
multiline = self._FLAG_VALUES[multiline]
|
|
|
|
|
|
# Если глобально флаг MULTILINE включен, но в
|
|
|
# атрибутах тэга <reg> этот флаг присутствует со
|
|
|
# значением False -- для этого регулярного
|
|
|
# выражения этот флаг также будет False.
|
|
|
multiline_global = self._multiline_flag & multiline
|
|
|
else:
|
|
|
multiline = False
|
|
|
multiline_global = self._multiline_flag
|
|
|
|
|
|
if multiline_global or multiline:
|
|
|
regex_flags |= re.MULTILINE
|
|
|
|
|
|
if 'dotall' in patch_element.attrib:
|
|
|
dotall = patch_element.attrib['dotall']
|
|
|
if dotall not in self._FLAG_VALUES:
|
|
|
raise FormatError('invalid dotall value')
|
|
|
else:
|
|
|
dotall = self._FLAG_VALUES[dotall]
|
|
|
|
|
|
# Если глобально флаг DOTALL включен, но в
|
|
|
# атрибутах тэга <reg> этот флаг присутствует со
|
|
|
# значением False -- для этого регулярного
|
|
|
# выражения этот флаг также будет False.
|
|
|
dotall_global = self._dotall_flag & dotall
|
|
|
else:
|
|
|
dotall = False
|
|
|
dotall_global = self._dotall_flag
|
|
|
|
|
|
if dotall_global or dotall:
|
|
|
regex_flags |= re.DOTALL
|
|
|
|
|
|
regex_expression = re.compile(element_text,
|
|
|
regex_flags)
|
|
|
else:
|
|
|
text_for_replace = element_text
|
|
|
else:
|
|
|
if patch_element.tag in PATCH_DOCUMENT_TAGS:
|
|
|
error_message = '<{0}> is expected, <{1}> instead.'.\
|
|
|
format(patch_tag,
|
|
|
patch_element.tag)
|
|
|
else:
|
|
|
error_message = 'unknown tag: {0}'.format(
|
|
|
patch_element.tag
|
|
|
)
|
|
|
raise ("incorrect text of the template: {}".format(
|
|
|
error_message))
|
|
|
else:
|
|
|
self._document_to_patch = re.sub(regex_expression,
|
|
|
text_for_replace,
|
|
|
self._document_to_patch)
|
|
|
continue
|
|
|
return True
|
|
|
|
|
|
@property
|
|
|
def document_text(self):
|
|
|
return self._document_text
|