Source code for masci_tools.util.xml.converters

# -*- 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/.                      #
#                                                                             #
###############################################################################
"""
Common functions for converting types to and from XML files
"""


[docs]def convert_xml_attribute(stringattribute, possible_types, constants=None, logger=None, list_return=False): """ Tries to converts a given string attribute to the types given in possible_types. First succeeded conversion will be returned If no logger is given and a attribute cannot be converted an error is raised :param stringattribute: str, Attribute to convert. :param possible_types: list of str What types it will try to convert to :param constants: dict, of constants defined in fleur input :param logger: logger object for logging warnings if given the errors are logged and the list is returned with the unconverted values otherwise a error is raised, when the first conversion fails :param list_return: if True, the returned quantity is always a list even if only one element is in it :return: The converted value of the first successful conversion """ from masci_tools.util.fleur_calculate_expression import calculate_expression if not isinstance(stringattribute, list): stringattribute = [stringattribute] exceptions = [] converted_list = [] all_success = True for attrib in stringattribute: for value_type in possible_types: if value_type == 'float': try: converted_value = float(attrib) except (ValueError, TypeError) as exc: exceptions.append(exc) continue elif value_type == 'float_expression': if constants is None: raise ValueError( "For calculating attributes of the type 'float_expression' constants have to be given") try: converted_value = calculate_expression(attrib, constants) except ValueError as exc: exceptions.append(exc) continue elif value_type == 'int': try: converted_value = int(attrib) except (ValueError, TypeError) as exc: exceptions.append(exc) continue elif value_type == 'switch': try: converted_value = convert_from_fortran_bool(attrib) except (ValueError, TypeError) as exc: exceptions.append(exc) continue elif value_type == 'string': converted_value = str(attrib) converted_list.append(converted_value) break else: if logger is None: raise ValueError(f"Could not convert '{attrib}'. Tried: {possible_types}.\n" 'The following errors occurred:\n ' + '\n '.join([str(exc) for exc in exceptions])) else: logger.warning("Could not convert '%s'. The following errors occurred:", attrib) for exc in exceptions: logger.warning(' %s', str(exc)) logger.debug(exc, exc_info=exc) converted_list.append(attrib) all_success = False ret_value = converted_list if len(converted_list) == 1 and not list_return: ret_value = converted_list[0] return ret_value, all_success
[docs]def convert_attribute_to_xml(attributevalue, possible_types, logger=None, float_format='.10', list_return=False): """ Tries to converts a given attributevalue to a string for a xml file according to the types given in possible_types. First succeeded conversion will be returned :param attributevalue: value to convert. :param possible_types: list of str What types it will try to convert from :param logger: logger object for logging warnings if given the errors are logged and the list is returned with the unconverted values otherwise a error is raised, when the first conversion fails :param list_return: if True, the returned quantity is always a list even if only one element is in it :return: The converted str of the value of the first succesful conversion """ import numpy as np if not isinstance(attributevalue, (list, np.ndarray)): attributevalue = [attributevalue] possible_types = possible_types.copy() if 'int' in possible_types: #Since it just converts to string possible_types.remove('int') possible_types.append('int') if 'string' in possible_types: #Move string to back possible_types.remove('string') possible_types.append('string') #Always try string converted_list = [] exceptions = [] all_success = True for value in attributevalue: for value_type in possible_types: if value_type in ('float', 'float_expression'): try: converted_value = f'{value:{float_format}f}' except ValueError as exc: exceptions.append(exc) continue elif value_type == 'switch': try: converted_value = convert_to_fortran_bool(value) except (ValueError, TypeError) as exc: exceptions.append(exc) continue elif value_type in ('string', 'int'): converted_value = str(value) converted_list.append(converted_value) break else: if logger is None: raise ValueError(f"Could not convert '{value}' to text. Tried: {possible_types}.\n" 'The following errors occurred:\n ' + '\n '.join([str(exc) for exc in exceptions])) else: logger.warning("Could not convert '%s' to text. The following errors occurred:", value) for exc in exceptions: logger.warning(' %s', str(exc)) logger.debug(exc, exc_info=exc) converted_list.append(value) all_success = False ret_value = converted_list if len(converted_list) == 1 and not list_return: ret_value = converted_list[0] return ret_value, all_success
[docs]def convert_xml_text(tagtext, possible_definitions, constants=None, logger=None, list_return=False): """ Tries to converts a given string text based on the definitions (length and type). First succeeded conversion will be returned :param tagtext: str, text to convert. :param possible_defintions: list of dicts What types it will try to convert to :param constants: dict, of constants defined in fleur input :param logger: logger object for logging warnings if given the errors are logged and the list is returned with the unconverted values otherwise a error is raised, when the first conversion fails :param list_return: if True, the returned quantity is always a list even if only one element is in it :return: The converted value of the first succesful conversion """ if not isinstance(tagtext, list): tagtext = [tagtext] converted_list = [] all_success = True for text in tagtext: base_text = text.strip() split_text = text.split(' ') while '' in split_text: split_text.remove('') text_definition = None for definition in possible_definitions: if definition['length'] == len(split_text): text_definition = definition if text_definition is None: for definition in possible_definitions: if definition['length'] == 'unbounded' or \ definition['length'] == 1: text_definition = definition if text_definition is None: if logger is None: raise ValueError(f"Failed to convert '{text}', no matching definition found") else: logger.warning("Failed to convert '%s', no matching definition found", text) converted_list.append(text) all_success = False continue #Avoid splitting the text accidentally if there is no length restriction if text_definition['length'] == 1: split_text = [base_text] converted_text = [] for value in split_text: converted_value, suc = convert_xml_attribute(value, text_definition['type'], constants=constants, logger=logger) converted_text.append(converted_value) if not suc: all_success = False if len(converted_text) == 1 and text_definition['length'] != 'unbounded': converted_text = converted_text[0] converted_list.append(converted_text) ret_value = converted_list if len(converted_list) == 1 and not list_return: ret_value = converted_list[0] return ret_value, all_success
[docs]def convert_text_to_xml(textvalue, possible_definitions, logger=None, float_format='16.13', list_return=False): """ Tries to convert a given list of values to str for a xml file based on the definitions (length and type). First succeeded conversion will be returned :param textvalue: value to convert :param possible_definitions: list of dicts What types it will try to convert to :param logger: logger object for logging warnings if given the errors are logged and the list is returned with the unconverted values otherwise a error is raised, when the first conversion fails :param list_return: if True, the returned quantity is always a list even if only one element is in it :return: The converted value of the first succesful conversion """ import numpy as np if not isinstance(textvalue, (list, np.ndarray)): textvalue = [textvalue] elif not isinstance(textvalue[0], (list, np.ndarray)): textvalue = [textvalue] converted_list = [] all_success = True for text in textvalue: if not isinstance(text, (list, np.ndarray)): text = [text] text_definition = None for definition in possible_definitions: if definition['length'] == len(text): text_definition = definition if text_definition is None: for definition in possible_definitions: if definition['length'] == 'unbounded': text_definition = definition if text_definition is None: if len(text) == 1 and isinstance(text[0], str): converted_list.append(text[0]) continue if logger is None: raise ValueError(f"Failed to convert '{text}', no matching definition found") else: logger.warning("Failed to convert '%s', no matching definition found", text) converted_list.append('') all_success = False continue converted_text = [] for value in text: converted_value, suc = convert_attribute_to_xml(value, text_definition['type'], logger=logger, float_format=float_format) converted_text.append(converted_value) if not suc: if isinstance(value, str): converted_list.append(value) continue all_success = False converted_list.append(' '.join(converted_text)) ret_value = converted_list if len(converted_list) == 1 and not list_return: ret_value = converted_list[0] return ret_value, all_success
[docs]def convert_from_fortran_bool(stringbool): """ Converts a string in this case ('T', 'F', or 't', 'f') to True or False :param stringbool: a string ('t', 'f', 'F', 'T') :return: boolean (either True or False) """ true_items = ['True', 't', 'T'] false_items = ['False', 'f', 'F'] if isinstance(stringbool, str): if stringbool in false_items: return False elif stringbool in true_items: return True else: raise ValueError(f"Could not convert: '{stringbool}' to boolean, " "which is not 'True', 'False', 't', 'T', 'F' or 'f'") elif isinstance(stringbool, bool): return stringbool # no conversion needed... raise TypeError(f"Could not convert: '{stringbool}' to boolean, " 'only accepts str or boolean')
[docs]def convert_to_fortran_bool(boolean): """ Converts a Boolean as string to the format defined in the input :param boolean: either a boolean or a string ('True', 'False', 'F', 'T') :return: a string (either 't' or 'f') """ if isinstance(boolean, bool): if boolean: return 'T' else: return 'F' elif isinstance(boolean, str): # basestring): if boolean in ('True', 't', 'T'): return 'T' elif boolean in ('False', 'f', 'F'): return 'F' else: raise ValueError(f"A string: {boolean} for a boolean was given, which is not 'True'," "'False', 't', 'T', 'F' or 'f'") raise TypeError('convert_to_fortran_bool accepts only a string or ' f'bool as argument, given {boolean} ')
[docs]def convert_fleur_lo(loelements): """ Converts lo xml elements from the inp.xml file into a lo string for the inpgen """ # Developer hint: Be careful with using '' and "", basestring and str are not the same... # therefore other conversion methods might fail, or the wrong format could be written. from masci_tools.util.xml.common_functions import get_xml_attribute shell_map = {0: 's', 1: 'p', 2: 'd', 3: 'f'} lo_string = '' for element in loelements: lo_type = get_xml_attribute(element, 'type') if lo_type != 'SCLO': # non standard los not supported for now continue l_num = get_xml_attribute(element, 'l') n_num = get_xml_attribute(element, 'n') lostr = f'{n_num}{shell_map[int(l_num)]}' lo_string = lo_string + ' ' + lostr return lo_string.strip()
[docs]def convert_str_version_number(version_str): """ Convert the version number as a integer for easy comparisons :param version_str: str of the version number, e.g. '0.33' :returns: tuple of ints representing the version str """ version_numbers = version_str.split('.') if len(version_numbers) != 2: raise ValueError(f"Version number is malformed: '{version_str}'") return tuple(int(part) for part in version_numbers)