1#!/usr/bin/env python3 2# 3# Copyright (C) 2022 Intel Corporation. 4# 5# SPDX-License-Identifier: BSD-3-Clause 6# 7 8import os 9import argparse 10import elementpath 11 12from scenario_transformer import ScenarioTransformer 13 14from pipeline import PipelineObject, PipelineStage, PipelineEngine 15from schema_slicer import SlicingSchemaByVMTypeStage 16 17class DefaultValuePopulator(ScenarioTransformer): 18 def get_default_value(self, xsd_element_node, xml_parent_node): 19 # The attribute @default of the xsd:element node 20 v = xsd_element_node.get("default") 21 if v is not None: 22 return v 23 24 # The acrn:defaults and acrn:unique-among annotations which define a set of default values that shall be unique 25 # among a collection of nodes 26 annot_node = self.get_node(xsd_element_node, "xs:annotation") 27 if annot_node is not None: 28 defaults = annot_node.get("{https://projectacrn.org}defaults") 29 unique_among = annot_node.get("{https://projectacrn.org}unique-among") 30 if defaults is not None and unique_among is not None: 31 try: 32 default_values = set(eval(defaults)) 33 existing_values = set(elementpath.select(self.xml_etree, unique_among, variables={"parent": xml_parent_node})) 34 available_defaults = default_values - existing_values 35 return sorted(list(available_defaults))[0] 36 except: 37 pass 38 39 return None 40 41 def add_missing_nodes(self, xsd_element_node, xml_parent_node, new_node_index): 42 element_name = xsd_element_node.get("name") 43 default_value = self.get_default_value(xsd_element_node, xml_parent_node) 44 45 # If the node is neither of a complex type (i.e. it does not have an child node) nor has a default value, do not 46 # create the node at all. Users are required to fill in proper values in such nodes, and missing any of them 47 # shall trigger a validation error. 48 if self.complex_type_of_element(xsd_element_node) is None and default_value is None: 49 return [] 50 51 new_node = xml_parent_node.makeelement(element_name, {}) 52 new_node.text = default_value 53 54 if new_node_index is not None: 55 xml_parent_node.insert(new_node_index, new_node) 56 else: 57 xml_parent_node.append(new_node) 58 59 return [new_node] 60 61 def fill_empty_node(self, xsd_element_node, xml_parent_node, xml_empty_node): 62 default_value = self.get_default_value(xsd_element_node, xml_parent_node) 63 if default_value is not None: 64 xml_empty_node.text = default_value 65 66class DefaultValuePopulatingStage(PipelineStage): 67 uses = {"schema_etree", "scenario_etree"} 68 provides = {"scenario_etree"} 69 70 def run(self, obj): 71 populator = DefaultValuePopulator(obj.get("schema_etree")) 72 etree = obj.get("scenario_etree") 73 populator.transform(etree) 74 obj.set("scenario_etree", etree) 75 76def main(args): 77 from xml_loader import XMLLoadStage 78 from lxml_loader import LXMLLoadStage 79 80 pipeline = PipelineEngine(["schema_path", "scenario_path"]) 81 pipeline.add_stages([ 82 LXMLLoadStage("schema"), 83 XMLLoadStage("scenario"), 84 SlicingSchemaByVMTypeStage(), 85 DefaultValuePopulatingStage(), 86 ]) 87 88 obj = PipelineObject(schema_path = args.schema, scenario_path = args.scenario) 89 pipeline.run(obj) 90 obj.get("scenario_etree").write(args.out) 91 92if __name__ == "__main__": 93 config_tools_dir = os.path.join(os.path.dirname(__file__), "..") 94 schema_dir = os.path.join(config_tools_dir, "schema") 95 96 parser = argparse.ArgumentParser(description="Populate a given scenario XML with default values of nonexistent nodes") 97 parser.add_argument("scenario", help="Path to the scenario XML file from users") 98 parser.add_argument("out", nargs="?", default="out.xml", help="Path where the output is placed") 99 parser.add_argument("--schema", default=os.path.join(schema_dir, "config.xsd"), help="the XML schema that defines the syntax of scenario XMLs") 100 args = parser.parse_args() 101 102 main(args) 103