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