From 06eacfa32d824156e7dd4e7a4f2eff36cdc20bd9 Mon Sep 17 00:00:00 2001 From: Junjie Mao Date: Fri, 18 Feb 2022 23:45:54 +0800 Subject: [PATCH] config_tools: walk through xs:alternative The "xs:alternative" nodes in XML schema enables specifying different types for the same node according to the contents of that node. A typical usage of this construct in the schema of scenario XMLs is to specify different types for a `vm` node depending on whether it is a pre-launched, post-launched or service VMs, so that each VM contains only configurations supported in that kind of VM. This patch adds support of xs:alternative nodes to the schema transformer so that default values can be populated appropriately after we add xs:alternative nodes to the schema. Tracked-On: #6690 Signed-off-by: Junjie Mao --- .../scenario_config/scenario_transformer.py | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/misc/config_tools/scenario_config/scenario_transformer.py b/misc/config_tools/scenario_config/scenario_transformer.py index 2e10515aa..608dff2f6 100644 --- a/misc/config_tools/scenario_config/scenario_transformer.py +++ b/misc/config_tools/scenario_config/scenario_transformer.py @@ -5,6 +5,8 @@ # SPDX-License-Identifier: BSD-3-Clause # +import elementpath + class ScenarioTransformer: xpath_ns = { "xs": "http://www.w3.org/2001/XMLSchema", @@ -12,20 +14,38 @@ class ScenarioTransformer: @classmethod def get_node(cls, element, xpath): - return next(iter(element.xpath(xpath, namespaces=cls.xpath_ns)), None) + return element.find(xpath, namespaces=cls.xpath_ns) 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}']") + def type_of_element(self, type_tag, xsd_element_node, xml_node): + xsd_alternative_node = xsd_element_node + if xml_node is not None: + for alternative in xsd_element_node.findall("xs:alternative", namespaces=self.xpath_ns): + if elementpath.select(xml_node, alternative.get("test")): + xsd_alternative_node = alternative + break + + type_node = xsd_alternative_node.find(type_tag, namespaces=self.xpath_ns) + if type_node is None: + type_name = xsd_alternative_node.get("type") + if type_name: + type_node = self.get_node(self.xsd_etree, f".//{type_tag}[@name='{type_name}']") + + return type_node + + def simple_type_of_element(self, xsd_element_node, xml_node = None): + return self.type_of_element("xs:simpleType", xsd_element_node, xml_node) + + def complex_type_of_element(self, xsd_element_node, xml_node = None): + return self.type_of_element("xs:complexType", xsd_element_node, xml_node) + + def transform_node(self, xsd_element_node, xml_node): + complex_type_node = self.complex_type_of_element(xsd_element_node, xml_node) 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: @@ -47,7 +67,7 @@ class ScenarioTransformer: 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]) + self.transform_node(xsd_element_node, children.pop(0)[1]) def transform_all(self, xsd_all_node, xml_node): for xsd_element_node in xsd_all_node.findall("xs:element", namespaces=self.xpath_ns): @@ -73,6 +93,6 @@ class ScenarioTransformer: 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}']") + xsd_root_node = self.get_node(self.xsd_etree, f".//xs:element[@name='{xml_root_node.tag}']") if xsd_root_node is not None: self.transform_node(xsd_root_node, xml_root_node)