From 17590518dedd7f78667476f0537e71440b7b4340 Mon Sep 17 00:00:00 2001 From: Rasmus Jakobsson Date: Mon, 21 Oct 2024 10:02:38 +0200 Subject: [PATCH 1/3] Fixed such that the parser doesnt get stuck when models has loops --- bpmnconstraints/parser/bpmn_parser.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/bpmnconstraints/parser/bpmn_parser.py b/bpmnconstraints/parser/bpmn_parser.py index b6b6982..971e7ca 100644 --- a/bpmnconstraints/parser/bpmn_parser.py +++ b/bpmnconstraints/parser/bpmn_parser.py @@ -55,6 +55,7 @@ def run(self): self.__flatten_model() else: self.model = XmlModel(self.bpmn_model) + self.__parse() self.__mark_gateway_elements() if self.transitivity: @@ -115,22 +116,34 @@ def __get_parsed_cfo_by_bpmn_element(self, elem): if parsed_cfo.get("id") == elem_id: return parsed_cfo - def __find_transitive_closure(self, cfo, transitivity): + def __find_transitive_closure(self, cfo, transitivity, visited): + # Check if the current node has already been visited to prevent a loop + if cfo.get("id") in visited: + print(f"Already visited {cfo.get('id')}, skipping to avoid a loop.") + return + + # Mark the current node as visited + visited.add(cfo.get("id")) + if cfo: for successor in cfo.get("successor"): successor_id = successor.get("id") + successor = self.__get_cfo_by_id(successor_id) if successor: if "is in gateway" not in successor: transitivity.append(successor) for successor in cfo.get("successor"): successor_cfo = self.__get_cfo_by_id(successor.get("id")) - self.__find_transitive_closure(successor_cfo, transitivity) + self.__find_transitive_closure(successor_cfo, transitivity, visited) def __add_transitivity(self): for cfo in self.sequence: transitivity = [] - self.__find_transitive_closure(cfo, transitivity) + + visited = set() # Initialize the visited set for cycle detection + self.__find_transitive_closure(cfo, transitivity, visited) + if transitivity: cfo.update({"transitivity": transitivity}) From 8de0980ed0a956edde6d1f438fe6db94625c50aa Mon Sep 17 00:00:00 2001 From: Rasmus Jakobsson Date: Mon, 21 Oct 2024 10:10:57 +0200 Subject: [PATCH 2/3] Fixed linting --- bpmnconstraints/parser/bpmn_parser.py | 59 ++++++++++++++++++--------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/bpmnconstraints/parser/bpmn_parser.py b/bpmnconstraints/parser/bpmn_parser.py index 971e7ca..24a4157 100644 --- a/bpmnconstraints/parser/bpmn_parser.py +++ b/bpmnconstraints/parser/bpmn_parser.py @@ -25,7 +25,8 @@ def __create_model(self, bpmn, is_file): if is_file: try: file_extension = Path(bpmn).suffix - if not file_extension or file_extension not in [".json", ".xml"]: + if not file_extension or file_extension not in [ + ".json", ".xml"]: return None elif file_extension == ".xml": return ElementTree.parse(bpmn).getroot() @@ -72,18 +73,23 @@ def validate_splitting_and_joining_gateway_cases(self): Otherwise, the parser interprets the gateways as start/end events instead of the activities. """ - item_indices = {item["name"]: index for index, item in enumerate(self.sequence)} + item_indices = { + item["name"]: index for index, + item in enumerate( + self.sequence)} for cfo in self.sequence: if cfo["is start"] and (cfo["type"] in DISCARDED_START_GATEWAYS): cfo["is start"] = False for successor in cfo["successor"]: - self.sequence[item_indices[successor["name"]]]["is start"] = True + self.sequence[item_indices[successor["name"]] + ]["is start"] = True if cfo["is end"] and ( cfo["name"] in GATEWAY_NAMES or cfo["type"] == "exclusivegateway" ): cfo["is end"] = False for predecessor in cfo["predecessor"]: - self.sequence[item_indices[predecessor["name"]]]["is end"] = True + self.sequence[item_indices[predecessor["name"]] + ]["is end"] = True def __mark_gateway_elements(self): for cfo in self.sequence: @@ -100,7 +106,8 @@ def __mark_gateway_elements(self): "type" ) in ALLOWED_GATEWAYS and predecessor_cfo.get("joining"): continue - if cfo.get("type") in ALLOWED_GATEWAYS and cfo.get("joining"): + if cfo.get("type") in ALLOWED_GATEWAYS and cfo.get( + "joining"): continue if "is in gateway" in predecessor_cfo: cfo.update({"is in gateway": True}) @@ -119,7 +126,8 @@ def __get_parsed_cfo_by_bpmn_element(self, elem): def __find_transitive_closure(self, cfo, transitivity, visited): # Check if the current node has already been visited to prevent a loop if cfo.get("id") in visited: - print(f"Already visited {cfo.get('id')}, skipping to avoid a loop.") + print( + f"Already visited {cfo.get('id')}, skipping to avoid a loop.") return # Mark the current node as visited @@ -134,8 +142,10 @@ def __find_transitive_closure(self, cfo, transitivity, visited): if "is in gateway" not in successor: transitivity.append(successor) for successor in cfo.get("successor"): - successor_cfo = self.__get_cfo_by_id(successor.get("id")) - self.__find_transitive_closure(successor_cfo, transitivity, visited) + successor_cfo = self.__get_cfo_by_id( + successor.get("id")) + self.__find_transitive_closure( + successor_cfo, transitivity, visited) def __add_transitivity(self): for cfo in self.sequence: @@ -199,7 +209,8 @@ def __valid_cfo_element(self, elem): return True if self.__is_element_gateway(elem): return True - if self.__is_element_start_event(elem) and self.__valid_start_name(elem): + if self.__is_element_start_event( + elem) and self.__valid_start_name(elem): return True if self.__is_element_end_event(elem) and self.__valid_end_name(elem): return True @@ -249,7 +260,8 @@ def __get_successors(self, elem): else self.model.get_id(connection) ) elem = self.__get_element_by_id(connection_id) - if self.model.get_element_type(elem) in ALLOWED_CONNECTING_OBJECTS: + if self.model.get_element_type( + elem) in ALLOWED_CONNECTING_OBJECTS: connection = self.model.get_outgoing_connection(elem) if connection: elem = ( @@ -295,7 +307,8 @@ def __format_list(self, elems, gateway=False): } try: - cfo.update({"splitting": len(self.__get_successors(elem)) >= 2}) + cfo.update( + {"splitting": len(self.__get_successors(elem)) >= 2}) except Exception: pass formatted.append(cfo) @@ -312,29 +325,36 @@ def __get_element_by_id(self, connection_id): raise Exception(f"Could not find element with ID {connection_id}") def __get_activity_type(self, elem): - return ACTIVITY_MAPPING.get(self.model.get_element_type(elem), "Activity") + return ACTIVITY_MAPPING.get( + self.model.get_element_type(elem), "Activity") def __get_gateway_type(self, elem): - return GATEWAY_MAPPING.get(self.model.get_element_type(elem), "Gateway") + return GATEWAY_MAPPING.get( + self.model.get_element_type(elem), "Gateway") def __get_end_type(self, elem): - return END_EVENT_MAPPING.get(self.model.get_element_type(elem), "EndEvent") + return END_EVENT_MAPPING.get( + self.model.get_element_type(elem), "EndEvent") def __get_label(self, elem): try: if self.__is_element_activity(elem): try: - return self.sanitizer.sanitize_label(self.model.get_name(elem)) + return self.sanitizer.sanitize_label( + self.model.get_name(elem)) except KeyError: return self.__get_activity_type(elem) if self.__is_element_gateway(elem): try: - return self.sanitizer.sanitize_label(self.model.get_name(elem)) + return self.sanitizer.sanitize_label( + self.model.get_name(elem)) except KeyError: return self.__get_gateway_type(elem) - if self.__is_element_start_event(elem) or self.__is_element_end_event(elem): + if self.__is_element_start_event( + elem) or self.__is_element_end_event(elem): try: - return self.sanitizer.sanitize_label(self.model.get_name(elem)) + return self.sanitizer.sanitize_label( + self.model.get_name(elem)) except KeyError: return self.model.get_element_type(elem) except KeyError: @@ -368,7 +388,8 @@ def __is_predecessor_start_event(self, predecessors): def __is_successor_end_event(self, successors): for successor in successors: if successor: - if self.model.get_element_type(successor) in ALLOWED_END_EVENTS: + if self.model.get_element_type( + successor) in ALLOWED_END_EVENTS: if self.__valid_end_name(successor): return False return True From 440804afe717c686db7097565cfb70fec5acb796 Mon Sep 17 00:00:00 2001 From: Rasmus Jakobsson Date: Mon, 21 Oct 2024 10:24:05 +0200 Subject: [PATCH 3/3] fixed lint with black --- bpmnconstraints/parser/bpmn_parser.py | 59 +++++++++------------------ 1 file changed, 20 insertions(+), 39 deletions(-) diff --git a/bpmnconstraints/parser/bpmn_parser.py b/bpmnconstraints/parser/bpmn_parser.py index 24a4157..8d6a51a 100644 --- a/bpmnconstraints/parser/bpmn_parser.py +++ b/bpmnconstraints/parser/bpmn_parser.py @@ -25,8 +25,7 @@ def __create_model(self, bpmn, is_file): if is_file: try: file_extension = Path(bpmn).suffix - if not file_extension or file_extension not in [ - ".json", ".xml"]: + if not file_extension or file_extension not in [".json", ".xml"]: return None elif file_extension == ".xml": return ElementTree.parse(bpmn).getroot() @@ -73,23 +72,18 @@ def validate_splitting_and_joining_gateway_cases(self): Otherwise, the parser interprets the gateways as start/end events instead of the activities. """ - item_indices = { - item["name"]: index for index, - item in enumerate( - self.sequence)} + item_indices = {item["name"]: index for index, item in enumerate(self.sequence)} for cfo in self.sequence: if cfo["is start"] and (cfo["type"] in DISCARDED_START_GATEWAYS): cfo["is start"] = False for successor in cfo["successor"]: - self.sequence[item_indices[successor["name"]] - ]["is start"] = True + self.sequence[item_indices[successor["name"]]]["is start"] = True if cfo["is end"] and ( cfo["name"] in GATEWAY_NAMES or cfo["type"] == "exclusivegateway" ): cfo["is end"] = False for predecessor in cfo["predecessor"]: - self.sequence[item_indices[predecessor["name"]] - ]["is end"] = True + self.sequence[item_indices[predecessor["name"]]]["is end"] = True def __mark_gateway_elements(self): for cfo in self.sequence: @@ -106,8 +100,7 @@ def __mark_gateway_elements(self): "type" ) in ALLOWED_GATEWAYS and predecessor_cfo.get("joining"): continue - if cfo.get("type") in ALLOWED_GATEWAYS and cfo.get( - "joining"): + if cfo.get("type") in ALLOWED_GATEWAYS and cfo.get("joining"): continue if "is in gateway" in predecessor_cfo: cfo.update({"is in gateway": True}) @@ -126,8 +119,7 @@ def __get_parsed_cfo_by_bpmn_element(self, elem): def __find_transitive_closure(self, cfo, transitivity, visited): # Check if the current node has already been visited to prevent a loop if cfo.get("id") in visited: - print( - f"Already visited {cfo.get('id')}, skipping to avoid a loop.") + print(f"Already visited {cfo.get('id')}, skipping to avoid a loop.") return # Mark the current node as visited @@ -142,10 +134,10 @@ def __find_transitive_closure(self, cfo, transitivity, visited): if "is in gateway" not in successor: transitivity.append(successor) for successor in cfo.get("successor"): - successor_cfo = self.__get_cfo_by_id( - successor.get("id")) + successor_cfo = self.__get_cfo_by_id(successor.get("id")) self.__find_transitive_closure( - successor_cfo, transitivity, visited) + successor_cfo, transitivity, visited + ) def __add_transitivity(self): for cfo in self.sequence: @@ -209,8 +201,7 @@ def __valid_cfo_element(self, elem): return True if self.__is_element_gateway(elem): return True - if self.__is_element_start_event( - elem) and self.__valid_start_name(elem): + if self.__is_element_start_event(elem) and self.__valid_start_name(elem): return True if self.__is_element_end_event(elem) and self.__valid_end_name(elem): return True @@ -260,8 +251,7 @@ def __get_successors(self, elem): else self.model.get_id(connection) ) elem = self.__get_element_by_id(connection_id) - if self.model.get_element_type( - elem) in ALLOWED_CONNECTING_OBJECTS: + if self.model.get_element_type(elem) in ALLOWED_CONNECTING_OBJECTS: connection = self.model.get_outgoing_connection(elem) if connection: elem = ( @@ -307,8 +297,7 @@ def __format_list(self, elems, gateway=False): } try: - cfo.update( - {"splitting": len(self.__get_successors(elem)) >= 2}) + cfo.update({"splitting": len(self.__get_successors(elem)) >= 2}) except Exception: pass formatted.append(cfo) @@ -325,36 +314,29 @@ def __get_element_by_id(self, connection_id): raise Exception(f"Could not find element with ID {connection_id}") def __get_activity_type(self, elem): - return ACTIVITY_MAPPING.get( - self.model.get_element_type(elem), "Activity") + return ACTIVITY_MAPPING.get(self.model.get_element_type(elem), "Activity") def __get_gateway_type(self, elem): - return GATEWAY_MAPPING.get( - self.model.get_element_type(elem), "Gateway") + return GATEWAY_MAPPING.get(self.model.get_element_type(elem), "Gateway") def __get_end_type(self, elem): - return END_EVENT_MAPPING.get( - self.model.get_element_type(elem), "EndEvent") + return END_EVENT_MAPPING.get(self.model.get_element_type(elem), "EndEvent") def __get_label(self, elem): try: if self.__is_element_activity(elem): try: - return self.sanitizer.sanitize_label( - self.model.get_name(elem)) + return self.sanitizer.sanitize_label(self.model.get_name(elem)) except KeyError: return self.__get_activity_type(elem) if self.__is_element_gateway(elem): try: - return self.sanitizer.sanitize_label( - self.model.get_name(elem)) + return self.sanitizer.sanitize_label(self.model.get_name(elem)) except KeyError: return self.__get_gateway_type(elem) - if self.__is_element_start_event( - elem) or self.__is_element_end_event(elem): + if self.__is_element_start_event(elem) or self.__is_element_end_event(elem): try: - return self.sanitizer.sanitize_label( - self.model.get_name(elem)) + return self.sanitizer.sanitize_label(self.model.get_name(elem)) except KeyError: return self.model.get_element_type(elem) except KeyError: @@ -388,8 +370,7 @@ def __is_predecessor_start_event(self, predecessors): def __is_successor_end_event(self, successors): for successor in successors: if successor: - if self.model.get_element_type( - successor) in ALLOWED_END_EVENTS: + if self.model.get_element_type(successor) in ALLOWED_END_EVENTS: if self.__valid_end_name(successor): return False return True