From 01a8267e7d6816c6373f137a19f7d8fe10b588e8 Mon Sep 17 00:00:00 2001 From: imcovangent <I.vanGent@tudelft.nl> Date: Mon, 5 Feb 2018 09:25:08 +0100 Subject: [PATCH] Added functionalities to SSBJ. Former-commit-id: 25efca8c43f09119795b9ca16e16f3c840819e59 --- examples/knowledgebases/ssbj/Aerodynamics.py | 31 ++++- examples/knowledgebases/ssbj/Performance.py | 2 +- examples/knowledgebases/ssbj/Propulsion.py | 2 +- examples/knowledgebases/ssbj/Structures.py | 2 +- kadmos/utilities/xmls.py | 136 +++++++++++++++++++ 5 files changed, 167 insertions(+), 6 deletions(-) diff --git a/examples/knowledgebases/ssbj/Aerodynamics.py b/examples/knowledgebases/ssbj/Aerodynamics.py index 6c8e8a3e5..e26325854 100644 --- a/examples/knowledgebases/ssbj/Aerodynamics.py +++ b/examples/knowledgebases/ssbj/Aerodynamics.py @@ -10,6 +10,8 @@ The files were adjusted for optimal use in KADMOS by Imco van Gent. from __future__ import absolute_import, division, print_function import sys +import os +import inspect import numpy as np from lxml import etree @@ -17,11 +19,16 @@ from lxml import etree from examples.knowledgebases.ssbj import root_tag, x_tc, x_AR, x_Lambda, x_Sref, x_L, x_WT, x_h, x_M, x_ESF, x_Theta, \ x_CDmin, x_D, x_fin, x_dpdx, x_Cf from examples.knowledgebases.ssbj.common import PolynomialFunction, add_discipline_to_cmdows, run_tool -from openlego.api import AbstractDiscipline -from openlego.utils.xml_utils import xml_safe_create_element +from kadmos.utilities.xmls import xml_safe_create_element -class Aerodynamics(AbstractDiscipline): +class Aerodynamics(): # AbstractDiscipline + + @property + def name(self): + # type: () -> str + """:obj:`str`: Name of this discipline.""" + return self.__class__.__name__ @property def creator(self): @@ -39,6 +46,24 @@ class Aerodynamics(AbstractDiscipline): def description(self): return u'Aerodynamic analysis discipline of the SSBJ test case.' + @property + def path(self): + # type: () -> str + """:obj:`str`: Path at which this discipline resides.""" + return os.path.dirname(inspect.getfile(self.__class__)) + + @property + def in_file(self): + # type: () -> str + """:obj:`str`: Path of the template input XML file of this discipline.""" + return os.path.join(self.path, self.name + '-input.xml') + + @property + def out_file(self): + # type: () -> str + """:obj:`str`: Path of the template output XML file of this discipline.""" + return os.path.join(self.path, self.name + '-output.xml') + def generate_input_xml(self): root = etree.Element(root_tag) doc = etree.ElementTree(root) diff --git a/examples/knowledgebases/ssbj/Performance.py b/examples/knowledgebases/ssbj/Performance.py index b67bd5f46..da3bce8b9 100644 --- a/examples/knowledgebases/ssbj/Performance.py +++ b/examples/knowledgebases/ssbj/Performance.py @@ -17,7 +17,7 @@ from lxml import etree from examples.knowledgebases.ssbj import root_tag, x_WT, x_h, x_M, x_fin, x_SFC, x_WF, x_R from examples.knowledgebases.ssbj.common import add_discipline_to_cmdows, run_tool from openlego.api import AbstractDiscipline -from openlego.utils.xml_utils import xml_safe_create_element +from kadmos.utilities.xmls import xml_safe_create_element class Performance(AbstractDiscipline): diff --git a/examples/knowledgebases/ssbj/Propulsion.py b/examples/knowledgebases/ssbj/Propulsion.py index d8b1ff414..3ba2211cb 100644 --- a/examples/knowledgebases/ssbj/Propulsion.py +++ b/examples/knowledgebases/ssbj/Propulsion.py @@ -16,7 +16,7 @@ from lxml import etree from examples.knowledgebases.ssbj import root_tag, x_h, x_M, x_ESF, x_D, x_Temp, x_SFC, x_WE, x_DT, x_WBE, x_T from examples.knowledgebases.ssbj.common import PolynomialFunction, add_discipline_to_cmdows, run_tool from openlego.api import AbstractDiscipline -from openlego.utils.xml_utils import xml_safe_create_element +from kadmos.utilities.xmls import xml_safe_create_element class Propulsion(AbstractDiscipline): diff --git a/examples/knowledgebases/ssbj/Structures.py b/examples/knowledgebases/ssbj/Structures.py index 87e51071c..ce72022e3 100644 --- a/examples/knowledgebases/ssbj/Structures.py +++ b/examples/knowledgebases/ssbj/Structures.py @@ -18,7 +18,7 @@ from examples.knowledgebases.ssbj import root_tag, x_tc, x_AR, x_Lambda, x_Sref, x_L, x_Nz, x_WT, x_WF, x_sigma1, x_sigma2, x_sigma3, x_sigma4, x_sigma5, x_Theta from examples.knowledgebases.ssbj.common import PolynomialFunction, add_discipline_to_cmdows, run_tool from openlego.api import AbstractDiscipline -from openlego.utils.xml_utils import xml_safe_create_element +from kadmos.utilities.xmls import xml_safe_create_element class Structures(AbstractDiscipline): diff --git a/kadmos/utilities/xmls.py b/kadmos/utilities/xmls.py index ec4882ded..74a86032d 100644 --- a/kadmos/utilities/xmls.py +++ b/kadmos/utilities/xmls.py @@ -2,6 +2,8 @@ import re import ast import logging +import numpy as np + from collections import OrderedDict from lxml import etree @@ -16,6 +18,13 @@ logger = logging.getLogger(__name__) # Settings for the parser parser = etree.XMLParser(remove_blank_text=True) +# Patterns for XML attribute names and values +pttrn_attr_val = r'([-.0-9:A-Z_a-z]+?)' +pttrn_attr_name = r'([:A-Z_a-z][0-9:A-Z_a-z]*?)' + +# Regular expressions to match attributes and indices within valid XPaths +re_atr = re.compile(r'\[@' + pttrn_attr_name + "=['\"]" + pttrn_attr_val + "['\"]\]") +re_ind = re.compile(r'\[([0-9]+?)\]') def recursively_stringify(tree): """ @@ -412,6 +421,133 @@ class ExtendedElement(etree.ElementBase): return dictionary +def xml_safe_create_element( + tree, # type: etree._ElementTree + xpath, # type: str + value=None # type: Optional[Union[str, int, float, List[Union[str, int, float]], np.ndarray]] +): + # type: (object, object, object) -> object + # type: (...) -> etree._Element + """Method taken from OpenLEGO package by Daniel de Vries: + + Create an element at the given XML XPath with the given value. + + This method ensures that all elements implied by the given X-Path exist. + + Supplying a value is optional. If no value is supplied an empty XML node is created at the deepest level implied by + the XPath. + + Parameters + ---------- + tree : :obj:`etree._ElementTree` + `etree._ElementTree` in which to create the element. + + xpath : str + XPath to ensure. + + value : str or int or float or list of str or list of int or list of float or :obj:`np.ndarray` + Optional value to write at the deepest node of the ensured XPath. + + Returns + ------- + :obj:`etree._Element` + Instance of `etree._Element` corresponding to the newly created element. + """ + # Split the xpath to get the intermediate nodes as a list + xpath_list = xpath.split('/') + n = len(xpath_list) + + # Loop over the elements in the XPath from tip to root until the XPath is found to already exist + elem = None + i = 0 + for i in range(0, n - 1): + xpath = '/'.join(xpath_list[0:(n - i)]) + try: + elems = tree.xpath(xpath) + if len(elems): + elem = elems[0] + break + except etree.XPathError: + raise ValueError('Specified XPath is invalid') + + # If no existing element was found the root elements of the tree and XPath don't match + if elem is None: + raise ValueError("Specified XPath is incompatible with the given XML tree: root tags don't match") + + # Loop over the part of the XPath beyond this point and create all intermediate elements including attributes + for j in range(n - i, n): + tag = xpath_list[j] + + # See if this node has an integer index specified + match_ind = re_ind.search(tag) + if match_ind: + tag = tag[:match_ind.start()] + tag[match_ind.end():] + index = int(match_ind.group(1)) - 1 + else: + index = 0 + + # Find any attributes at this node + attrib = {} + match_attr = list(re_atr.finditer(tag)) + if match_attr: + # Loop over all attributes on this node + for match in match_attr: + if match.start() < len(tag): + tag = tag[:match.start()] + attrib.update({match.group(1): match.group(2)}) + + # Check if there are siblings with the same name + siblings = elem.findall(tag) + n_siblings = len(siblings) + + # Check if there's a sibling with the same name at this index without conflicting attributes + if index < n_siblings and not any( + [siblings[index].attrib[key] != attrib[key] for key in attrib.keys() if key in siblings[index].attrib]): + # If so, use it instead of adding a new one + siblings[index].attrib.update(attrib) + elem = siblings[index] + elif index <= n_siblings: + # In this case just append a new element + _elem = etree.Element(tag, attrib) + elem.append(_elem) + elem = _elem + else: + # In the last case, insert as many empty siblings until this node's index + sibling = None + for i in range(index - n_siblings): + _sibling = etree.Element(tag) + if i == 0: + if not n_siblings: + elem.append(_sibling) + else: + siblings[-1].addnext(_sibling) + else: + sibling.addnext(_sibling) + sibling = _sibling + + # Finally at a new element at the right index with all attributes + elem = etree.Element(tag, attrib) + sibling.addnext(elem) + + # Finally we can update the current XPath, since it has been assured to exist at this point + xpath = '/'.join([xpath, xpath_list[j]]) + + # If a value was supplied assign it to the deepest element in the XPath + if value is not None: + if isinstance(value, np.ndarray): + value = np.atleast_1d(value).flatten() + + if isinstance(value, np.ndarray): + if value.size == 1: + elem.text = str(value[0]) + else: + elem.text = ';'.join([str(v) for v in value[:]]) + elem.attrib.update({'mapType': 'vector'}) + else: + elem.text = str(value) + + return elem + # Set element on the module level parser.set_element_class_lookup(etree.ElementDefaultClassLookup(element=ExtendedElement)) Element = parser.makeelement -- GitLab