config_tools: populate default values in scenario XML

While we have default values of configuration entries stated in the schema
of scenario XMLs, today we still require user-given scenario XMLs to
contain literally ALL XML nodes. Missing of a single node will cause schema
validation errors even though we can use its default value defined in the
schema.

This patch allows user-given scenario XMLs to ignore nodes with default
values. It is done by adding the missing nodes, all containing the defined
default values, to the input scenario XML when copying it to the build
directory. This approach imposes no changes to either the schema or
subsequent scripts in the build system.

Tracked-On: #6292
Signed-off-by: Junjie Mao <junjie.mao@intel.com>
This commit is contained in:
Junjie Mao 2021-08-31 13:39:32 +08:00 committed by wenlingz
parent 955103f7ea
commit 2bfaa34cf2
2 changed files with 97 additions and 1 deletions

View File

@ -113,6 +113,7 @@ HV_UNIFIED_XML_IN := $(BASEDIR)/scripts/makefile/unified.xml.in
HV_PREDEFINED_DATA_DIR := $(realpath $(BASEDIR)/../misc/config_tools/data)
HV_CONFIG_TOOL_DIR := $(realpath $(BASEDIR)/../misc/config_tools)
HV_CONFIG_XFORM_DIR := $(HV_CONFIG_TOOL_DIR)/xforms
HV_SCENARIO_XSD := $(HV_CONFIG_TOOL_DIR)/schema/config.xsd
# Paths to the outputs:
HV_CONFIG_DIR := $(HV_OBJDIR)/configs
@ -208,7 +209,7 @@ $(HV_SCENARIO_XML):
if [ -f $(SCENARIO_FILE) ]; then \
echo "Scneario XML is configuration fetched from $(realpath $(SCENARIO_FILE))"; \
mkdir -p $(dir $(HV_SCENARIO_XML)); \
cp $(SCENARIO_FILE) $(HV_SCENARIO_XML); \
python3 $(HV_CONFIG_TOOL_DIR)/scenario_config/default_populator.py $(HV_SCENARIO_XSD) $(SCENARIO_FILE) $(HV_SCENARIO_XML); \
else \
echo "No pre-defined scenario available at $(SCENARIO_FILE)"; \
echo "Try setting another predefined BOARD or SCENARIO or specifying a scenario XML file"; \

View File

@ -0,0 +1,95 @@
#!/usr/bin/env python3
#
# Copyright (C) 2021 Intel Corporation. All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
import argparse
import lxml.etree as etree
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_node = xml_node.find(element_name)
if xml_child_node is None:
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:
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:
default_value = xsd_element_node.get("default")
xml_node.text = default_value
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()
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.write(out_file, pretty_print=True)
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")
args = parser.parse_args()
main(args.xsd, args.xml, args.out)