From 3b1581749025def07df7d90ad211841a2d3da4e7 Mon Sep 17 00:00:00 2001 From: Olaf Corning Date: Tue, 21 Nov 2023 00:33:23 -0800 Subject: [PATCH 1/8] added object to XML functionality --- src/ObjectToXML.py | 33 ++++++++++++++ src/__init__.py | 1 + tests/unit/__init__.py | 1 + tests/unit/test_ObjectToXML.py | 81 ++++++++++++++++++++++++++++++++++ 4 files changed, 116 insertions(+) create mode 100644 src/ObjectToXML.py create mode 100644 src/__init__.py create mode 100644 tests/unit/test_ObjectToXML.py diff --git a/src/ObjectToXML.py b/src/ObjectToXML.py new file mode 100644 index 0000000..e507c09 --- /dev/null +++ b/src/ObjectToXML.py @@ -0,0 +1,33 @@ +import xml.etree.ElementTree as ET +from dataclasses import dataclass, field +from typing import List + +@dataclass(frozen=True, order=True) +class xml_object: + name: str + self_closing: bool = False #True if < tag /> + attribute_dict: dict = field(default_factory=dict) + children: List['xml_object'] = field(default_factory=list) + parent: 'xml_object' = None # Added parent attribute + + + def __post_init__(self): + # Set parent attribute for each child + for child in self.children: + child.parent = self + + def convert_to_element_tree(self): + element = ET.Element(self.name, self.attribute_dict) + for child in self.children: + child_element = child.convert_to_element_tree() + element.append(child_element) + return element + def save_XML(self, file_name): + tree = ET.ElementTree(self.convert_to_element_tree()) + print(type(tree)) + ET.indent(tree, space="\t", level=0) + tree.write(file_name, encoding="utf-8") + + + + diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..22433fd --- /dev/null +++ b/src/__init__.py @@ -0,0 +1 @@ +from src.ObjectToXML import xml_object \ No newline at end of file diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py index e69de29..b392963 100644 --- a/tests/unit/__init__.py +++ b/tests/unit/__init__.py @@ -0,0 +1 @@ +from unit.test_ObjectToXML import TestObjectCreation \ No newline at end of file diff --git a/tests/unit/test_ObjectToXML.py b/tests/unit/test_ObjectToXML.py new file mode 100644 index 0000000..3af8d62 --- /dev/null +++ b/tests/unit/test_ObjectToXML.py @@ -0,0 +1,81 @@ +import unittest +import sys +import os + +current_script_path = os.path.dirname(os.path.abspath(__file__)) +relative_path_to_xml_object = os.path.normpath(os.path.join(current_script_path, '../../src/')) +sys.path.append(relative_path_to_xml_object) +from ObjectToXML import xml_object + +output =""" + + + + + + + + + + + + + + +""" + +class TestObjectCreation(unittest.TestCase): + """Test the class xml_object creation method""" + + def setUp_nochildren_noparent(self): + self.population_simple = xml_object(name = 'population', attribute_dict = {'id':'C', 'init':'100', 'class':'cancer'}, self_closing = False) + def setUp_children(self): + """ + populations + |-population + |-population parameter + |-population process + |-population process + """ + self.tree_with_children = xml_object(name = 'populations', self_closing = False) + self.tree_with_children.children.append(xml_object(name = 'population', attribute_dict = {'id':'C', 'init':'100', 'class':'cancer'}, self_closing = False)) + self.tree_with_children.children[0].children.append(xml_object(name = 'population.parameter', attribute_dict = {'id':'DIVISION_POTENTIAL', 'value':'3'}, self_closing = True)) + self.tree_with_children.children[0].children.append(xml_object(name = 'population.process', attribute_dict = {'id':'METABOLISM', 'version':'complex'}, self_closing = True)) + self.tree_with_children.children[0].children.append(xml_object(name = 'population.process', attribute_dict = {'id':'SIGNALING', 'version':'complex'}, self_closing = True) ) + + print(self) + print("olaf") + print(self) + + def test_to_convert_to_element_tree_simple(self): + self.setUp_nochildren_noparent() + root_element = xml_object.convert_to_element_tree(self.population_simple) + self.assertEqual(root_element.tag, 'population') + self.assertEqual(root_element.attrib, {'id':'C', 'init':'100', 'class':'cancer'}) + xml_object.save_XML(self.population_simple, "simpleTest.xml") + + def test_to_convert_to_element_tree_children(self): + self.setUp_children() + root_element = xml_object.convert_to_element_tree(self.tree_with_children) + + self.assertEqual(root_element.tag, 'populations') + self.assertEqual(root_element[0].tag, 'population') + self.assertEqual(root_element[0].attrib, {'id':'C', 'init':'100', 'class':'cancer'}) + + children_elements = list(root_element[0]) + + self.assertEqual(children_elements[0].tag, 'population.parameter') + self.assertEqual(children_elements[0].attrib, {'id':'DIVISION_POTENTIAL', 'value':'3'}) + + self.assertEqual(children_elements[1].tag, 'population.process') + self.assertEqual(children_elements[1].attrib, {'id':'METABOLISM', 'version':'complex'}) + + self.assertEqual(children_elements[2].tag, 'population.process') + self.assertEqual(children_elements[2].attrib, {'id':'SIGNALING', 'version':'complex'}) + + xml_object.save_XML(self.tree_with_children, "childrenTest.xml") + + + +if __name__ == "__main__": + unittest.main() From 257170171aa2b2cca6414c5f1cb53025ebce96fd Mon Sep 17 00:00:00 2001 From: Olaf Corning Date: Tue, 21 Nov 2023 00:38:12 -0800 Subject: [PATCH 2/8] added object to XML functionality (and pyproject.toml) --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index b0426da..80ee1ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,3 +22,6 @@ sphinx_mdinclude = "^0.5.1" [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" + +[tool.pytest.ini_options] +pythonpath = "arcade-xml-generator" \ No newline at end of file From 881bc2ca0935e9757258ab89a4485e2f92bb0484 Mon Sep 17 00:00:00 2001 From: olaf-c <42818212+olaf-c@users.noreply.github.com> Date: Wed, 22 Nov 2023 14:09:40 -0800 Subject: [PATCH 3/8] Update build.yml --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e34e8ce..4d933ba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: - python-version: [3.9] + python-version: [3.12] steps: From 6338bbb5698b744d2c0e9096986972079f7eb075 Mon Sep 17 00:00:00 2001 From: Olaf Corning Date: Mon, 27 Nov 2023 16:59:13 -0800 Subject: [PATCH 4/8] added input XML to object functionality --- src/EditXML.py | 53 ++++++++++++++++++++++++++++++++++ src/GUI.py | 50 ++++++++++++++++++++++++++++++++ src/XMLObject.py | 33 +++++++++++++++++++++ tests/unit/test_XMLToObject.py | 32 ++++++++++++++++++++ 4 files changed, 168 insertions(+) create mode 100644 src/EditXML.py create mode 100644 src/GUI.py create mode 100644 src/XMLObject.py create mode 100644 tests/unit/test_XMLToObject.py diff --git a/src/EditXML.py b/src/EditXML.py new file mode 100644 index 0000000..2d0a249 --- /dev/null +++ b/src/EditXML.py @@ -0,0 +1,53 @@ +import tkinter as tk +import xml.etree.ElementTree as ET +import sys +import os + +current_script_path = os.path.dirname(os.path.abspath(__file__)) +relative_path_to_XMLObject = os.path.normpath(os.path.join(current_script_path, '../../src/')) +sys.path.append(relative_path_to_XMLObject) +from XMLObject import XMLObject + + +def list_directories(): + directoryList =[] + directoryTuples = os.walk(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) + for x in directoryTuples: + directoryList.append(x[0]) + return directoryList + +def find_and_store_parameter_files_as_XMLObject(directories, keyword): + xml_files = {} + for directory in directories: + for filename in os.listdir(directory): + if filename.endswith(".xml") and keyword in filename: + file_path = os.path.join(directory, filename) + key = "_".join([os.path.basename(directory.rstrip('/')), os.path.splitext(filename)[0]])#drops extension + xml_files[key] = load_xml(file_path) + return xml_files + +def load_xml(file_name): + tree = ET.parse(file_name) + root_element = tree.getroot() + return element_to_xml_object(root_element) + +def element_to_xml_object(element, parent=None): + xml_object = XMLObject( + name=element.tag, + attribute_dict=element.attrib, + parent=parent + ) + for child_element in element: + child_xml_object = element_to_xml_object(child_element, parent=xml_object) + xml_object.children.append(child_xml_object) + return xml_object + + +# list = [ + +# ] + +# def create_new_population(): +# raise error NotImplemented + +# def modify_population(): \ No newline at end of file diff --git a/src/GUI.py b/src/GUI.py new file mode 100644 index 0000000..8666310 --- /dev/null +++ b/src/GUI.py @@ -0,0 +1,50 @@ +import sys +import os +import tkinter as tk +import datetime +import EditXML +import XMLObject +def initiate_GUI (xml_files): + root = tk.Tk() + root.title("XML Editor") + + # Create a dropdown menu + xml_dropdown = tk.StringVar() + xml_dropdown.set("Select an XML file") + + menu = tk.OptionMenu(root, xml_dropdown, *xml_files.keys(), command=lambda _: select_xml_data(xml_dropdown.get(), xml_files)) + menu.pack() + + # Create a frame to hold the XML data + global xml_frame + xml_frame = tk.Frame(root) + xml_frame.pack() + + # List to hold XML edit widgets for cleanup + global xml_edit_widgets + xml_edit_widgets = [] + + root.mainloop() +def select_xml_data(selected_key, XML_trees): + xml_tree = XML_trees[selected_key] + + # Clear previous widgets + for widget in xml_edit_widgets: + widget.destroy() + xml_edit_widgets.clear() + display_XMLObject(xml_tree.getroot()) + +def display_XMLObject(XMLObject): + #NOT IMPLEMENTED + print("Displaying XMLObject:") + print(XMLObject) + +if __name__ == "__main__": + + root_directory = 'arcade-xml-generator' + subdirectory = 'xml_files' + directories = EditXML.list_directories() + xml_files = EditXML.find_and_store_parameter_files_as_XMLObject(directories, "parameter") + print(directories) + print(xml_files) + initiate_GUI(xml_files) \ No newline at end of file diff --git a/src/XMLObject.py b/src/XMLObject.py new file mode 100644 index 0000000..9ccc6e9 --- /dev/null +++ b/src/XMLObject.py @@ -0,0 +1,33 @@ +import xml.etree.ElementTree as ET +from dataclasses import dataclass, field +from typing import List + +@dataclass(frozen=True, order=True) +class XMLObject: + name: str + attribute_dict: dict = field(default_factory=dict) + children: List['XMLObject'] = field(default_factory=list) + parent: 'XMLObject' = None # Added parent attribute + + + def __post_init__(self): + # Set parent attribute for each child + for child in self.children: + child.parent = self + + + def convert_to_element_tree(self): + element = ET.Element(self.name, self.attribute_dict) + for child in self.children: + child_element = child.convert_to_element_tree() + element.append(child_element) + return element + def save_XML(self, file_name): + tree = ET.ElementTree(self.convert_to_element_tree()) + print(type(tree)) + ET.indent(tree, space="\t", level=0) + tree.write(file_name, encoding="utf-8") + + + + diff --git a/tests/unit/test_XMLToObject.py b/tests/unit/test_XMLToObject.py new file mode 100644 index 0000000..3719093 --- /dev/null +++ b/tests/unit/test_XMLToObject.py @@ -0,0 +1,32 @@ +import sys +import os +import unittest +import xml.etree.ElementTree as ET + +current_script_path = os.path.dirname(os.path.abspath(__file__)) +relative_path_to_XMLObject = os.path.normpath(os.path.join(current_script_path, '../../src/')) +sys.path.append(relative_path_to_XMLObject) +from XMLObject import XMLObject +import EditXML + +def compare_xml_files(file1, file2): + tree1 = ET.parse(file1) + tree2 = ET.parse(file2) + + # Get the root elements + root1 = tree1.getroot() + root2 = tree2.getroot() + + # Compare the XML content + return ET.tostring(root1) == ET.tostring(root2) +class TestXMLToObject(unittest.TestCase): + def test_convert_xml_to_object(self): + tree_with_children = EditXML.load_xml('childrenTest.xml') + XMLObject.save_XML(tree_with_children, 'childrenTest_converted.xml') + self.assertTrue(compare_xml_files('childrenTest.xml', 'childrenTest_converted.xml')) + + #def check_load_xml_selection(self): + # intialize GUI + # Check if XML selection == loaded XML +if __name__ == "__main__": + unittest.main() From f5d550326954ede9e04d8e80407ff4b773ca4831 Mon Sep 17 00:00:00 2001 From: Olaf Corning Date: Tue, 19 Dec 2023 23:11:40 -0800 Subject: [PATCH 5/8] Gui incomplete. Should be remade with PyQt Designer. Compiler has necessary components in xml_object.py, uncertain how low level it should operate. Should it receive from gui.py strings or xml_objects. --- src/compile_xml.py | 38 +++++++++ src/read_input_xml.py | 108 ++++++++++++++++++++++++ src/xml_object.py | 88 ++++++++++++++++++++ tests/unit/test_compile.py | 24 ++++++ tests/unit/test_read_input_xml.py | 105 ++++++++++++++++++++++++ tests/unit/test_xml_object.py | 131 ++++++++++++++++++++++++++++++ 6 files changed, 494 insertions(+) create mode 100644 src/compile_xml.py create mode 100644 src/read_input_xml.py create mode 100644 src/xml_object.py create mode 100644 tests/unit/test_compile.py create mode 100644 tests/unit/test_read_input_xml.py create mode 100644 tests/unit/test_xml_object.py diff --git a/src/compile_xml.py b/src/compile_xml.py new file mode 100644 index 0000000..65563c5 --- /dev/null +++ b/src/compile_xml.py @@ -0,0 +1,38 @@ +#This module is not yet implemented. When it has been fully developed it will be a small module that takes a dictionary of xml_objects +# and organizes them using xml_object methods. The method of arranging the xml_objects will be specific by the string title of the initial .xml parameter file +# A final version of .xml will be saved for setup. Future iterations may include a Linter and a connection to Bash to directly run the setup. + +from logging import raiseExceptions +import tkinter as tk +import xml.etree.ElementTree as ET +import sys +import os + +current_script_path = os.path.dirname(os.path.abspath(__file__)) +relative_path_to_XMLObject = os.path.normpath(os.path.join(current_script_path, '../../src/')) +sys.path.append(relative_path_to_XMLObject) +from xml_object import XMLObject + + +class ReadOutputFunctions(): + def compile(self, dictionary_of_inputs, input_parameter_filename): + #Takes dictionary of inputs, and calls the function associated with the input_parameter_filename + if (input_parameter_filename == "parameter.patch"): + self.user_input_to_patch_setup(dictionary_of_inputs) + elif (input_parameter_filename == "potts.patch"): + self.user_input_to_potts_setup(dictionary_of_inputs) + else: + raise NotImplementedError("This function is not yet implemented.") + return + def user_input_to_patch_setup(self, dictionary_of_inputs): + #Compiles dictionary to create patch setup .xml + #NOT IMPLEMENTED + return + def user_input_to_potts_setup(self, dictionary_of_inputs): + #Compiles dictionary to create potts .xml + #NOT IMPLEMENTED + return + ###### POPULATION LEVEL ###### + def build_a_population(self, population_id_object, population_parameters_object): + + return diff --git a/src/read_input_xml.py b/src/read_input_xml.py new file mode 100644 index 0000000..6a1038a --- /dev/null +++ b/src/read_input_xml.py @@ -0,0 +1,108 @@ +import xml.etree.ElementTree as ET +import sys +import os + +current_script_path = os.path.dirname(os.path.abspath(__file__)) +relative_path_to_XMLObject = os.path.normpath(os.path.join(current_script_path, '../../src/')) +sys.path.append(relative_path_to_XMLObject) +from xml_object import XMLObject + + +def element_to_xml_object(element, parent=None): + #helper function for ReadInputFunctions. Takes a element tree and creates an xml_object + xml_object = XMLObject( + tag=element.tag, + attribute_dict=element.attrib, + parent=parent + ) + for child_element in element: + child_xml_object = element_to_xml_object(child_element, parent=xml_object) + xml_object.children.append(child_xml_object) + return xml_object +def list_to_xml_object(element, wrapper): + #helper function for ReadInputFunctions. Takes a list and creates an xml_object + childrenList= [] + for child_element in element: + child_xml_object = XMLObject( + tag=child_element.tag, + attribute_dict=child_element.attrib + ) + childrenList.append(child_xml_object) + root = XMLObject( + tag=wrapper, + children = childrenList + ) + + return root + +#Contains functions that locate and reads in .xml files and outputs dictionaries of element trees. +class ReadInputFunctions(): + def list_directories(): + #Lists all directories starting one level above and going down + directoryList =[] + directoryTuples = os.walk(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) + for x in directoryTuples: + directoryList.append(x[0]) + return directoryList + + def find_and_store_xml_files(directories, keyword): + # Saves all .xml files found in given list of directories that contain the keyword. Currently, the keyword is always "parameter" + xml_files = {} + for directory in directories: + for filename in os.listdir(directory): + if filename.endswith(".xml") and keyword in filename: + file_path = os.path.join(directory, filename) + key = os.path.splitext(os.path.basename(filename))[0]#drops extension + xml_files[key] = (file_path) + return xml_files + + + + def grab_parameters_based_on_prefix(file_name, prefix): + #Grabs ET elements out of .xml file based on prefix + #file_name is a string identifying the .xml file. prefix is a string + tree = ET.parse(file_name) + default_parameters = [] + for elem in tree.iter(): + if elem.tag.startswith(prefix): + default_parameters.append(elem) + return default_parameters + + def load_xml_as_object(file_name): + #Load .xml as an element tree and return the root. Gives a file_name + tree = ET.parse(file_name) + root_element = tree.getroot() + return element_to_xml_object(root_element) + + +class ReadInputModuleSpecific(): + def read_in_patch_parameters(xml_files): + #Reads in from dictionary of xml_files + #Requires parameters.patch.xml and parameters.xml as identified by their keys without .xml + dict_of_parameters = {} + #get default from parameters + dict_of_parameters.update({"default": ReadInputFunctions.grab_parameters_based_on_prefix(xml_files["parameter"], "default")}) + #get series from patch (tag is also default) + dict_of_parameters.update({"series": ReadInputFunctions.grab_parameters_based_on_prefix(xml_files["parameter.patch"], "default")}) + #get patch from patch + dict_of_parameters.update({"patch": ReadInputFunctions.grab_parameters_based_on_prefix(xml_files["parameter.patch"], "patch")}) + #get population from patch + dict_of_parameters.update({"population": ReadInputFunctions.grab_parameters_based_on_prefix(xml_files["parameter.patch"], "population")}) + #get layer from patch + dict_of_parameters.update({"layer": ReadInputFunctions.grab_parameters_based_on_prefix(xml_files["parameter.patch"], "layer")}) + #get action from patch + dict_of_parameters.update({"action": ReadInputFunctions.grab_parameters_based_on_prefix(xml_files["parameter.patch"], "action")}) + #get component from patch + dict_of_parameters.update({"component": ReadInputFunctions.grab_parameters_based_on_prefix(xml_files["parameter.patch"], "component")}) + return dict_of_parameters + + def read_in_potts_parameters(xml_files): + raise NotImplementedError("This function is not yet implemented.") + + def read_in_module(self, module, xml_files): + if module == "parameter.potts": + return self.read_in_potts_parameters(xml_files) + elif module == "parameter.patch": + return self.read_in_patch_parameters(xml_files) + else: + raise NotImplementedError("This is either an invalid parameter file or this function is not yet implemented.") diff --git a/src/xml_object.py b/src/xml_object.py new file mode 100644 index 0000000..af88bf8 --- /dev/null +++ b/src/xml_object.py @@ -0,0 +1,88 @@ +import xml.etree.ElementTree as ET +from dataclasses import dataclass, field +from typing import List +import copy + +@dataclass(order=True) +class XMLObject: + tag: str + attribute_dict: dict = field(default_factory=dict) + children: List['XMLObject'] = field(default_factory=list) + parent: 'XMLObject' = None #Initialized later + default_tag: str = field(init=False) + default_attribute_dict: dict = field(init=False) + + + def __post_init__(self): + # Set parent attribute for each child + for child in self.children: + child.parent = self + # Set default values + self.default_tag = copy.deepcopy(self.tag) + self.default_attribute_dict = copy.deepcopy(self.attribute_dict) + + + def convert_to_element_tree(self): + element = ET.Element(self.tag, self.attribute_dict) + for child in self.children: + child_element = child.convert_to_element_tree() + element.append(child_element) + return element + def save_XML(self, file_name): + tree = ET.ElementTree(self.convert_to_element_tree()) + print(type(tree)) + ET.indent(tree, space="\t", level=0) + tree.write(file_name, encoding="utf-8") + def merge_with_tag(self, donor_xml_object, target_tag): + # Find a node with a matching tag in the target_tag + target_node = self.find_node_with_tag(donor_xml_object, target_tag) + if target_node is not None: + # Append donor_xml_object as a child to the found node + target_node.append(donor_xml_object) + def find_node_with_tag(self, tag): + # Recursive function to find a node with a matching tag + if self.tag == tag: + return self + if self.children == None: + return None + for child in self.children: + node = child.find_node_with_tag(tag) + if node is not None: + return node + return None + def merge_with_id_value(self, donor_xml_object, target_id): + # Find a node with a matching tag in the target_tag + target_node = self.find_node_with_tag(donor_xml_object, target_id) + if target_node is not None: + # Append donor_xml_object as a child to the found node + target_node.append(donor_xml_object) + def find_node_with_id_value(self, id_value): + # Recursive function to find a node with a matching id value + if "id" in self.attribute_dict and self.attribute_dict["id"] == id_value: + return self + if self.children == None: + return None + for child in self.children: + node = child.find_node_with_id_value(id_value) + if node is not None: + return node + return None + def reset_tag(self): + self.tag = self.default_tag + def reset_attribute(self): + self.attribute_dict = self.default_attribute_dict + def reset(self): + self.reset_tag() + self.reset_attribute() + def edit_tag(self, new_tag): + self.tag = new_tag + def edit_attribute(self, key, new_attribute): + if key in self.attribute_dict: + self.attribute_dict[key] = new_attribute + else: + raise KeyError(f"Key '{key}' not found in attribute_dict") + def create_a_population(self, id, parameter_init, parameter_class): + return + + + diff --git a/tests/unit/test_compile.py b/tests/unit/test_compile.py new file mode 100644 index 0000000..d398713 --- /dev/null +++ b/tests/unit/test_compile.py @@ -0,0 +1,24 @@ +import unittest +import sys +import os + +current_script_path = os.path.dirname(os.path.abspath(__file__)) +relative_path_to_XMLObject = os.path.normpath(os.path.join(current_script_path, '../../src/')) +sys.path.append(relative_path_to_XMLObject) +from xml_object import XMLObject +from compile_xml import ReadOutputFunctions + +#These tests and the module it tests are not implemented. +class TestCompileSetUpFile(unittest.TestCase): + + #This method will test the pipeline of user input into a xml_object + def test_user_input_to_object(self): + return + + #This method tests the ability to create a .xml patch set up file. + def test_patch_specific(self): + return + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unit/test_read_input_xml.py b/tests/unit/test_read_input_xml.py new file mode 100644 index 0000000..9c354f0 --- /dev/null +++ b/tests/unit/test_read_input_xml.py @@ -0,0 +1,105 @@ +import sys +import os +import unittest +import xml.etree.ElementTree as ET + +current_script_path = os.path.dirname(os.path.abspath(__file__)) +relative_path_to_XMLObject = os.path.normpath(os.path.join(current_script_path, '../../src/')) +sys.path.append(relative_path_to_XMLObject) +from xml_object import XMLObject +from read_input_xml import ReadInputModuleSpecific +from read_input_xml import ReadInputFunctions + +# Utilized by the tests for quick comparison +def compare_xml_files(file1, file2): + tree1 = ET.parse(file1) + tree2 = ET.parse(file2) + + # Get the root elements + root1 = tree1.getroot() + root2 = tree2.getroot() + + # Compare the XML content + return ET.tostring(root1) == ET.tostring(root2) +def compare_element_lists(list1, list2): + # Check if the lengths are equal + if len(list1) != len(list2): + return False + + # Compare each element in the lists + for elem1, elem2 in zip(list1, list2): + if not compare_elements(elem1, elem2): + return False + + return True + +def compare_elements(elem1, elem2): + # Compare the tag, attributes, and text content of two elements + return ( + elem1.tag == elem2.tag and + elem1.attrib == elem2.attrib and + elem1.text == elem2.text and + compare_element_lists(elem1, elem2) + ) +class TestXMLToObject(unittest.TestCase): + #Tests the read_input_xml.py methods + def setUp(self): + test_files_directory = os.path.dirname(os.path.abspath(__file__)) + os.chdir(test_files_directory) + def test_fine_and_store_parameter_files_as_XMLObject(self): + # Tests that read_input_xml can locate all the parameter .xml files + list_of_directories = ReadInputFunctions.list_directories() + list_of_parameter_files = ReadInputFunctions.find_and_store_xml_files(list_of_directories, "parameter") + tester_parameter_list = ['parameter.patch', 'parameter.potts', 'parameter'] + self.assertEqual(len(list_of_parameter_files), len(tester_parameter_list)) + + def test_convert_xml_to_object(self): + # Tests that .xml file can be converted into a xml_object + list_of_directories = ReadInputFunctions.list_directories() + dict_of_parameter_files = ReadInputFunctions.find_and_store_xml_files(list_of_directories, "") + print("testPrint") + print(dict_of_parameter_files['children_test']) + tree_with_children = ReadInputFunctions.load_xml_as_object(dict_of_parameter_files['children_test']) + XMLObject.save_XML(tree_with_children, 'children_test_converted.xml') + self.assertTrue(compare_xml_files('children_test.xml', 'children_test_converted.xml')) + + + def test_parse_xml_by_tag(self): + # Tests that .xml can be parsed by tag + list_of_directories = ReadInputFunctions.list_directories() + list_of_parameter_files = ReadInputFunctions.find_and_store_xml_files(list_of_directories, "parameter") + parsed_list_of_actions = ReadInputFunctions.grab_parameters_based_on_prefix(list_of_parameter_files['parameter.patch'], 'action') + + list_of_directories = ReadInputFunctions.list_directories() + list_of_parameter_files = ReadInputFunctions.find_and_store_xml_files(list_of_directories, "parameter") + parsed_list_of_patch = ReadInputFunctions.grab_parameters_based_on_prefix(list_of_parameter_files['parameter.patch'], 'layer') + + tree = ET.parse('action_param.xml') + only_list_of_actions = [] + for elem in tree.getroot(): + only_list_of_actions.append(elem) + + self.assertTrue(compare_element_lists(parsed_list_of_actions, only_list_of_actions)) + self.assertFalse(compare_element_lists(parsed_list_of_actions, parsed_list_of_patch)) + + def test_read_in_patch_parameters(self): + #Tests that the patch specific parsing is done correctly + list_of_directories = ReadInputFunctions.list_directories() + list_of_parameter_files = ReadInputFunctions.find_and_store_xml_files(list_of_directories, "parameter") + patch_dict = ReadInputModuleSpecific.read_in_patch_parameters(list_of_parameter_files) + self.assertIn('default', patch_dict, f"default not found in the dictionary") + self.assertIsInstance(patch_dict["default"], list) + self.assertIn("series", patch_dict, f"series not found in the dictionary") + self.assertIsInstance(patch_dict["series"], list) + self.assertIn("population", patch_dict, f"population not found in the dictionary") + self.assertIsInstance(patch_dict["population"], list) + self.assertIn("layer", patch_dict, f"layer not found in the dictionary") + self.assertIsInstance(patch_dict["layer"], list) + self.assertIn("action", patch_dict, f"action not found in the dictionary") + self.assertIsInstance(patch_dict["action"], list) + self.assertIn("component", patch_dict, f"component not found in the dictionary") + self.assertIsInstance(patch_dict["component"], list) + # FUTURE IMPLEMENTATION: Potts test + +if __name__ == "__main__": + unittest.main() diff --git a/tests/unit/test_xml_object.py b/tests/unit/test_xml_object.py new file mode 100644 index 0000000..5f0e0ea --- /dev/null +++ b/tests/unit/test_xml_object.py @@ -0,0 +1,131 @@ +import unittest +import sys +import os + +current_script_path = os.path.dirname(os.path.abspath(__file__)) +relative_path_to_XMLObject = os.path.normpath(os.path.join(current_script_path, '../../src/')) +sys.path.append(relative_path_to_XMLObject) +from xml_object import XMLObject + +class TestObjectCreation(unittest.TestCase): + #Tests the xml_object initializaiton and associated functions + + def setUp_nochildren_noparent(self): + #Creates an object with no children or parents. + self.population_simple = XMLObject(tag = 'population', attribute_dict = {'id':'C', 'init':'100', 'class':'cancer'}) + def setUp_children(self): + #Creates an object with children and parents. + """ + populations + |-population + |-population parameter + |-population process + |-population process + """ + self.tree_with_children = XMLObject(tag = 'populations') + self.tree_with_children.children.append(XMLObject(tag = 'population', attribute_dict = {'id':'C', 'init':'100', 'class':'cancer'})) + self.tree_with_children.children[0].children.append(XMLObject(tag = 'population.parameter', attribute_dict = {'id':'DIVISION_POTENTIAL', 'value':'3'})) + self.tree_with_children.children[0].children.append(XMLObject(tag = 'population.process', attribute_dict = {'id':'METABOLISM', 'version':'complex'})) + self.tree_with_children.children[0].children.append(XMLObject(tag = 'population.process', attribute_dict = {'id':'SIGNALING', 'version':'complex'}) ) + + def test_to_convert_to_element_tree_simple(self): + #tests the creation of childless tree + self.setUp_nochildren_noparent() + root_element = XMLObject.convert_to_element_tree(self.population_simple) + self.assertEqual(root_element.tag, 'population') + self.assertEqual(root_element.attrib, {'id':'C', 'init':'100', 'class':'cancer'}) + XMLObject.save_XML(self.population_simple, "simpleTest.xml") + + def test_object_save(self): + #tests that the object can be saved as an .xml file + path = os.path.join(os.getcwd(), "saveTest.xml") + if os.path.isfile(path): + os.remove(path) + self.setUp_nochildren_noparent() + XMLObject.save_XML(self.population_simple, path) + self.assertTrue(os.path.isfile(path)) + os.remove(path) + + def test_to_convert_to_element_tree_children(self): + #tests that the object can be turned into a ET from .xml level manipulation + self.setUp_children() + root_element = XMLObject.convert_to_element_tree(self.tree_with_children) + + self.assertEqual(root_element.tag, 'populations') + self.assertEqual(root_element[0].tag, 'population') + self.assertEqual(root_element[0].attrib, {'id':'C', 'init':'100', 'class':'cancer'}) + + children_elements = list(root_element[0]) + + self.assertEqual(children_elements[0].tag, 'population.parameter') + self.assertEqual(children_elements[0].attrib, {'id':'DIVISION_POTENTIAL', 'value':'3'}) + + self.assertEqual(children_elements[1].tag, 'population.process') + self.assertEqual(children_elements[1].attrib, {'id':'METABOLISM', 'version':'complex'}) + + self.assertEqual(children_elements[2].tag, 'population.process') + self.assertEqual(children_elements[2].attrib, {'id':'SIGNALING', 'version':'complex'}) + + XMLObject.save_XML(self.tree_with_children, "children_test.xml") + + def test_edit_attribute(self): + #tests that attributes can be edited + self.setUp_nochildren_noparent() + XMLObject.edit_attribute(self.population_simple,"id", "barry") + self.assertEqual(self.population_simple.attribute_dict["id"], "barry") + + def test_reset_attribute(self): + #tests that attributes can be reset + self.setUp_nochildren_noparent() + XMLObject.edit_attribute(self.population_simple,"id", "barry") + self.population_simple.reset_attribute() + self.assertEqual(self.population_simple.attribute_dict["id"], "C") + + def test_edit_tag(self): + #tests that tags can be edited + self.setUp_nochildren_noparent() + XMLObject.edit_tag(self.population_simple, "barry") + self.assertEqual(self.population_simple.tag, "barry") + def test_reset_tag(self): + #tests that tags can be reset + self.setUp_nochildren_noparent() + XMLObject.edit_tag(self.population_simple, "barry") + self.population_simple.reset_tag() + self.assertEqual(self.population_simple.tag, "population") + def test_reset(self): + #tests that both attributes and tags can be reset at once + self.setUp_nochildren_noparent() + XMLObject.edit_attribute(self.population_simple,"id", "barry") + XMLObject.edit_tag(self.population_simple, "barry") + self.population_simple.reset() + self.assertEqual(self.population_simple.attribute_dict["id"], "C") + self.assertEqual(self.population_simple.tag, "population") + def test_find_node_with_tag(self): + #tests that a node can be identified using a tag + self.setUp_children() + node = XMLObject.find_node_with_tag(self.tree_with_children, "population.parameter") + root_element = XMLObject.convert_to_element_tree(self.tree_with_children) + children_elements = list(root_element[0]) + self.assertEqual(children_elements[0].tag, node.tag) + self.assertEqual(children_elements[0].get("id"), node.attribute_dict["id"]) + + def test_find_node_with_id_value(self): + #tests that a node can be identified using an id + self.setUp_children() + node = XMLObject.find_node_with_id_value(self.tree_with_children, "METABOLISM") + root_element = XMLObject.convert_to_element_tree(self.tree_with_children) + children_elements = list(root_element[0]) + self.assertEqual(children_elements[1].tag, node.tag) + self.assertEqual(children_elements[1].get("id"), node.attribute_dict["id"]) + #Potential future methods: + # def test_merge_with_id_value(self): + # self.setUp_children() + # self.setUp_nochildren_noparent() + + # def test_merge_with_tag(self): + # self.setUp_children() + # self.setUp_nochildren_noparent() + + +if __name__ == "__main__": + unittest.main() From 4ae29b25aa3061d39c38686e8bb75ba42277d5c9 Mon Sep 17 00:00:00 2001 From: Olaf Corning Date: Tue, 19 Dec 2023 23:48:20 -0800 Subject: [PATCH 6/8] add init files --- src/__init__.py | 5 ++++- tests/unit/__init__.py | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/__init__.py b/src/__init__.py index 22433fd..187eca8 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1 +1,4 @@ -from src.ObjectToXML import xml_object \ No newline at end of file +from src.xml_object import XMLObject +from src.read_input_xml import ReadInputFunctions, ReadInputModuleSpecific +from src.compile_xml import ReadOutputFunctions +from src.gui import Window \ No newline at end of file diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py index b392963..7c6720c 100644 --- a/tests/unit/__init__.py +++ b/tests/unit/__init__.py @@ -1 +1,2 @@ -from unit.test_ObjectToXML import TestObjectCreation \ No newline at end of file +from unit.test_ObjectToXML import TestObjectCreation +from unit.test_XMLToObject import TestXMLToObject \ No newline at end of file From 4312f4a68f2096b9438a58cc866c6a0c280ee19e Mon Sep 17 00:00:00 2001 From: Olaf Corning Date: Tue, 2 Jan 2024 12:25:21 -0800 Subject: [PATCH 7/8] Completed read-in. Gui missing significant functionality -- should be remade with PyQt Designer or another GUI designer. Functions required for creating final set up .xml located in xml_object.py and compile_xml.py --- src/EditXML.py | 53 ----- src/GUI.py | 403 ++++++++++++++++++++++++++++++++---- src/ObjectToXML.py | 33 --- src/XMLObject.py | 33 --- src/old/edit_xml.py | 0 src/old/gui.py | 77 +++++++ src/old/parameter_object.py | 40 ++++ 7 files changed, 476 insertions(+), 163 deletions(-) delete mode 100644 src/EditXML.py delete mode 100644 src/ObjectToXML.py delete mode 100644 src/XMLObject.py create mode 100644 src/old/edit_xml.py create mode 100644 src/old/gui.py create mode 100644 src/old/parameter_object.py diff --git a/src/EditXML.py b/src/EditXML.py deleted file mode 100644 index 2d0a249..0000000 --- a/src/EditXML.py +++ /dev/null @@ -1,53 +0,0 @@ -import tkinter as tk -import xml.etree.ElementTree as ET -import sys -import os - -current_script_path = os.path.dirname(os.path.abspath(__file__)) -relative_path_to_XMLObject = os.path.normpath(os.path.join(current_script_path, '../../src/')) -sys.path.append(relative_path_to_XMLObject) -from XMLObject import XMLObject - - -def list_directories(): - directoryList =[] - directoryTuples = os.walk(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) - for x in directoryTuples: - directoryList.append(x[0]) - return directoryList - -def find_and_store_parameter_files_as_XMLObject(directories, keyword): - xml_files = {} - for directory in directories: - for filename in os.listdir(directory): - if filename.endswith(".xml") and keyword in filename: - file_path = os.path.join(directory, filename) - key = "_".join([os.path.basename(directory.rstrip('/')), os.path.splitext(filename)[0]])#drops extension - xml_files[key] = load_xml(file_path) - return xml_files - -def load_xml(file_name): - tree = ET.parse(file_name) - root_element = tree.getroot() - return element_to_xml_object(root_element) - -def element_to_xml_object(element, parent=None): - xml_object = XMLObject( - name=element.tag, - attribute_dict=element.attrib, - parent=parent - ) - for child_element in element: - child_xml_object = element_to_xml_object(child_element, parent=xml_object) - xml_object.children.append(child_xml_object) - return xml_object - - -# list = [ - -# ] - -# def create_new_population(): -# raise error NotImplemented - -# def modify_population(): \ No newline at end of file diff --git a/src/GUI.py b/src/GUI.py index 8666310..62ef8f4 100644 --- a/src/GUI.py +++ b/src/GUI.py @@ -1,50 +1,365 @@ +#This is the file the package is run from. It is still being implemented. It currently interacts read_input_xml. +#In future versions it will save user inputs as dictionary of xml_objects which will then be compiled using compile_xml.py +#Future versions will also work with Potts + import sys import os -import tkinter as tk -import datetime -import EditXML -import XMLObject -def initiate_GUI (xml_files): - root = tk.Tk() - root.title("XML Editor") - - # Create a dropdown menu - xml_dropdown = tk.StringVar() - xml_dropdown.set("Select an XML file") - - menu = tk.OptionMenu(root, xml_dropdown, *xml_files.keys(), command=lambda _: select_xml_data(xml_dropdown.get(), xml_files)) - menu.pack() - - # Create a frame to hold the XML data - global xml_frame - xml_frame = tk.Frame(root) - xml_frame.pack() - - # List to hold XML edit widgets for cleanup - global xml_edit_widgets - xml_edit_widgets = [] - - root.mainloop() -def select_xml_data(selected_key, XML_trees): - xml_tree = XML_trees[selected_key] +import xml.etree.ElementTree as ET + +from PyQt5.QtCore import pyqtSignal, Qt +from PyQt5.QtWidgets import ( + QApplication, + QTabWidget, + QVBoxLayout, + QHBoxLayout, + QFormLayout, + QWidget, + QMainWindow, + QLabel, + QComboBox, + QPushButton, + QLineEdit, + QGroupBox, + QToolBar, + QMenu, + QToolButton, + QAction, + QMessageBox +) + +current_script_path = os.path.dirname(os.path.abspath(__file__)) +relative_path_to_XMLObject = os.path.normpath(os.path.join(current_script_path, '../../src/')) +sys.path.append(relative_path_to_XMLObject) +from xml_object import XMLObject +from read_input_xml import ReadInputModuleSpecific +from read_input_xml import ReadInputFunctions + +class Window(QMainWindow): + #Storage of input xml + xml_files_dictionary = {} + selected_xml_parameter_file = {} + parsed_parameter_dictionary = {} - # Clear previous widgets - for widget in xml_edit_widgets: - widget.destroy() - xml_edit_widgets.clear() - display_XMLObject(xml_tree.getroot()) + #Stores user inputs for population id so that + population_id_list = [] + layer_id_list = [] -def display_XMLObject(XMLObject): - #NOT IMPLEMENTED - print("Displaying XMLObject:") - print(XMLObject) + #Used to store layout of population tab between additions of new populations + population_tab_layout = None + def __init__(self): + super(Window, self).__init__() + self.initGeneral() + self.initUI() -if __name__ == "__main__": + def initGeneral(self): + directories = ReadInputFunctions.list_directories() + self.xml_files_dictionary = ReadInputFunctions.find_and_store_xml_files(directories, "parameter") - root_directory = 'arcade-xml-generator' - subdirectory = 'xml_files' - directories = EditXML.list_directories() - xml_files = EditXML.find_and_store_parameter_files_as_XMLObject(directories, "parameter") - print(directories) - print(xml_files) - initiate_GUI(xml_files) \ No newline at end of file + def initUI(self): + #This initial UI requests which parameter .xml file you wish to use + self.setWindowTitle('ARCADE Parameter Setup') + central_widget = QWidget(self) + self.setCentralWidget(central_widget) + + self.layout = QVBoxLayout(central_widget) + + # Dropdown menu + self.label = QLabel('Please select which ARCADE module you wish to run:') + self.layout.addWidget(self.label) + xml_file_names_list = list(self.xml_files_dictionary.keys()) + xml_file_names_list.remove("parameter") + self.dropdown = QComboBox(self) + self.dropdown.addItems(xml_file_names_list) + self.layout.addWidget(self.dropdown) + + # Switch button + self.switch_button = QPushButton('Confirm', self) + self.switch_button.clicked.connect(self.select_parameters_screen) + self.layout.addWidget(self.switch_button) + + def select_parameters_screen(self): + # This is the second screen. It displays options based on the selected parameter .xml file + # Save the selected value + self.selected_xml_parameter_file = self.dropdown.currentText() + self.parsed_parameter_dictionary = ReadInputModuleSpecific.read_in_module(ReadInputModuleSpecific, self.selected_xml_parameter_file, self.xml_files_dictionary) + # Clear the current layout + self.clear_layout() + + # Resize + self.resize(1470, 1010) + + # Create the tab widget + ## Possibly should have if statement for patch/potts. Separate tab_ui functions + widget = QWidget() + layout = QVBoxLayout() + tabs = QTabWidget() + tabs.addTab(self.general_tab_ui(), "General") + tabs.addTab(self.series_tab_ui(), "Series") + tabs.addTab(self.module_specific_tab_ui(), "Module Specific") + tabs.addTab(self.population_tab_ui(), "Populations") + tabs.addTab(self.layer_tab_ui(), "Layers") + tabs.addTab(self.action_tab_ui(), "Actions") + tabs.addTab(self.components_tab_ui(), "Components") + layout.addWidget(tabs) + widget.setLayout(layout) + self.setCentralWidget(widget) + self.setupToolbar() + + def clear_layout(self): + # Clear the existing layout by removing all widgets + # Used before switching screens and displaying a new UI + for i in reversed(range(self.layout.count())): + self.layout.itemAt(i).widget().setParent(None) + def setupToolbar(self): + # While the toolbar displays. The functionality is not implemented + # Create a toolbar + toolbar = QToolBar(self) + self.addToolBar(toolbar) + + # Create "Run" button with a dropdown menu + run_button = QToolButton(self) + run_button.setText('Run') + run_menu = QMenu(self) + run_button.setMenu(run_menu) + toolbar.addWidget(run_button) + + # Create "Commands" button with a dropdown menu + commands_button = QToolButton(self) + commands_button.setText('Commands') + commands_menu = QMenu(self) + commands_button.setMenu(commands_menu) + toolbar.addWidget(commands_button) + + # Add actions to the menus + run_action = QAction('Run Action', self) + run_action.triggered.connect(self.run_command) + run_menu.addAction(run_action) + + commands_action = QAction('Commands Action', self) + commands_action.triggered.connect(self.show_commands) + commands_menu.addAction(commands_action) + + def run_command(self): + # NOT IMPLEMENTED + return + + def show_commands(self): + # NOT IMPLEMENTED + commands_text = "Example List of available commands:\n\n1. Command A\n2. Command B\n3. Command C" + QMessageBox.information(self, 'Available Commands', commands_text) + + def general_tab_ui(self): + # Creates a tab for accessing general parameter values + #Set up Container + general_tab = QWidget() + layout = QVBoxLayout() + + #Display parameters from xml + for element in self.parsed_parameter_dictionary["default"]: + nested_layout = QHBoxLayout() # Create a new instance for each iteration + for attribute_name, current_value in element.items(): + if attribute_name == "id" or attribute_name == "description": + label = QLabel(f'{attribute_name}: {current_value}') + nested_layout.addWidget(label) + else: + label = QLabel(f'{attribute_name}: ') + nested_layout.addWidget(label) + + text_field = QLineEdit() + text_field.setText(current_value) + nested_layout.addWidget(text_field) + + layout.addLayout(nested_layout) + + #display everything + general_tab.setLayout(layout) + return general_tab + def series_tab_ui(self): + # Creates a tab for accessing series parameter values + series_tab = QWidget() + layout = QVBoxLayout() + + + #Display parameters from xml + for element in self.parsed_parameter_dictionary["series"]: + nested_layout = QHBoxLayout() # Create a new instance for each iteration + for attribute_name, current_value in element.items(): + if attribute_name == "id" or attribute_name == "description": + label = QLabel(f'{attribute_name}: {current_value}') + nested_layout.addWidget(label) + else: + label = QLabel(f'{attribute_name}: ') + nested_layout.addWidget(label) + + text_field = QLineEdit() + text_field.setText(current_value) + nested_layout.addWidget(text_field) + + layout.addLayout(nested_layout) + + series_tab.setLayout(layout) + return series_tab + def module_specific_tab_ui(self): + # Creates a tab for accessing module specific (i.e., patch vs potts) parameter values + module_specific_tab = QWidget() + layout = QVBoxLayout() + #Display parameters from xml + for element in self.parsed_parameter_dictionary["patch"]: + nested_layout = QHBoxLayout() # Create a new instance for each iteration + for attribute_name, current_value in element.items(): + if attribute_name == "id" or attribute_name == "description": + label = QLabel(f'{attribute_name}: {current_value}') + nested_layout.addWidget(label) + else: + label = QLabel(f'{attribute_name}: ') + nested_layout.addWidget(label) + + text_field = QLineEdit() + text_field.setText(current_value) + nested_layout.addWidget(text_field) + + layout.addLayout(nested_layout) + + module_specific_tab.setLayout(layout) + return module_specific_tab + def population_tab_ui(self): + # Creates a tab for accessing population parameter values + # Can add a new population to see additional sub-population parameters + population_tab = QWidget() + layout = QVBoxLayout() + + nested_layout = QHBoxLayout() + nested_layout.addWidget(QLabel('population: ')) + nested_layout.addWidget(QLabel("id")) + self.population_id_input = QLineEdit(self) + nested_layout.addWidget(self.population_id_input) + nested_layout.addWidget(QLineEdit()) + nested_layout.addWidget(QLabel("init")) + nested_layout.addWidget(QLineEdit()) + nested_layout.addWidget(QLabel("class")) + nested_layout.addWidget(QLineEdit()) + nested_layout.addWidget(QLabel("Valid classes include cancer_stem and tissue")) + layout.addLayout(nested_layout) + self.add_row_button = QPushButton('Add population', self) + self.add_row_button.clicked.connect(self.add_new_population) + layout.addWidget(self.add_row_button) + #population_id_list + population_tab.setLayout(layout) + self.population_tab_layout = layout + return population_tab + def add_new_population(self): + # Displays population parameters once a population is created + # Save the given population IDs for reference by components + population_id = self.population_id_input.text().strip() + + # Create a QGroupBox with the given group box + group_box = QGroupBox(population_id) + group_layout = QVBoxLayout() + # Display parameters from xml + for element in self.parsed_parameter_dictionary["population"]: + nested_layout = QHBoxLayout() # Create a new instance for each iteration + for attribute_name, current_value in element.items(): + if attribute_name == "id" or attribute_name == "description" or attribute_name == "module" or attribute_name == "process": + form_layout = QFormLayout() + label = QLabel(f'{attribute_name}: {current_value}') + form_layout.addWidget(label) + nested_layout.addLayout(form_layout) + else: + form_layout = QFormLayout() + label = QLabel(f'{attribute_name}: ') + form_layout.addWidget(label) + text_field = QLineEdit() + text_field.setText(current_value) + form_layout.addRow(label, text_field) + nested_layout.addLayout(form_layout) + group_layout.addLayout(nested_layout) + + # Set the main layout for the QGroupBox + group_box.setLayout(group_layout) + + # Append the group name to the class variable + Window.population_id_list.append(population_id) + print(f'Group Name List: {Window.population_id_list}') + self.population_tab_layout.addWidget(group_box) + + def layer_tab_ui(self): + # Creates a tab for accessing layer parameter values + layer_tab = QWidget() + layout = QVBoxLayout() + #Display parameters from xml + for element in self.parsed_parameter_dictionary["layer"]: + nested_layout = QHBoxLayout() # Create a new instance for each iteration + for attribute_name, current_value in element.items(): + if attribute_name == "id" or attribute_name == "description": + label = QLabel(f'{attribute_name}: {current_value}') + nested_layout.addWidget(label) + else: + label = QLabel(f'{attribute_name}: ') + nested_layout.addWidget(label) + + text_field = QLineEdit() + text_field.setText(current_value) + nested_layout.addWidget(text_field) + + layout.addLayout(nested_layout) + + layer_tab.setLayout(layout) + return layer_tab + def action_tab_ui(self): + # Creates a tab for accessing action parameter values + action_tab = QWidget() + layout = QVBoxLayout() + #Display parameters from xml + for element in self.parsed_parameter_dictionary["action"]: + nested_layout = QHBoxLayout() # Create a new instance for each iteration + for attribute_name, current_value in element.items(): + if attribute_name == "id" or attribute_name == "description": + label = QLabel(f'{attribute_name}: {current_value}') + nested_layout.addWidget(label) + else: + label = QLabel(f'{attribute_name}: ') + nested_layout.addWidget(label) + + text_field = QLineEdit() + text_field.setText(current_value) + nested_layout.addWidget(text_field) + + layout.addLayout(nested_layout) + + action_tab.setLayout(layout) + return action_tab + def components_tab_ui(self): + # Creates a tab for accessing component parameter values + components_tab = QWidget() + layout = QVBoxLayout() + #Display parameters from xml + for element in self.parsed_parameter_dictionary["component"]: + nested_layout = QHBoxLayout() # Create a new instance for each iteration + for attribute_name, current_value in element.items(): + if attribute_name == "id" or attribute_name == "description": + label = QLabel(f'{attribute_name}: {current_value}') + nested_layout.addWidget(label) + else: + label = QLabel(f'{attribute_name}: ') + nested_layout.addWidget(label) + + text_field = QLineEdit() + text_field.setText(current_value) + nested_layout.addWidget(text_field) + + layout.addLayout(nested_layout) + + components_tab.setLayout(layout) + return components_tab + + +if __name__ == "__main__": + + app = QApplication(sys.argv) + window = Window() + window.show() + sys.exit(app.exec_()) + + + + diff --git a/src/ObjectToXML.py b/src/ObjectToXML.py deleted file mode 100644 index e507c09..0000000 --- a/src/ObjectToXML.py +++ /dev/null @@ -1,33 +0,0 @@ -import xml.etree.ElementTree as ET -from dataclasses import dataclass, field -from typing import List - -@dataclass(frozen=True, order=True) -class xml_object: - name: str - self_closing: bool = False #True if < tag /> - attribute_dict: dict = field(default_factory=dict) - children: List['xml_object'] = field(default_factory=list) - parent: 'xml_object' = None # Added parent attribute - - - def __post_init__(self): - # Set parent attribute for each child - for child in self.children: - child.parent = self - - def convert_to_element_tree(self): - element = ET.Element(self.name, self.attribute_dict) - for child in self.children: - child_element = child.convert_to_element_tree() - element.append(child_element) - return element - def save_XML(self, file_name): - tree = ET.ElementTree(self.convert_to_element_tree()) - print(type(tree)) - ET.indent(tree, space="\t", level=0) - tree.write(file_name, encoding="utf-8") - - - - diff --git a/src/XMLObject.py b/src/XMLObject.py deleted file mode 100644 index 9ccc6e9..0000000 --- a/src/XMLObject.py +++ /dev/null @@ -1,33 +0,0 @@ -import xml.etree.ElementTree as ET -from dataclasses import dataclass, field -from typing import List - -@dataclass(frozen=True, order=True) -class XMLObject: - name: str - attribute_dict: dict = field(default_factory=dict) - children: List['XMLObject'] = field(default_factory=list) - parent: 'XMLObject' = None # Added parent attribute - - - def __post_init__(self): - # Set parent attribute for each child - for child in self.children: - child.parent = self - - - def convert_to_element_tree(self): - element = ET.Element(self.name, self.attribute_dict) - for child in self.children: - child_element = child.convert_to_element_tree() - element.append(child_element) - return element - def save_XML(self, file_name): - tree = ET.ElementTree(self.convert_to_element_tree()) - print(type(tree)) - ET.indent(tree, space="\t", level=0) - tree.write(file_name, encoding="utf-8") - - - - diff --git a/src/old/edit_xml.py b/src/old/edit_xml.py new file mode 100644 index 0000000..e69de29 diff --git a/src/old/gui.py b/src/old/gui.py new file mode 100644 index 0000000..77a0f4a --- /dev/null +++ b/src/old/gui.py @@ -0,0 +1,77 @@ +import sys +import os +import tkinter as tk +from tkinter import ttk +import datetime +import EditXML +import XMLObject + + +def initiate_GUI(xml_files): + root = tk.Tk() + root.title("XML Editor") + root.geometry("400x300") + #Create a separator + separator = ttk.Separator(root, orient='vertical') + separator.place(relx=0.47, rely=0, relwidth=0.2, relheight=1) + + # Create a frame to hold the XML data + xml_frame = tk.Frame(root) + xml_frame.pack() + + # List to hold XML edit widgets for cleanup + xml_edit_widgets = [] + + # Additional menu with Exit and Save buttons + menu_bar = tk.Menu(root) + root.config(menu=menu_bar) + + file_menu = tk.Menu(menu_bar, tearoff=0) + menu_bar.add_cascade(label="File", menu=file_menu) + + file_menu.add_command(label="Save", command=lambda: save_action()) + file_menu.add_separator() + file_menu.add_command(label="Command Options", command=lambda: command_action(root)) + + # Create a dropdown menu + xml_dropdown = tk.StringVar() + xml_dropdown.set("Select an XML file") + + option_menu = tk.OptionMenu(root, xml_dropdown, *xml_files.keys(), command=lambda selected_key: select_xml_data(selected_key, xml_files, xml_frame)) + option_menu.place(relx=0.5, rely=0.1, relheight=0.10, relwidth=0.4) + + root.mainloop() +def save_action(): + print("Save action executed") + +def command_action(root): + #root.destroy() + print("Checkboxes for commands") + +def select_xml_data(selected_key, XML_trees, xml_frame): + xml_object = XML_trees[selected_key] + display_XMLObject(xml_object, xml_frame) + +def display_XMLObject(xml_object, frame, indent=0): + # Create a button for the current XmlObject + button_text = f"{xml_object.tag} ({len(xml_object.children)})" # Example button text + button = tk.Button(frame, text=button_text, command=lambda: on_button_click(xml_object)) + + # Adjust the row and column parameters + button.grid(row=indent, column=2, sticky="w", padx=indent * 20) # Column is set to 2 + + # Recursively display children + for i, child in enumerate(xml_object.children): + display_XMLObject(child, frame, indent + 1) + +def on_button_click(xml_object): + # Example function to handle button click + print(f"Button clicked for {xml_object.tag}") + +if __name__ == "__main__": + root_directory = 'arcade-xml-generator' + subdirectory = 'xml_files' + directories = EditXML.list_directories() + xml_object = None + xml_files = EditXML.find_and_store_parameter_files_as_XMLObject(directories, "parameter") + initiate_GUI(xml_files) \ No newline at end of file diff --git a/src/old/parameter_object.py b/src/old/parameter_object.py new file mode 100644 index 0000000..45da817 --- /dev/null +++ b/src/old/parameter_object.py @@ -0,0 +1,40 @@ + +import xml.etree.ElementTree as ET +from dataclasses import dataclass, field +from typing import List + +@dataclass(frozen=True, order=True) +class parameter_object: + wrapper_register_or_given_id: str = None + class_wrapper: str = None + subcategory: str = None #e.g. migration module + modified = False + tag: str + attribute_dict: dict = field(default_factory=dict) + description: {} = None #found in some parameter attributes + + +@dataclass(frozen=True, order=True) +class parameters: + set: parameter_object + series: [parameter_object] + model_type: str #no options given in xml + model_type_parameter: [parameter_object] = None #needs wrapper based on file name + + agents: parameter_object(tag = "agents", attribute_dict=None) + agents: parameter_object(tag = "populations", attribute_dict=None) + population: parameter_object #no options given in xml + population_parameters: [parameter_object] + + layers: [parameter_object] #Needs environment, layers, and ID wrapper + actions: [parameter_object] + components: [parameter_object] + + set.name = + +# AUTOADDS EQUIVALENT: +# def __init__(self, name: str, unit_price: float, quantity_on_hand: int = 0): +# self.name = name +# self.unit_price = unit_price +# self.quantity_on_hand = quantity_on_hand + From 7c879739c5840417387072b2639324653af1a4a0 Mon Sep 17 00:00:00 2001 From: Olaf Corning Date: Tue, 2 Jan 2024 12:26:07 -0800 Subject: [PATCH 8/8] Added tests --- tests/unit/test_ObjectToXML.py | 81 ---------------------------------- tests/unit/test_XMLToObject.py | 32 -------------- 2 files changed, 113 deletions(-) delete mode 100644 tests/unit/test_ObjectToXML.py delete mode 100644 tests/unit/test_XMLToObject.py diff --git a/tests/unit/test_ObjectToXML.py b/tests/unit/test_ObjectToXML.py deleted file mode 100644 index 3af8d62..0000000 --- a/tests/unit/test_ObjectToXML.py +++ /dev/null @@ -1,81 +0,0 @@ -import unittest -import sys -import os - -current_script_path = os.path.dirname(os.path.abspath(__file__)) -relative_path_to_xml_object = os.path.normpath(os.path.join(current_script_path, '../../src/')) -sys.path.append(relative_path_to_xml_object) -from ObjectToXML import xml_object - -output =""" - - - - - - - - - - - - - - -""" - -class TestObjectCreation(unittest.TestCase): - """Test the class xml_object creation method""" - - def setUp_nochildren_noparent(self): - self.population_simple = xml_object(name = 'population', attribute_dict = {'id':'C', 'init':'100', 'class':'cancer'}, self_closing = False) - def setUp_children(self): - """ - populations - |-population - |-population parameter - |-population process - |-population process - """ - self.tree_with_children = xml_object(name = 'populations', self_closing = False) - self.tree_with_children.children.append(xml_object(name = 'population', attribute_dict = {'id':'C', 'init':'100', 'class':'cancer'}, self_closing = False)) - self.tree_with_children.children[0].children.append(xml_object(name = 'population.parameter', attribute_dict = {'id':'DIVISION_POTENTIAL', 'value':'3'}, self_closing = True)) - self.tree_with_children.children[0].children.append(xml_object(name = 'population.process', attribute_dict = {'id':'METABOLISM', 'version':'complex'}, self_closing = True)) - self.tree_with_children.children[0].children.append(xml_object(name = 'population.process', attribute_dict = {'id':'SIGNALING', 'version':'complex'}, self_closing = True) ) - - print(self) - print("olaf") - print(self) - - def test_to_convert_to_element_tree_simple(self): - self.setUp_nochildren_noparent() - root_element = xml_object.convert_to_element_tree(self.population_simple) - self.assertEqual(root_element.tag, 'population') - self.assertEqual(root_element.attrib, {'id':'C', 'init':'100', 'class':'cancer'}) - xml_object.save_XML(self.population_simple, "simpleTest.xml") - - def test_to_convert_to_element_tree_children(self): - self.setUp_children() - root_element = xml_object.convert_to_element_tree(self.tree_with_children) - - self.assertEqual(root_element.tag, 'populations') - self.assertEqual(root_element[0].tag, 'population') - self.assertEqual(root_element[0].attrib, {'id':'C', 'init':'100', 'class':'cancer'}) - - children_elements = list(root_element[0]) - - self.assertEqual(children_elements[0].tag, 'population.parameter') - self.assertEqual(children_elements[0].attrib, {'id':'DIVISION_POTENTIAL', 'value':'3'}) - - self.assertEqual(children_elements[1].tag, 'population.process') - self.assertEqual(children_elements[1].attrib, {'id':'METABOLISM', 'version':'complex'}) - - self.assertEqual(children_elements[2].tag, 'population.process') - self.assertEqual(children_elements[2].attrib, {'id':'SIGNALING', 'version':'complex'}) - - xml_object.save_XML(self.tree_with_children, "childrenTest.xml") - - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/unit/test_XMLToObject.py b/tests/unit/test_XMLToObject.py deleted file mode 100644 index 3719093..0000000 --- a/tests/unit/test_XMLToObject.py +++ /dev/null @@ -1,32 +0,0 @@ -import sys -import os -import unittest -import xml.etree.ElementTree as ET - -current_script_path = os.path.dirname(os.path.abspath(__file__)) -relative_path_to_XMLObject = os.path.normpath(os.path.join(current_script_path, '../../src/')) -sys.path.append(relative_path_to_XMLObject) -from XMLObject import XMLObject -import EditXML - -def compare_xml_files(file1, file2): - tree1 = ET.parse(file1) - tree2 = ET.parse(file2) - - # Get the root elements - root1 = tree1.getroot() - root2 = tree2.getroot() - - # Compare the XML content - return ET.tostring(root1) == ET.tostring(root2) -class TestXMLToObject(unittest.TestCase): - def test_convert_xml_to_object(self): - tree_with_children = EditXML.load_xml('childrenTest.xml') - XMLObject.save_XML(tree_with_children, 'childrenTest_converted.xml') - self.assertTrue(compare_xml_files('childrenTest.xml', 'childrenTest_converted.xml')) - - #def check_load_xml_selection(self): - # intialize GUI - # Check if XML selection == loaded XML -if __name__ == "__main__": - unittest.main()