diff --git a/misc/config_tools/scenario_config/default_populator.py b/misc/config_tools/scenario_config/default_populator.py index ee23f2f18..d61398241 100755 --- a/misc/config_tools/scenario_config/default_populator.py +++ b/misc/config_tools/scenario_config/default_populator.py @@ -1,88 +1,37 @@ #!/usr/bin/env python3 # -# Copyright (C) 2021 Intel Corporation. All rights reserved. +# Copyright (C) 2022 Intel Corporation. # # SPDX-License-Identifier: BSD-3-Clause # import argparse import lxml.etree as etree +from scenario_transformer import ScenarioTransformer -xpath_ns = { - "xs": "http://www.w3.org/2001/XMLSchema", -} - -def get_node(element, xpath): - return next(iter(element.xpath(xpath, namespaces=xpath_ns)), None) - -def populate_sequence(xsd_etree, xsd_sequence_node, xml_node): - children = list(xml_node) - for element_node in xsd_sequence_node.findall("xs:element", namespaces=xpath_ns): - element_name = element_node.get("name") - element_min_occurs = element_node.get("minOccurs") - - if len(children) == 0: - if element_min_occurs != "0": - xml_child_node = etree.Element(element_name) - xml_node.append(xml_child_node) - populate(xsd_etree, element_node, xml_child_node, True) - elif children[0].tag != element_name: - if element_min_occurs != "0": - xml_child_node = etree.Element(element_name) - children[0].addprevious(xml_child_node) - populate(xsd_etree, element_node, xml_child_node, True) - else: - while len(children) > 0 and children[0].tag == element_name: - child_node = children.pop(0) - populate(xsd_etree, element_node, child_node, False) - -def populate_all(xsd_etree, xsd_all_node, xml_node): - for element_node in xsd_all_node.findall("xs:element", namespaces=xpath_ns): - element_name = element_node.get("name") - if not element_name: - continue - xml_child_nodes = xml_node.findall(element_name) - if len(xml_child_nodes) == 0: - element_min_occurs = element_node.get("minOccurs") - if element_min_occurs == "0": - continue - - xml_child_node = etree.Element(element_name) - xml_node.append(xml_child_node) - populate(xsd_etree, element_node, xml_child_node, True) - else: - for xml_child_node in xml_child_nodes: - populate(xsd_etree, element_node, xml_child_node, False) - -def populate(xsd_etree, xsd_element_node, xml_node, is_new_node): - complex_type_node = xsd_element_node.find("xs:complexType", namespaces=xpath_ns) - if not complex_type_node: - type_name = xsd_element_node.get("type") - if type_name: - complex_type_node = get_node(xsd_etree, f"//xs:complexType[@name='{type_name}']") - - if complex_type_node is not None: - sequence_node = complex_type_node.find("xs:sequence", namespaces=xpath_ns) - if sequence_node is not None: - populate_sequence(xsd_etree, sequence_node, xml_node) - - all_node = complex_type_node.find("xs:all", namespaces=xpath_ns) - if all_node is not None: - populate_all(xsd_etree, all_node, xml_node) - elif is_new_node: +class DefaultValuePopulator(ScenarioTransformer): + def add_missing_nodes(self, xsd_element_node, xml_parent_node, new_node_index): + element_name = xsd_element_node.get("name") default_value = xsd_element_node.get("default") - xml_node.text = default_value + + new_node = etree.Element(element_name) + if default_value is not None: + new_node.text = default_value + + if new_node_index is not None: + xml_parent_node.insert(new_node_index, new_node) + else: + xml_parent_node.append(new_node) + + return [new_node] def main(xsd_file, xml_file, out_file): - xml_etree = etree.parse(xml_file, etree.XMLParser(remove_blank_text=True)) xsd_etree = etree.parse(xsd_file) xsd_etree.xinclude() + populator = DefaultValuePopulator(xsd_etree) - xml_root = xml_etree.getroot() - xsd_root = get_node(xsd_etree, f"/xs:schema/xs:element[@name='{xml_root.tag}']") - - if xsd_root is not None: - populate(xsd_etree, xsd_root, xml_root, False) + xml_etree = etree.parse(xml_file, etree.XMLParser(remove_blank_text=True)) + populator.transform(xml_etree) xml_etree.write(out_file, pretty_print=True) @@ -90,7 +39,7 @@ if __name__ == "__main__": parser = argparse.ArgumentParser(description="Populate a given scenario XML with default values of nonexistent nodes") parser.add_argument("xsd", help="Path to the schema of scenario XMLs") parser.add_argument("xml", help="Path to the scenario XML file from users") - parser.add_argument("out", default="out.xml", help="Path where the output is placed") + parser.add_argument("out", nargs="?", default="out.xml", help="Path where the output is placed") args = parser.parse_args() main(args.xsd, args.xml, args.out) diff --git a/misc/config_tools/scenario_config/scenario_transformer.py b/misc/config_tools/scenario_config/scenario_transformer.py new file mode 100644 index 000000000..2e10515aa --- /dev/null +++ b/misc/config_tools/scenario_config/scenario_transformer.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2022 Intel Corporation. +# +# SPDX-License-Identifier: BSD-3-Clause +# + +class ScenarioTransformer: + xpath_ns = { + "xs": "http://www.w3.org/2001/XMLSchema", + } + + @classmethod + def get_node(cls, element, xpath): + return next(iter(element.xpath(xpath, namespaces=cls.xpath_ns)), None) + + def __init__(self, xsd_etree, visit_optional_node=False): + self.xsd_etree = xsd_etree + + self._visit_optional_node = visit_optional_node + + def transform_node(self, xsd_element_node, xml_node): + complex_type_node = xsd_element_node.find("xs:complexType", namespaces=self.xpath_ns) + if not complex_type_node: + type_name = xsd_element_node.get("type") + if type_name: + complex_type_node = self.get_node(self.xsd_etree, f"//xs:complexType[@name='{type_name}']") + + if complex_type_node is not None: + xsd_sequence_node = complex_type_node.find("xs:sequence", namespaces=self.xpath_ns) + if xsd_sequence_node is not None: + self.transform_sequence(xsd_sequence_node, xml_node) + + xsd_all_node = complex_type_node.find("xs:all", namespaces=self.xpath_ns) + if xsd_all_node is not None: + self.transform_all(xsd_all_node, xml_node) + + def transform_sequence(self, xsd_sequence_node, xml_node): + children = list(enumerate(list(xml_node))) + for xsd_element_node in xsd_sequence_node.findall("xs:element", namespaces=self.xpath_ns): + element_name = xsd_element_node.get("name") + element_min_occurs = xsd_element_node.get("minOccurs") + + if len(children) == 0 or children[0][1].tag != element_name: + if self._visit_optional_node or element_min_occurs != "0": + index = children[0][0] if len(children) > 0 else None + self.add_and_transform_missing_node(xsd_element_node, xml_node, new_node_index=index) + else: + while len(children) > 0 and children[0][1].tag == element_name: + self.transform_node(xsd_element_node, children.pop(0)[0]) + + def transform_all(self, xsd_all_node, xml_node): + for xsd_element_node in xsd_all_node.findall("xs:element", namespaces=self.xpath_ns): + element_name = xsd_element_node.get("name") + if not element_name: + continue + + xml_children = xml_node.findall(element_name) + if len(xml_children) == 0: + element_min_occurs = xsd_element_node.get("minOccurs") + if self._visit_optional_node or element_min_occurs != "0": + self.add_and_transform_missing_node(xsd_element_node, xml_node) + else: + for xml_child_node in xml_children: + self.transform_node(xsd_element_node, xml_child_node) + + def add_and_transform_missing_node(self, xsd_element_node, xml_parent_node, new_node_index=None): + for new_node in self.add_missing_nodes(xsd_element_node, xml_parent_node, new_node_index): + self.transform_node(xsd_element_node, new_node) + + def add_missing_nodes(self, xsd_element_node, xml_parent_node, new_node_index): + return [] + + def transform(self, xml_etree): + xml_root_node = xml_etree.getroot() + xsd_root_node = self.get_node(self.xsd_etree, f"/xs:schema/xs:element[@name='{xml_root_node.tag}']") + if xsd_root_node is not None: + self.transform_node(xsd_root_node, xml_root_node) diff --git a/misc/config_tools/scenario_config/validator.py b/misc/config_tools/scenario_config/validator.py index 3ccf3648f..ce0704898 100644 --- a/misc/config_tools/scenario_config/validator.py +++ b/misc/config_tools/scenario_config/validator.py @@ -18,7 +18,7 @@ except ImportError: "To enable the validation, install the python package by executing: pip3 install xmlschema.") sys.exit(0) -from default_populator import get_node, populate +from default_populator import DefaultValuePopulator def existing_file_type(parser): def aux(arg): @@ -40,12 +40,11 @@ def log_level_type(parser): return aux def load_schema(xsd_xml, datachecks_xml): - global schema, schema_etree, schema_root, datachecks + global schema, schema_etree, datachecks schema_etree = etree.parse(xsd_xml) schema_etree.xinclude() schema = xmlschema.XMLSchema11(etree.tostring(schema_etree, encoding="unicode")) - schema_root = get_node(schema_etree, f"/xs:schema/xs:element") datachecks_etree = etree.parse(datachecks_xml) datachecks_etree.xinclude() @@ -55,7 +54,6 @@ config_tools_dir = os.path.join(os.path.dirname(__file__), "..") schema_dir = os.path.join(config_tools_dir, "schema") schema = None schema_etree = None -schema_root = None datachecks = None load_schema(os.path.join(schema_dir, "config.xsd"), os.path.join(schema_dir, "datachecks.xsd")) @@ -67,7 +65,7 @@ def validate_one(board_xml, scenario_xml): scenario_name = os.path.basename(scenario_xml) scenario_etree = etree.parse(scenario_xml, etree.XMLParser(remove_blank_text=True)) - populate(schema_etree, schema_root, scenario_etree.getroot(), False) + DefaultValuePopulator(schema_etree).transform(scenario_etree) it = schema.iter_errors(scenario_etree) for error in it: