# -*- coding: utf-8 -*-
###############################################################################
# Copyright (c), Forschungszentrum Jülich GmbH, IAS-1/PGI-1, Germany. #
# All rights reserved. #
# This file is part of the Masci-tools package. #
# (Material science tools) #
# #
# The code is hosted on GitHub at https://github.com/judftteam/masci-tools. #
# For further information on the license, see the LICENSE.txt file. #
# For further information please visit http://judft.de/. #
# #
###############################################################################
"""
This module contains helper functions for extracting information easily from the
schema_dicts defined for the Fleur input/output
Also provides convienient functions to use just a attribute name for extracting the
attribute from the right place in the given etree
"""
from masci_tools.util.parse_tasks_decorators import register_parsing_function
from masci_tools.util.lockable_containers import LockableList
from lxml import etree
def _find_paths(schema_dict, name, entries, contains=None, not_contains=None):
"""
Find all paths in the schema_dict in the given entries for the given name
and matching the contains/not_contains criteria
:param schema_dict: dict, containing all the path information and more
:param name: str, name of the tag
:param contains: str or list of str, this string has to be in the final path
:param not_contains: str or list of str, this string has to NOT be in the final path
:returns: list of str, found xpaths matching the criteria
"""
if contains is None:
contains = []
elif not isinstance(contains, (list, set)):
contains = [contains]
if not_contains is None:
not_contains = []
elif not isinstance(not_contains, (list, set)):
not_contains = [not_contains]
path_list = []
for entry in entries:
if name in schema_dict[entry]:
entry_paths = schema_dict[entry][name]
if not isinstance(entry_paths, LockableList):
entry_paths = [entry_paths]
else:
entry_paths = entry_paths.get_unlocked()
invalid_paths = set()
for phrase in contains:
for xpath in entry_paths:
if phrase not in xpath:
invalid_paths.add(xpath)
for phrase in not_contains:
for xpath in entry_paths:
if phrase in xpath:
invalid_paths.add(xpath)
for invalid in invalid_paths:
entry_paths.remove(invalid)
path_list += entry_paths
return path_list
[docs]def get_tag_xpath(schema_dict, name, contains=None, not_contains=None):
"""
Tries to find a unique path from the schema_dict based on the given name of the tag
and additional further specifications
:param schema_dict: dict, containing all the path information and more
:param name: str, name of the tag
:param contains: str or list of str, this string has to be in the final path
:param not_contains: str or list of str, this string has to NOT be in the final path
:returns: str, xpath for the given tag
:raises ValueError: If no unique path could be found
"""
possible_lists = ['tag_paths']
if 'iteration_tag_paths' in schema_dict:
possible_lists += ['iteration_tag_paths']
paths = _find_paths(schema_dict, name, possible_lists, contains=contains, not_contains=not_contains)
if len(paths) == 1:
return paths[0]
elif len(paths) == 0:
raise ValueError(f'The tag {name} has no possible paths with the current specification.\n'
f'contains: {contains}, not_contains: {not_contains}')
else:
raise ValueError(f'The tag {name} has multiple possible paths with the current specification.\n'
f'contains: {contains}, not_contains: {not_contains} \n'
f'These are possible: {paths}')
[docs]def get_relative_tag_xpath(schema_dict, name, root_tag, contains=None, not_contains=None):
"""
Tries to find a unique relative path from the schema_dict based on the given name of the tag
name of the root, from which the path should be relative and additional further specifications
:param schema_dict: dict, containing all the path information and more
:param name: str, name of the tag
:param root_tag: str, name of the tag from which the path should be relative
:param contains: str or list of str, this string has to be in the final path
:param not_contains: str or list of str, this string has to NOT be in the final path
:returns: str, xpath for the given tag
:raises ValueError: If no unique path could be found
"""
from masci_tools.util.xml.common_functions import abs_to_rel_xpath
possible_lists = ['tag_paths']
if 'iteration_tag_paths' in schema_dict:
possible_lists += ['iteration_tag_paths']
#The paths have to include the root_tag
if contains is None:
contains = [root_tag]
else:
contains = set(contains)
contains.add(root_tag)
paths = _find_paths(schema_dict, name, possible_lists, contains=contains, not_contains=not_contains)
rel_paths = set()
for xpath in paths:
rel_paths.add(abs_to_rel_xpath(xpath, root_tag))
if len(rel_paths) == 1:
return rel_paths.pop()
elif len(rel_paths) == 0:
raise ValueError(f'The tag {name} has no possible relative paths with the current specification.\n'
f'contains: {contains}, not_contains: {not_contains}, root_tag {root_tag}')
else:
raise ValueError(f'The tag {name} has multiple possible relative paths with the current specification.\n'
f'contains: {contains}, not_contains: {not_contains}, root_tag {root_tag} \n'
f'These are possible: {rel_paths}')
[docs]def get_attrib_xpath(schema_dict, name, contains=None, not_contains=None, exclude=None, tag_name=None):
"""
Tries to find a unique path from the schema_dict based on the given name of the attribute
and additional further specifications
:param schema_dict: dict, containing all the path information and more
:param name: str, name of the attribute
:param root_tag: str, name of the tag from which the path should be relative
:param contains: str or list of str, this string has to be in the final path
:param not_contains: str or list of str, this string has to NOT be in the final path
:param exclude: list of str, here specific types of attributes can be excluded
valid values are: settable, settable_contains, other
:param tag_name: str, if given this name will be used to find a path to a tag with the
same name in :py:func:`get_tag_xpath()`
:returns: str, xpath to the tag with the given attribute
:raises ValueError: If no unique path could be found
"""
if tag_name is not None:
tag_xpath = get_tag_xpath(schema_dict, tag_name, contains=contains, not_contains=not_contains)
err_msg = f'No attribute {name} found at tag {tag_name}'
if tag_xpath in schema_dict['tag_info']:
if name not in schema_dict['tag_info'][tag_xpath]['attribs']:
raise ValueError(err_msg)
else:
if name not in schema_dict['iteration_tag_info'][tag_xpath]['attribs']:
raise ValueError(err_msg)
return f'{tag_xpath}/@{name}'
possible_lists = ['unique_attribs', 'unique_path_attribs', 'other_attribs']
output = False
if 'iteration_unique_attribs' in schema_dict:
#outputschema
output = True
possible_lists += ['iteration_unique_attribs', 'iteration_unique_path_attribs', 'iteration_other_attribs']
if exclude is not None:
for list_name in exclude:
possible_lists.remove(f'{list_name}_attribs')
if output:
possible_lists.remove(f'iteration_{list_name}_attribs')
paths = _find_paths(schema_dict, name, possible_lists, contains=contains, not_contains=not_contains)
if len(paths) == 1:
return paths[0]
elif len(paths) == 0:
raise ValueError(f'The attrib {name} has no possible paths with the current specification.\n'
f'contains: {contains}, not_contains: {not_contains}, exclude {exclude}')
else:
raise ValueError(f'The attrib {name} has multiple possible paths with the current specification.\n'
f'contains: {contains}, not_contains: {not_contains}, exclude {exclude}\n'
f'These are possible: {paths}')
[docs]def get_relative_attrib_xpath(schema_dict,
name,
root_tag,
contains=None,
not_contains=None,
exclude=None,
tag_name=None):
"""
Tries to find a unique relative path from the schema_dict based on the given name of the attribute
name of the root, from which the path should be relative and additional further specifications
:param schema_dict: dict, containing all the path information and more
:param name: str, name of the attribute
:param contains: str or list of str, this string has to be in the final path
:param not_contains: str or list of str, this string has to NOT be in the final path
:param exclude: list of str, here specific types of attributes can be excluded
valid values are: settable, settable_contains, other
:param tag_name: str, if given this name will be used to find a path to a tag with the
same name in :py:func:`get_relative_tag_xpath()`
:returns: str, xpath for the given tag
:raises ValueError: If no unique path could be found
"""
from masci_tools.util.xml.common_functions import abs_to_rel_xpath
if tag_name is not None:
tag_xpath = get_relative_tag_xpath(schema_dict,
tag_name,
root_tag,
contains=contains,
not_contains=not_contains)
tag_info = get_tag_info(schema_dict,
tag_name,
path_return=False,
multiple_paths=True,
contains=contains,
not_contains=not_contains)
err_msg = f'No attribute {name} found at tag {tag_name}'
if name not in tag_info['attribs']:
raise ValueError(err_msg)
if tag_xpath.endswith('/'):
return f'{tag_xpath}@{name}'
else:
return f'{tag_xpath}/@{name}'
possible_lists = ['unique_attribs', 'unique_path_attribs', 'other_attribs']
output = False
if 'iteration_unique_attribs' in schema_dict:
#outputschema
output = True
possible_lists += ['iteration_unique_attribs', 'iteration_unique_path_attribs', 'iteration_other_attribs']
if exclude is not None:
for list_name in exclude:
possible_lists.remove(f'{list_name}_attribs')
if output:
possible_lists.remove(f'iteration_{list_name}_attribs')
#The paths have to include the root_tag
if contains is None:
contains = [root_tag]
else:
contains = set(contains)
contains.add(root_tag)
paths = _find_paths(schema_dict, name, possible_lists, contains=contains, not_contains=not_contains)
rel_paths = set()
for xpath in paths:
rel_paths.add(abs_to_rel_xpath(xpath, root_tag))
if len(rel_paths) == 1:
return rel_paths.pop()
elif len(rel_paths) == 0:
raise ValueError(f'The attrib {name} has no possible relative paths with the current specification.\n'
f'contains: {contains}, not_contains: {not_contains}, root_tag {root_tag}')
else:
raise ValueError(f'The attrib {name} has multiple possible relative paths with the current specification.\n'
f'contains: {contains}, not_contains: {not_contains}, root_tag {root_tag} \n'
f'These are possible: {rel_paths}')
[docs]def get_tag_info(schema_dict,
name,
contains=None,
not_contains=None,
path_return=True,
convert_to_builtin=False,
multiple_paths=False,
parent=False):
"""
Tries to find a unique path from the schema_dict based on the given name of the tag
and additional further specifications and returns the tag_info entry for this tag
:param schema_dict: dict, containing all the path information and more
:param name: str, name of the tag
:param contains: str or list of str, this string has to be in the final path
:param not_contains: str or list of str, this string has to NOT be in the final path
:param path_return: bool, if True the found path will be returned alongside the tag_info
:param convert_to_builtin: bool, if True the CaseInsensitiveFrozenSets are converetd to normal sets
with the rigth case of the attributes
:param multiple_paths: bool, if True mulitple paths are allowed to match as long as they have the same tag_info
:param parent: bool, if True the tag_info for the parent of the tag is returned
:returns: dict, tag_info for the found xpath
:returns: str, xpath to the tag if `path_return=True`
"""
import copy
from masci_tools.util.case_insensitive_dict import CaseInsensitiveFrozenSet
from masci_tools.util.xml.common_functions import split_off_tag
if multiple_paths:
possible_lists = ['tag_paths']
if 'iteration_tag_paths' in schema_dict:
possible_lists += ['iteration_tag_paths']
paths = _find_paths(schema_dict, name, possible_lists, contains=contains, not_contains=not_contains)
else:
paths = [get_tag_xpath(schema_dict, name, contains=contains, not_contains=not_contains)]
tag_info = None
for path in paths:
if parent:
path, _ = split_off_tag(path)
err_msg = f'Could not fing tag_info for {path}'
if path in schema_dict['tag_info']:
entry = schema_dict['tag_info'][path]
elif 'iteration_tag_info' in schema_dict:
if path in schema_dict['iteration_tag_info']:
entry = schema_dict['iteration_tag_info'][path]
else:
raise ValueError(err_msg)
else:
raise ValueError(err_msg)
if tag_info is not None:
if entry != tag_info:
raise ValueError(f'Differing tag_info for the found paths {paths}')
else:
tag_info = entry
if not multiple_paths:
paths = paths[0]
if convert_to_builtin:
tag_info = {
key: set(val.original_case.values()) if isinstance(val, CaseInsensitiveFrozenSet) else val
for key, val in tag_info.items()
}
if path_return:
return tag_info, paths
else:
return tag_info
[docs]def read_constants(root, schema_dict, logger=None):
"""
Reads in the constants defined in the inp.xml
and returns them combined with the predefined constants from
fleur as a dictionary
:param root: root of the etree of the inp.xml file
:param schema_dict: schema_dictionary of the version of the file to read (inp.xml or out.xml)
:param logger: logger object for logging warnings, errors
:return: a python dictionary with all defined constants
"""
from masci_tools.util.constants import FLEUR_DEFINED_CONSTANTS
import copy
import warnings
defined_constants = copy.deepcopy(FLEUR_DEFINED_CONSTANTS)
try:
tag_exists(root, schema_dict, 'constant')
except ValueError as err:
if 'no possible' in str(err):
warnings.warn('Cannot extract custom constants for the given root. Assuming defaults')
return defined_constants
else:
raise
if not tag_exists(root, schema_dict, 'constant', logger=logger): #Avoid warnings for empty constants
return defined_constants
constants = evaluate_tag(root, schema_dict, 'constant', defined_constants, logger=logger)
if constants['name'] is not None:
if not isinstance(constants['name'], list):
constants = {key: [val] for key, val in constants.items()}
for name, value in zip(constants['name'], constants['value']):
if name not in defined_constants:
defined_constants[name] = value
else:
if logger is not None:
logger.error('Ambiguous definition of constant %s', name)
raise KeyError(f'Ambiguous definition of constant {name}')
return defined_constants
[docs]@register_parsing_function('attrib')
def evaluate_attribute(node, schema_dict, name, constants=None, logger=None, **kwargs):
"""
Evaluates the value of the attribute based on the given name
and additional further specifications with the available type information
:param node: etree Element, on which to execute the xpath evaluations
:param schema_dict: dict, containing all the path information and more
:param name: str, name of the attribute
:param constants: dict, contains the defined constants
:param logger: logger object for logging warnings, errors, if not provided all errors will be raised
Kwargs:
:param tag_name: str, name of the tag where the attribute should be parsed
:param contains: str, this string has to be in the final path
:param not_contains: str, this string has to NOT be in the final path
:param exclude: list of str, here specific types of attributes can be excluded
valid values are: settable, settable_contains, other
:param list_return: if True, the returned quantity is always a list even if only one element is in it
:param optional: bool, if True and no logger given none or an empty list is returned
:returns: list or single value, converted in convert_xml_attribute
"""
from masci_tools.util.xml.common_functions import eval_xpath
from masci_tools.util.xml.converters import convert_xml_attribute
list_return = kwargs.pop('list_return', False)
optional = kwargs.pop('optional', False)
attrib_xpath = None
if isinstance(node, etree._Element):
if node.tag != schema_dict['root_tag'] and node.tag != 'iteration':
attrib_xpath = get_relative_attrib_xpath(schema_dict, name, node.tag, **kwargs)
if attrib_xpath is None:
attrib_xpath = get_attrib_xpath(schema_dict, name, **kwargs)
stringattribute = eval_xpath(node, attrib_xpath, logger=logger, list_return=True)
if len(stringattribute) == 0:
if logger is None:
if not optional:
raise ValueError(f'No values found for attribute {name}')
else:
logger.warning('No values found for attribute %s', name)
if list_return:
return []
else:
return None
possible_types = schema_dict['attrib_types'][name]
converted_value, suc = convert_xml_attribute(stringattribute,
possible_types,
constants=constants,
logger=logger,
list_return=list_return)
if not suc:
if logger is None:
raise ValueError(f'Failed to evaluate attribute {name}, Got value: {stringattribute}')
else:
logger.warning('Failed to evaluate attribute %s, Got value: %s', name, stringattribute)
return converted_value
[docs]@register_parsing_function('text')
def evaluate_text(node, schema_dict, name, constants, logger=None, **kwargs):
"""
Evaluates the text of the tag based on the given name
and additional further specifications with the available type information
:param node: etree Element, on which to execute the xpath evaluations
:param schema_dict: dict, containing all the path information and more
:param name: str, name of the tag
:param constants: dict, contains the defined constants
:param logger: logger object for logging warnings, errors, if not provided all errors will be raised
Kwargs:
:param contains: str, this string has to be in the final path
:param not_contains: str, this string has to NOT be in the final path
:param list_return: if True, the returned quantity is always a list even if only one element is in it
:param optional: bool, if True and no logger given none or an empty list is returned
:returns: list or single value, converted in convert_xml_text
"""
from masci_tools.util.xml.common_functions import eval_xpath
from masci_tools.util.xml.converters import convert_xml_text
list_return = kwargs.pop('list_return', False)
optional = kwargs.pop('optional', False)
tag_xpath = None
if isinstance(node, etree._Element):
if node.tag != schema_dict['root_tag'] and node.tag != 'iteration':
tag_xpath = get_relative_tag_xpath(schema_dict, name, node.tag, **kwargs)
if tag_xpath is None:
tag_xpath = get_tag_xpath(schema_dict, name, **kwargs)
stringtext = eval_xpath(node, f'{tag_xpath}/text()', logger=logger, list_return=True)
for text in stringtext.copy():
if text.strip() == '':
stringtext.remove(text)
if len(stringtext) == 0:
if logger is None:
if not optional:
raise ValueError(f'No text found for tag {name}')
else:
logger.warning('No text found for tag %s', name)
if list_return:
return []
else:
return None
possible_definitions = schema_dict['simple_elements'][name]
converted_value, suc = convert_xml_text(stringtext,
possible_definitions,
constants=constants,
logger=logger,
list_return=list_return)
if not suc:
if logger is None:
raise ValueError(f'Failed to evaluate text for tag {name}, Got text: {stringtext}')
else:
logger.warning('Failed to evaluate text for tag %s, Got text: %s', name, stringtext)
return converted_value
[docs]@register_parsing_function('allAttribs', all_attribs_keys=True)
def evaluate_tag(node, schema_dict, name, constants=None, logger=None, **kwargs):
"""
Evaluates all attributes of the tag based on the given name
and additional further specifications with the available type information
:param node: etree Element, on which to execute the xpath evaluations
:param schema_dict: dict, containing all the path information and more
:param name: str, name of the tag
:param constants: dict, contains the defined constants
:param logger: logger object for logging warnings, errors, if not provided all errors will be raised
Kwargs:
:param contains: str, this string has to be in the final path
:param not_contains: str, this string has to NOT be in the final path
:param only_required: bool (optional, default False), if True only required attributes are parsed
:param ignore: list of str (optional), attributes not to parse
:param list_return: if True, the returned quantity is always a list even if only one element is in it
:param strict_missing_error: if True, and no logger is given an error is raised if any attribute is not found
:returns: dict, with attribute values converted via convert_xml_attribute
"""
from masci_tools.util.xml.common_functions import eval_xpath
from masci_tools.util.xml.converters import convert_xml_attribute
only_required = kwargs.pop('only_required', False)
strict_missing_error = kwargs.pop('strict_missing_error', False)
ignore = kwargs.pop('ignore', None)
list_return = kwargs.pop('list_return', False)
tag_xpath = None
if isinstance(node, etree._Element):
if node.tag != schema_dict['root_tag'] and node.tag != 'iteration':
kwargs['contains'] = set(kwargs.get('contains', []))
kwargs['contains'].add(node.tag)
tag_xpath = get_relative_tag_xpath(schema_dict, name, node.tag, **kwargs)
if tag_xpath is None:
tag_xpath = get_tag_xpath(schema_dict, name, **kwargs)
#Which attributes are expected
try:
tag_info = get_tag_info(schema_dict, name, path_return=False, multiple_paths=True, **kwargs)
attribs = tag_info['attribs']
optional = tag_info['optional_attribs']
except ValueError as err:
if logger is None:
raise ValueError(f'Failed to evaluate attributes from tag {name}: '
'No attributes to parse either the tag does not '
'exist or it has no attributes') from err
else:
logger.exception(
'Failed to evaluate attributes from tag %s: '
'No attributes to parse either the tag does not '
'exist or it has no attributes', name)
attribs = set()
optional = set()
if only_required:
attribs = attribs.difference(optional)
if ignore:
attribs = attribs.difference(ignore)
if not attribs:
if logger is None:
raise ValueError(f'Failed to evaluate attributes from tag {name}: '
'No attributes to parse either the tag does not '
'exist or it has no attributes')
else:
logger.error(
'Failed to evaluate attributes from tag %s: '
'No attributes to parse either the tag does not '
'exist or it has no attributes', name)
else:
attribs = sorted(list(attribs.original_case.values()))
out_dict = {}
for attrib in attribs:
stringattribute = eval_xpath(node, f'{tag_xpath}/@{attrib}', logger=logger, list_return=True)
if len(stringattribute) == 0:
if logger is None:
if strict_missing_error and attrib not in optional:
raise ValueError(f'No values found for attribute {attrib} at tag {name}')
else:
logger.warning('No values found for attribute %s at tag %s', attrib, name)
if list_return:
out_dict[attrib] = []
else:
out_dict[attrib] = None
continue
possible_types = schema_dict['attrib_types'][attrib]
out_dict[attrib], suc = convert_xml_attribute(stringattribute,
possible_types,
constants=constants,
logger=logger,
list_return=list_return)
if not suc:
if logger is None:
raise ValueError(f'Failed to evaluate attribute {attrib}, Got value: {stringattribute}')
else:
logger.warning('Failed to evaluate attribute %s, Got value: %s', attrib, stringattribute)
return out_dict
[docs]@register_parsing_function('singleValue', all_attribs_keys=True)
def evaluate_single_value_tag(node, schema_dict, name, constants=None, logger=None, **kwargs):
"""
Evaluates the value and unit attribute of the tag based on the given name
and additional further specifications with the available type information
:param node: etree Element, on which to execute the xpath evaluations
:param schema_dict: dict, containing all the path information and more
:param name: str, name of the tag
:param constants: dict, contains the defined constants
:param logger: logger object for logging warnings, errors, if not provided all errors will be raised
Kwargs:
:param contains: str, this string has to be in the final path
:param not_contains: str, this string has to NOT be in the final path
:param only_required: bool (optional, default False), if True only required attributes are parsed
:param ignore: list of str (optional), attributes not to parse
:param list_return: if True, the returned quantity is always a list even if only one element is in it
:param strict_missing_error: if True, and no logger is given an error is raised if any attribute is not found
:returns: value and unit, both converted in convert_xml_attribute
"""
only_required = kwargs.get('only_required', False)
ignore = kwargs.get('ignore', [])
value_dict = evaluate_tag(node, schema_dict, name, constants=constants, logger=logger, **kwargs)
if value_dict.get('value') is None:
if logger is None:
raise ValueError(f'Failed to evaluate singleValue from tag {name}: ' "Has no 'value' attribute")
else:
logger.warning('Failed to evaluate singleValue from tag %s: ' "Has no 'value' attribute", name)
if value_dict.get('units') is None and not only_required and 'units' not in ignore:
if logger is None:
raise ValueError(f'Failed to evaluate singleValue from tag {name}: ' "Has no 'units' attribute")
else:
logger.warning('Failed to evaluate singleValue from tag %s: ' "Has no 'units' attribute", name)
return value_dict
[docs]@register_parsing_function('parentAttribs', all_attribs_keys=True)
def evaluate_parent_tag(node, schema_dict, name, constants=None, logger=None, **kwargs):
"""
Evaluates all attributes of the parent tag based on the given name
and additional further specifications with the available type information
:param node: etree Element, on which to execute the xpath evaluations
:param schema_dict: dict, containing all the path information and more
:param name: str, name of the tag
:param constants: dict, contains the defined constants
:param logger: logger object for logging warnings, errors, if not provided all errors will be raised
Kwargs:
:param contains: str, this string has to be in the final path
:param not_contains: str, this string has to NOT be in the final path
:param only_required: bool (optional, default False), if True only required attributes are parsed
:param ignore: list of str (optional), attributes not to parse
:param list_return: if True, the returned quantity is always a list even if only one element is in it
:param strict_missing_error: if True, and no logger is given an error is raised if any attribute is not found
:returns: dict, with attribute values converted via convert_xml_attribute
"""
from masci_tools.util.xml.common_functions import eval_xpath, get_xml_attribute
from masci_tools.util.xml.converters import convert_xml_attribute
strict_missing_error = kwargs.pop('strict_missing_error', False)
list_return = kwargs.pop('list_return', False)
only_required = kwargs.pop('only_required', False)
ignore = kwargs.pop('ignore', None)
tag_xpath = None
if isinstance(node, etree._Element):
if node.tag != schema_dict['root_tag'] and node.tag != 'iteration':
kwargs['contains'] = set(kwargs.get('contains', []))
kwargs['contains'].add(node.tag)
tag_xpath = get_relative_tag_xpath(schema_dict, name, node.tag, **kwargs)
if tag_xpath is None:
tag_xpath = get_tag_xpath(schema_dict, name, **kwargs)
#Which attributes are expected
try:
tag_info = get_tag_info(schema_dict, name, path_return=False, multiple_paths=True, parent=True, **kwargs)
attribs = tag_info['attribs']
optional = tag_info['optional_attribs']
except ValueError as err:
if logger is None:
raise ValueError(f'Failed to evaluate attributes from parent tag of {name}: '
'No attributes to parse either the tag does not '
'exist or it has no attributes') from err
else:
logger.exception(
'Failed to evaluate attributes from parent tag of %s: '
'No attributes to parse either the tag does not '
'exist or it has no attributes', name)
attribs = set()
optional = set()
if only_required:
attribs = attribs.difference(optional)
if ignore is not None:
attribs = attribs.difference(ignore)
if not attribs:
if logger is None:
raise ValueError(f'Failed to evaluate attributes from parent tag of {name}: '
'No attributes to parse either the tag does not '
'exist or it has no attributes')
else:
logger.error(
'Failed to evaluate attributes from parent tag of %s: '
'No attributes to parse either the tag does not '
'exist or it has no attributes', name)
else:
attribs = sorted(list(attribs.original_case.values()))
elems = eval_xpath(node, tag_xpath, logger=logger, list_return=True)
out_dict = {}
for attrib in attribs:
out_dict[attrib] = []
for elem in elems:
parent = elem.getparent()
for attrib in attribs:
stringattribute = get_xml_attribute(parent, attrib, logger=logger)
if stringattribute is None:
if logger is None:
if strict_missing_error and attrib not in optional:
raise ValueError(f'No values found for attribute {attrib} for parent tag of {name}')
else:
logger.warning('No values found for attribute %s for parent tag of %s', attrib, name)
out_dict[attrib].append(None)
continue
possible_types = schema_dict['attrib_types'][attrib]
value, suc = convert_xml_attribute(stringattribute, possible_types, constants=constants, logger=logger)
out_dict[attrib].append(value)
if not suc:
if logger is None:
raise ValueError(f'Failed to evaluate attribute {attrib}, Got value: {stringattribute}')
else:
logger.warning('Failed to evaluate attribute %s, Got value: %s', attrib, stringattribute)
if all(len(x) == 1 for x in out_dict.values()) and not list_return:
out_dict = {key: val[0] for key, val in out_dict.items()}
return out_dict
[docs]@register_parsing_function('attrib_exists')
def attrib_exists(node, schema_dict, name, logger=None, **kwargs):
"""
Evaluates whether the attribute exists in the xmltree based on the given name
and additional further specifications with the available type information
:param node: etree Element, on which to execute the xpath evaluations
:param schema_dict: dict, containing all the path information and more
:param name: str, name of the tag
:param logger: logger object for logging warnings, errors, if not provided all errors will be raised
Kwargs:
:param tag_name: str, name of the tag where the attribute should be parsed
:param contains: str, this string has to be in the final path
:param not_contains: str, this string has to NOT be in the final path
:param exclude: list of str, here specific types of attributes can be excluded
valid values are: settable, settable_contains, other
:returns: bool, True if any tag with the attribute exists
"""
from masci_tools.util.xml.common_functions import eval_xpath, split_off_attrib
attrib_xpath = None
if isinstance(node, etree._Element):
if node.tag != schema_dict['root_tag'] and node.tag != 'iteration':
attrib_xpath = get_relative_attrib_xpath(schema_dict, name, node.tag, **kwargs)
if attrib_xpath is None:
attrib_xpath = get_attrib_xpath(schema_dict, name, **kwargs)
tag_xpath, attrib_name = split_off_attrib(attrib_xpath)
tags = eval_xpath(node, tag_xpath, logger=logger, list_return=True)
return any(attrib_name in tag.attrib for tag in tags)
[docs]@register_parsing_function('exists')
def tag_exists(node, schema_dict, name, logger=None, **kwargs):
"""
Evaluates whether the tag exists in the xmltree based on the given name
and additional further specifications with the available type information
:param node: etree Element, on which to execute the xpath evaluations
:param schema_dict: dict, containing all the path information and more
:param name: str, name of the tag
:param logger: logger object for logging warnings, errors, if not provided all errors will be raised
Kwargs:
:param contains: str, this string has to be in the final path
:param not_contains: str, this string has to NOT be in the final path
:returns: bool, True if any nodes with the path exist
"""
return get_number_of_nodes(node, schema_dict, name, logger=logger, **kwargs) != 0
[docs]@register_parsing_function('numberNodes')
def get_number_of_nodes(node, schema_dict, name, logger=None, **kwargs):
"""
Evaluates the number of occurences of the tag in the xmltree based on the given name
and additional further specifications with the available type information
:param node: etree Element, on which to execute the xpath evaluations
:param schema_dict: dict, containing all the path information and more
:param name: str, name of the tag
:param logger: logger object for logging warnings, errors, if not provided all errors will be raised
Kwargs:
:param contains: str, this string has to be in the final path
:param not_contains: str, this string has to NOT be in the final path
:returns: bool, True if any nodes with the path exist
"""
return len(eval_simple_xpath(node, schema_dict, name, logger=logger, list_return=True, **kwargs))
[docs]def eval_simple_xpath(node, schema_dict, name, logger=None, **kwargs):
"""
Evaluates a simple xpath expression of the tag in the xmltree based on the given name
and additional further specifications with the available type information
:param node: etree Element, on which to execute the xpath evaluations
:param schema_dict: dict, containing all the path information and more
:param name: str, name of the tag
:param logger: logger object for logging warnings, errors, if not provided all errors will be raised
Kwargs:
:param contains: str, this string has to be in the final path
:param not_contains: str, this string has to NOT be in the final path
:param list_return: bool, if True a list is always returned
:returns: etree Elements obtained via the simple xpath expression
"""
from masci_tools.util.xml.common_functions import eval_xpath
list_return = kwargs.pop('list_return', False)
tag_xpath = None
if isinstance(node, etree._Element):
if node.tag != schema_dict['root_tag'] and node.tag != 'iteration':
tag_xpath = get_relative_tag_xpath(schema_dict, name, node.tag, **kwargs)
if tag_xpath is None:
tag_xpath = get_tag_xpath(schema_dict, name, **kwargs)
return eval_xpath(node, tag_xpath, logger=logger, list_return=list_return)