"""Cioset management."""
from __future__ import annotations
from os.path import normpath, join, dirname
from re import sub as re_sub
from lxml.etree import Element, ElementTree, fromstring, tostring
from chrysalio.lib.utils import normalize_spaces, make_id
from cioprocessor.relaxng import RELAXNG_CIOSET
from ciowarehouse2.lib.ciopath import CioPath
from .utils import special_unprotect
# =============================================================================
[docs]
def cioset_update(original: ElementTree, values: dict) -> ElementTree:
"""Update original Cioset XML according to values.
:type original: lxml.etree._ElementTree
:param original:
Initial content of the file as a XML DOM object.
:param dict values:
Values of the form.
:rtype: lxml.etree._ElementTree
"""
# Title
namespace = RELAXNG_CIOSET['namespace']
value = normalize_spaces(values['cioset_title'].replace(' ', '‧'))
title_elt = original.find(f"{{{namespace}}}title")
if not value and title_elt is not None:
original.getroot().remove(title_elt)
elif value:
if title_elt is None:
title_elt = Element('title')
original.getroot().insert(0, title_elt)
title_elt.text = value
# Division
namespaces = {'set': namespace}
for elt in original.xpath('//set:division', namespaces=namespaces):
num = int(
elt.xpath(
'count(preceding::set:division|ancestor::set:division)',
namespaces=namespaces))
_update_title(values, f'division{num}', elt)
# Resources, copies and values
for tag in ('resources', 'copies', 'values'):
for num, elt in enumerate(original.xpath( # yapf: disable
f'//set:{tag}', namespaces=namespaces)):
_update_title(values, f'{tag}{num}', elt)
value = make_id(values.get(f'cioset_{tag}{num}_for', ''), 'class')
if value:
elt.set('for', value)
else:
elt.attrib.pop('for', None)
# Defines and values
for tag in ('define', 'value'):
for num, elt in enumerate(original.xpath( # yapf: disable
f'//set:{tag}', namespaces=namespaces)):
elt.text = value2string(elt, values.get(f'cioset_{tag}{num}'))
return ElementTree(
fromstring(
re_sub(
r'<file>\s+<path>([^<]+)</path>\s+</file>',
r'<file><path>\1</path></file>',
special_unprotect(
tostring(original, pretty_print=True,
encoding='utf-8').decode('utf8')))))
# =============================================================================
[docs]
def value2string(elt: Element, value: str | None) -> str:
"""Convert a string value according to type ``type_``.
:type elt: lxml.etree.Element
:param elt:
DOM element owner of the value.
:param str value:
Type of the value.
:rtype: str
"""
type_ = elt.get('type')
value = value.replace('\r\n', '\n') if value is not None else ''
if type_ == 'boolean':
return 'true' \
if value.strip() not in ('', 'false', 'False', '0') else 'false'
if type_ == 'text':
indent = ' ' * 2 * (int(elt.xpath('count(ancestor::*)')) + 1)
lines = value.split('\n')
for num, line in enumerate(lines):
line = normalize_spaces(line.replace(' ', '‧'))
lines[num] = f'{indent}{line}'
return '\n{0}\n{1}'.format(
'\n'.join(lines).rstrip(), indent[2:]) \
if ''.join(lines).strip() else ''
return value.strip()
# =============================================================================
def _update_title(values: dict, name: str, root_elt: Element):
"""Update or remove a title.
:param dict values:
Values of the form.
:param str name:
Name of the value representing the title.
:type root_elt: lxml.etree.Element
:param root_elt:
DOM element owner of the title.
"""
value = normalize_spaces(
values.get('cioset_{0}'.format(name), '').replace(' ', '‧'))
title_elt = root_elt.find(
'{{{0}}}title'.format(RELAXNG_CIOSET['namespace']))
if not value and title_elt is not None:
root_elt.remove(title_elt)
elif value:
if title_elt is None:
title_elt = Element('title')
root_elt.insert(0, title_elt)
title_elt.text = value
# =============================================================================
[docs]
def root_ciopath(ciopath: CioPath, elt: Element) -> CioPath:
"""Return the `CioPath` the root (division… the XML element ``elt``.
:type ciopath: ciowarehouse2.lib.ciopath.CioPath
:param ciopath:
`CioPath` the `Cioset` file.
:type elt: lxml.etree.Element
:param elt:
Cioset XML element for the current file or division.
:rtype: ciowarehouse2.lib.ciopath.CioPath
"""
root = '.'
namespace = RELAXNG_CIOSET['namespace']
root_elt = elt.xpath(
'ancestor-or-self::*[set:root][1]', namespaces={'set': namespace})
if root_elt:
root = root_elt[0].findtext(f'{{{namespace}}}root').strip()
if ':' in root:
root = root[1:] if root[0] == '/' else root # Deprecated
return CioPath.from_str(root, True)
return CioPath(
ciopath.wid, normpath(join(dirname(ciopath.path), root)), True)
# =============================================================================
[docs]
def file_ciopath(ciopath: CioPath, file_elt: Element) -> CioPath:
"""Return the `CioPath` of the file pointed by ``file_elt``, possibly
according to <root> tags.
:type ciopath: ciowarehouse2.lib.ciopath.CioPath
:param ciopath:
`CioPath` of the cioset.
:type file_elt: lxml.etree.Element
:param file_elt:
Cioset XML file element for the current file.
:rtype: ciowarehouse2.lib.ciopath.CioPath
"""
# Get the file path
namespace = RELAXNG_CIOSET['namespace']
path = file_elt.find(f'{{{namespace}}}path')
path = file_elt.text.strip() if path is None else path.text.strip()
if ':' in path:
return CioPath.from_str(path)
return root_ciopath(ciopath, file_elt).join(path)