# Copyright (C) 2019 Intel Corporation. # SPDX-License-Identifier: BSD-3-Clause """Controller for config app. """ import os from xmlschema import XMLSchema11 from xmlschema.validators import Xsd11Element, XsdSimpleType, XsdAtomicBuiltin, Xsd11ComplexType, Xsd11Group, Xsd11Attribute import lxml.etree as etree class XmlConfig: """The core class to analyze and modify acrn config xml files""" def __init__(self, path=None, default=True): self._xml_path = path self._default = default self._curr_xml = None self._curr_xml_tree = None @staticmethod def _get_xml_type(xml_file): """ get the config type by file. :param xml_file: the file path of xml file. :return: the xml type. :raises: ValueError, OSError, SyntaxError. """ xml_type = '' if os.path.splitext(xml_file)[1] != '.xml': return xml_type try: tree = etree.parse(xml_file) root = tree.getroot() if 'user_vm_launcher' in root.attrib: xml_type = 'user_vm_launcher' elif 'scenario' in root.attrib: xml_type = 'scenario' elif 'board' in root.attrib: xml_type = 'board' elif 'board_setting' in root.attrib: xml_type = 'board_setting' except ValueError: print('xml parse error: {}'.format(xml_file)) xml_type = '' except OSError: print('xml open error: {}'.format(xml_file)) xml_type = '' except SyntaxError: print('xml syntax error: {}'.format(xml_file)) xml_type = '' return xml_type def list_all(self, xml_type=None): """ list all xml config files by type. :param xml_type: the xml type. :return: he list of xml config files. """ xmls = [] user_xmls = [] if self._xml_path is None or not os.path.exists(self._xml_path): return xmls, user_xmls for test_file in os.listdir(self._xml_path): test_file_path = os.path.join(self._xml_path, test_file) if os.path.isfile(test_file_path): if XmlConfig._get_xml_type(test_file_path) == xml_type: xmls.append(os.path.splitext(test_file)[0]) user_path = os.path.join(self._xml_path, 'user_defined') if os.path.isdir(user_path): for test_file in os.listdir(user_path): test_file_path = os.path.join(user_path, test_file) if os.path.isfile(test_file_path): if XmlConfig._get_xml_type(test_file_path) == xml_type: user_xmls.append(os.path.splitext(test_file)[0]) return xmls, user_xmls def set_curr(self, xml): """ set current xml file to analyze. :param xml: the xml file. :return: None. :raises: ValueError, OSError, SyntaxError. """ if self._xml_path is None or xml is None: return try: self._curr_xml = xml xml_path = os.path.join(self._xml_path, self._curr_xml + '.xml') \ if self._default \ else os.path.join(self._xml_path, 'user_defined', self._curr_xml + '.xml') parser = etree.XMLParser(remove_blank_text=True) tree = etree.parse(xml_path, parser) self._curr_xml_tree = tree except ValueError: print('xml parse error: {}'.format(xml)) self._curr_xml = None self._curr_xml_tree = None except OSError: print('xml open error: {}'.format(xml)) self._curr_xml = None self._curr_xml_tree = None except SyntaxError: print('xml syntax error: {}'.format(xml)) self._curr_xml = None self._curr_xml_tree = None def get_curr(self): """ get current xml config file. :return: current xml config file name. """ return self._curr_xml def get_curr_root(self): """ get the xml root of current xml config file. :return: the xml root of current xml config file. """ if self._curr_xml_tree is None: return None return self._curr_xml_tree.getroot() def get_curr_value(self, *args): """ get the value of the element by its path. :param args: the path of the element. :return: the value of the element. """ if self._curr_xml_tree is None: return None dest_node = self._get_dest_node(*args) if dest_node is None: return None if dest_node.text is None or dest_node.text.strip() == '': return '' return dest_node.text def set_curr_value(self, value, *args): """ set the value of the element by its path. :param value: the value of the element. :param args: the path of the element. :return: None. """ if self._curr_xml_tree is None: return dest_node = self._get_dest_node(*args) dest_node.text = value def set_curr_list(self, values, *args): """ set a list of sub element for the element by its path. :param values: the list of values of the element. :param args: the path of the element. :return: None. """ if self._curr_xml_tree is None: return tag = args[-1] args = args[:-1] dest_node = self._get_dest_node(*args) new_node_desc = None for node in list(dest_node): if node.tag == tag: if 'desc' in node.attrib: new_node_desc = node.attrib['desc'] dest_node.remove(node) for value in values: new_node = etree.SubElement(dest_node, tag) new_node.text = value if new_node_desc is not None: new_node.attrib['desc'] = new_node_desc def set_curr_attr(self, attr_name, attr_value, *args): """ set the attribute of the element by its path. :param attr_name: the attribute name of the element. :param attr_value: the attribute value of the element. :param args: the path of the element. :return: None. """ if self._curr_xml_tree is None: return dest_node = self._get_dest_node(*args) dest_node.attrib[attr_name] = attr_value def add_curr_value(self, key, desc, value, *args): """ add a sub element for the element by its path. :param key: the tag of the sub element. :param desc: the attribute desc of the sub element. :param value: the value of the sub element. :param args: the path of the element. :return: None. """ if self._curr_xml_tree is None: return dest_node = self._get_dest_node(*args) if key in ['vm']: etree.SubElement(dest_node, key, attrib={'id': value, 'desc': desc}) else: new_node = etree.SubElement(dest_node, key, attrib={'desc': desc}) new_node.text = value def get_curr_elem(self, *args): """ get elements for current path. :param args: the path of the element. :return: current element. """ if self._curr_xml_tree is None: return dest_node = self._get_dest_node(*args) return dest_node def clone_curr_elem(self, elem, *args): """ clone elements for current path. :param elem: the element to clone. :param args: the path of the element. :return: None. """ if self._curr_xml_tree is None: return dest_node = self._get_dest_node(*args) dest_node.append(elem) def insert_curr_elem(self, index, elem, *args): """ insert elements for current path. :param index: the location for the element to insert. :param elem: the element to insert. :param args: the path of the element. :return: None. """ if self._curr_xml_tree is None: return dest_node = self._get_dest_node(*args) dest_node.insert(index, elem) def delete_curr_elem(self, *args): """ delete the element by its path. :param args: the path of the element. :return: None. """ if self._curr_xml_tree is None: return father_node = self._get_dest_node(*args[:-1]) dest_node = self._get_dest_node(*args) father_node.remove(dest_node) def delete_curr_key(self, *args): """ delete the element by its path. :param args: the path of the element. :return: None. """ if self._curr_xml_tree is None: return dest_node = self._get_dest_node(*args) self._curr_xml_tree.getroot().remove(dest_node) def _get_dest_node(self, *args): """ get the destination element by its path. :param args: the path of the element. :return: the destination element. """ if self._curr_xml_tree is None: return None dest_node = self._curr_xml_tree.getroot() path = '.' for arg in args: # tag:attr=xxx # tag:attr # tag tag = None attr_name = None attr_value = None if ':' not in arg: tag = arg elif '=' not in arg: # tag = arg.split(':')[0] # attr_name = arg.split(':')[1] raise Exception('unsupported xml path: tag:attr') else: tag = arg.split(':')[0] attr = arg.split(':')[1] attr_name = attr.split('=')[0] attr_value = attr.split('=')[1] if attr_value is None: path += ("/" + tag) else: path += ("/" + tag + "[@" + attr_name + "='" + attr_value + "']") dest_node = dest_node.findall(path) if dest_node is not None and dest_node != []: return dest_node[0] raise Exception('can not find node by {} from xml'.format(args)) def save(self, xml=None, user_defined=False): """ save current xml to file. :param xml: the file name to save; if not specified, save current xml to default names. :param user_defined: save to user defined folder or default folder. :return: None. """ if self._curr_xml_tree is None: return if xml is None: xml = self._curr_xml xml_path = self._xml_path if user_defined: xml_path = os.path.join(self._xml_path, 'user_defined') if not os.path.isdir(xml_path): os.makedirs(xml_path) self._curr_xml_tree.write(os.path.join(xml_path, xml+'.xml'), encoding='utf-8', pretty_print=True) def _format_xml(self, element, depth=0): i = "\n" + depth * " " if element: if not element.text or not element.text.strip(): element.text = i + " " if not element.tail or not element.tail.strip(): element.tail = i for element in element: self._format_xml(element, depth + 1) if not element.tail or not element.tail.strip(): element.tail = i else: if depth and (not element.tail or not element.tail.strip()): element.tail = i def get_acrn_config_element(xsd_file): """ return the root element for the xsd file :param xsd_file: the input xsd schema file :return: the root element of the xsd file """ # schema = XMLSchema11(xsd_file) xsd_doc = etree.parse(xsd_file) xsd_doc.xinclude() schema = XMLSchema11(etree.tostring(xsd_doc, encoding="unicode")) xsd_element_root = schema.root_elements[0] acrn_config_element_root = xsd_2_acrn_config_element(xsd_element_root) # doc_dict = acrn_config_element_2_doc_dict(acrn_config_element_root, {}) # enum_dict = acrn_config_element_2_enum_dict(acrn_config_element_root, {}) # xpath_dict = acrn_config_element_2_xpath_dict(acrn_config_element_root, {}) # from pprint import pprint # pprint(acrn_config_element_root) # pprint(xpath_dict) return acrn_config_element_root def xsd_2_acrn_config_element(xsd_element, layer=0, index=0, path=''): """ translate XSD element to ACRN config element :param xsd_element: the xsd element :param layer: current layer :param index: current index of current layer :param path: path of current element :return: ACRN config element """ acrn_config_element = { 'name': xsd_element.name, 'type': None, 'path': path+'/'+xsd_element.name, 'layer': layer, 'index': index, 'doc': None, 'configurable': 'y', 'readonly': 'n', 'multiselect': 'n', 'default': xsd_element.default, 'attributes': None, # 'minOccurs': None, # 'maxOccurs': None, 'enumeration': None, 'sub_elements': None } if isinstance(xsd_element.type, Xsd11ComplexType): acrn_config_element['type'] = xsd_element.type.name for xsd_component in xsd_element.type.iter_components(): if isinstance(xsd_component, Xsd11Group): if acrn_config_element['sub_elements'] is None: acrn_config_element['sub_elements'] = {'all':[], 'choice':[], 'sequence':[]} index = 0 for sub_xsd_component in xsd_component.iter_components(): if isinstance(sub_xsd_component, Xsd11Element): sub_acrn_config_element = xsd_2_acrn_config_element(sub_xsd_component, layer+1, index, path+'/'+xsd_element.name) acrn_config_element['sub_elements'][xsd_component.model].append(sub_acrn_config_element) index += 1 else: if isinstance(xsd_element.type, XsdAtomicBuiltin): acrn_config_element['type'] = xsd_element.type.name elif isinstance(xsd_element.type.base_type, XsdSimpleType): acrn_config_element['type'] = xsd_element.type.base_type.name else: acrn_config_element['type'] = xsd_element.type.name if xsd_element.type.enumeration: acrn_config_element['enumeration'] = xsd_element.type.enumeration annotation = None if hasattr(xsd_element, 'annotation') and xsd_element.annotation: annotation = xsd_element.annotation elif hasattr(xsd_element.type, 'annotation') and xsd_element.type.annotation: annotation = xsd_element.type.annotation if annotation: if annotation.documentation: doc_list = [documentation.text for documentation in annotation.documentation] acrn_config_element['doc'] = '\n'.join(doc_list) for key in annotation.elem.keys(): if key.endswith('configurable'): acrn_config_element['configurable'] = annotation.elem.get(key) elif key.endswith('readonly'): acrn_config_element['readonly'] = annotation.elem.get(key) elif key.endswith('multiselect'): acrn_config_element['multiselect'] = annotation.elem.get(key) if xsd_element.attributes: attrs = [] for attr in xsd_element.attributes.iter_components(): if isinstance(attr, Xsd11Attribute): attrs.append({'name': attr.name, 'type': attr.type.name}) acrn_config_element['attributes'] = attrs return acrn_config_element def acrn_config_element_2_doc_dict(acrn_config_element, doc_dict): """ get the dictionary for documentation of all configurable elements by ACRN config element. :param acrn_config_element: the ACRN config element :param doc_dict: the dictionary to save documentation of all configurable elements :return: the dictionary to save documentation of all configurable elements """ if 'doc' in acrn_config_element and 'path' in acrn_config_element \ and acrn_config_element['path'] not in doc_dict: if acrn_config_element['doc']: doc_dict[acrn_config_element['path']] = acrn_config_element['doc'] else: doc_dict[acrn_config_element['path']] = acrn_config_element['name'] if 'sub_elements' in acrn_config_element and acrn_config_element['sub_elements']: for order_type in acrn_config_element['sub_elements']: for element in acrn_config_element['sub_elements'][order_type]: doc_dict = acrn_config_element_2_doc_dict(element, doc_dict) return doc_dict def acrn_config_element_2_enum_dict(acrn_config_element, enum_dict): """ get the dictionary for enumeration of all configurable elements by ACRN config element. :param acrn_config_element: the ACRN config element :param enum_dict: the dictionary to save enumeration of all configurable elements :return: the dictionary to save enumeration of all configurable elements """ if 'enumeration' in acrn_config_element and 'path' in acrn_config_element \ and acrn_config_element['path'] not in enum_dict \ and acrn_config_element['enumeration']: enum_dict[acrn_config_element['path']] = acrn_config_element['enumeration'] if 'sub_elements' in acrn_config_element and acrn_config_element['sub_elements']: for order_type in acrn_config_element['sub_elements']: for element in acrn_config_element['sub_elements'][order_type]: enum_dict = acrn_config_element_2_enum_dict(element, enum_dict) return enum_dict def acrn_config_element_2_xpath_dict(acrn_config_element, xpath_dict): """ get the dictionary for xpath of all configurable elements by ACRN config element. :param acrn_config_element: the ACRN config element :param xpath_dict: the dictionary to save xpath of all configurable elements :return: the dictionary to save xpath of all configurable elements """ if acrn_config_element['path'] not in xpath_dict.keys(): xpath_dict[acrn_config_element['path']] = { 'name': acrn_config_element['name'], 'type': acrn_config_element['type'], 'layer': acrn_config_element['layer'], 'index': acrn_config_element['index'], 'doc': acrn_config_element['doc'] if acrn_config_element['doc'] else acrn_config_element['name'], 'configurable': acrn_config_element['configurable'], 'readonly': acrn_config_element['readonly'], 'multiselect': acrn_config_element['multiselect'], 'default': acrn_config_element['default'], 'attributes': acrn_config_element['attributes'], # 'minOccurs': None, # 'maxOccurs': None, 'enumeration': acrn_config_element['enumeration'] } if 'sub_elements' in acrn_config_element and acrn_config_element['sub_elements']: for order_type in acrn_config_element['sub_elements']: for element in acrn_config_element['sub_elements'][order_type]: enum_dict = acrn_config_element_2_xpath_dict(element, xpath_dict) return xpath_dict