103 lines
4.1 KiB
Python
Executable File
103 lines
4.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# Copyright (C) 2022 Intel Corporation.
|
|
#
|
|
# SPDX-License-Identifier: BSD-3-Clause
|
|
#
|
|
|
|
import os
|
|
import argparse
|
|
import elementpath
|
|
|
|
from scenario_transformer import ScenarioTransformer
|
|
|
|
from pipeline import PipelineObject, PipelineStage, PipelineEngine
|
|
from schema_slicer import SlicingSchemaByVMTypeStage
|
|
|
|
class DefaultValuePopulator(ScenarioTransformer):
|
|
def get_default_value(self, xsd_element_node, xml_parent_node):
|
|
# The attribute @default of the xsd:element node
|
|
v = xsd_element_node.get("default")
|
|
if v is not None:
|
|
return v
|
|
|
|
# The acrn:defaults and acrn:unique-among annotations which define a set of default values that shall be unique
|
|
# among a collection of nodes
|
|
annot_node = self.get_node(xsd_element_node, "xs:annotation")
|
|
if annot_node is not None:
|
|
defaults = annot_node.get("{https://projectacrn.org}defaults")
|
|
unique_among = annot_node.get("{https://projectacrn.org}unique-among")
|
|
if defaults is not None and unique_among is not None:
|
|
try:
|
|
default_values = set(eval(defaults))
|
|
existing_values = set(elementpath.select(self.xml_etree, unique_among, variables={"parent": xml_parent_node}))
|
|
available_defaults = default_values - existing_values
|
|
return sorted(list(available_defaults))[0]
|
|
except:
|
|
pass
|
|
|
|
return None
|
|
|
|
def add_missing_nodes(self, xsd_element_node, xml_parent_node, new_node_index):
|
|
element_name = xsd_element_node.get("name")
|
|
default_value = self.get_default_value(xsd_element_node, xml_parent_node)
|
|
|
|
# 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
|
|
# create the node at all. Users are required to fill in proper values in such nodes, and missing any of them
|
|
# shall trigger a validation error.
|
|
if self.complex_type_of_element(xsd_element_node) is None and default_value is None:
|
|
return []
|
|
|
|
new_node = xml_parent_node.makeelement(element_name, {})
|
|
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 fill_empty_node(self, xsd_element_node, xml_parent_node, xml_empty_node):
|
|
default_value = self.get_default_value(xsd_element_node, xml_parent_node)
|
|
if default_value is not None:
|
|
xml_empty_node.text = default_value
|
|
|
|
class DefaultValuePopulatingStage(PipelineStage):
|
|
uses = {"schema_etree", "scenario_etree"}
|
|
provides = {"scenario_etree"}
|
|
|
|
def run(self, obj):
|
|
populator = DefaultValuePopulator(obj.get("schema_etree"))
|
|
etree = obj.get("scenario_etree")
|
|
populator.transform(etree)
|
|
obj.set("scenario_etree", etree)
|
|
|
|
def main(args):
|
|
from xml_loader import XMLLoadStage
|
|
from lxml_loader import LXMLLoadStage
|
|
|
|
pipeline = PipelineEngine(["schema_path", "scenario_path"])
|
|
pipeline.add_stages([
|
|
LXMLLoadStage("schema"),
|
|
XMLLoadStage("scenario"),
|
|
SlicingSchemaByVMTypeStage(),
|
|
DefaultValuePopulatingStage(),
|
|
])
|
|
|
|
obj = PipelineObject(schema_path = args.schema, scenario_path = args.scenario)
|
|
pipeline.run(obj)
|
|
obj.get("scenario_etree").write(args.out)
|
|
|
|
if __name__ == "__main__":
|
|
config_tools_dir = os.path.join(os.path.dirname(__file__), "..")
|
|
schema_dir = os.path.join(config_tools_dir, "schema")
|
|
|
|
parser = argparse.ArgumentParser(description="Populate a given scenario XML with default values of nonexistent nodes")
|
|
parser.add_argument("scenario", help="Path to the scenario XML file from users")
|
|
parser.add_argument("out", nargs="?", default="out.xml", help="Path where the output is placed")
|
|
parser.add_argument("--schema", default=os.path.join(schema_dir, "config.xsd"), help="the XML schema that defines the syntax of scenario XMLs")
|
|
args = parser.parse_args()
|
|
|
|
main(args)
|