Skip to content

Commit

Permalink
Merge branch 'ft-1494-enriched-bpmn-object' into 'integration'
Browse files Browse the repository at this point in the history
PM4Py-1494 Enriched BPMN object (Lomidze thesis)

See merge request process-mining/pm4py/pm4py-core!566
  • Loading branch information
fit-sebastiaan-van-zelst committed Jan 6, 2022
2 parents da6a478 + d494bf1 commit fefcd45
Show file tree
Hide file tree
Showing 15 changed files with 4,820 additions and 103 deletions.
2 changes: 1 addition & 1 deletion pm4py/objects/bpmn/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from pm4py.objects.bpmn import obj, exporter, layout, util
from pm4py.objects.bpmn import obj, exporter, layout, semantics, util
import pkgutil

if pkgutil.find_loader("lxml"):
Expand Down
18 changes: 12 additions & 6 deletions pm4py/objects/bpmn/exporter/variants/etree.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,21 +88,27 @@ def get_xml_string(bpmn_graph, parameters=None):
"parallelMultiple": parallelMultiple})
elif isinstance(node, BPMN.EndEvent):
task = ET.SubElement(process, "endEvent", {"id": node.get_id(), "name": node.get_name()})
elif isinstance(node, BPMN.OtherEvent):
task = ET.SubElement(process, node.type, {"id": node.get_id(), "name": node.get_name()})
elif isinstance(node, BPMN.IntermediateCatchEvent):
task = ET.SubElement(process, "intermediateCatchEvent", {"id": node.get_id(), "name": node.get_name()})
elif isinstance(node, BPMN.IntermediateThrowEvent):
task = ET.SubElement(process, "intermediateThrowEvent", {"id": node.get_id(), "name": node.get_name()})
elif isinstance(node, BPMN.BoundaryEvent):
task = ET.SubElement(process, "boundaryEvent", {"id": node.get_id(), "name": node.get_name()})
elif isinstance(node, BPMN.Task):
task = ET.SubElement(process, node.type, {"id": node.get_id(), "name": node.get_name()})
task = ET.SubElement(process, "task", {"id": node.get_id(), "name": node.get_name()})
elif isinstance(node, BPMN.SubProcess):
task = ET.SubElement(process, "subProcess", {"id": node.get_id(), "name": node.get_name()})
elif isinstance(node, BPMN.ExclusiveGateway):
task = ET.SubElement(process, "exclusiveGateway",
{"id": node.get_id(), "gatewayDirection": node.get_gatewayDirection(),
{"id": node.get_id(), "gatewayDirection": node.get_gateway_direction().value.lower(),
"name": ""})
elif isinstance(node, BPMN.ParallelGateway):
task = ET.SubElement(process, "parallelGateway",
{"id": node.get_id(), "gatewayDirection": node.get_gatewayDirection(),
{"id": node.get_id(), "gatewayDirection": node.get_gateway_direction().value.lower(),
"name": ""})
elif isinstance(node, BPMN.InclusiveGateway):
task = ET.SubElement(process, "inclusiveGateway",
{"id": node.get_id(), "gatewayDirection": node.get_gatewayDirection(),
{"id": node.get_id(), "gatewayDirection": node.get_gateway_direction().value.lower(),
"name": ""})
else:
raise Exception("Unexpected node type.")
Expand Down
176 changes: 136 additions & 40 deletions pm4py/objects/bpmn/importer/variants/lxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,110 +14,206 @@ def parse_element(bpmn_graph, counts, curr_el, parents, incoming_dict, outgoing_
Parses a BPMN element from the XML file
"""
tag = curr_el.tag.lower()
if tag.endswith("process"):
if tag.endswith("subprocess"): # subprocess invocation
name = curr_el.get("name").replace("\r", "").replace("\n", "") if "name" in curr_el.attrib else ""
subprocess = BPMN.SubProcess(id=curr_el.get("id"), name=name, process=process, depth=rec_depth)
bpmn_graph.add_node(subprocess)
node = subprocess
process = curr_el.get("id")
elif tag.endswith("shape"):
nodes_dict[process] = node
elif tag.endswith("process"): # process of the current subtree
process = curr_el.get("id")
bpmn_graph.set_process_id(process)
elif tag.endswith("shape"): # shape of a node, contains x,y,width,height information
bpmn_element = curr_el.get("bpmnElement")
elif tag.endswith("task"):
elif tag.endswith("task"): # simple task object
id = curr_el.get("id")
name = curr_el.get("name").replace("\r", "").replace("\n", "")
this_type = str(curr_el.tag)
this_type = this_type[this_type.index("}") + 1:]
task = BPMN.Task(name=name, type=this_type)
name = curr_el.get("name").replace("\r", "").replace("\n", "") if "name" in curr_el.attrib else ""
#this_type = str(curr_el.tag)
#this_type = this_type[this_type.index("}") + 1:]
task = BPMN.Task(id=id, name=name, process=process)
bpmn_graph.add_node(task)
node = task
nodes_dict[id] = node
elif tag.endswith("startevent"):
elif tag.endswith("startevent"): # start node starting the (sub)process
id = curr_el.get("id")
name = curr_el.get("name").replace("\r", " ").replace("\n", " ") if "name" in curr_el else str(uuid.uuid4())
start_event = BPMN.StartEvent(name=name)
name = curr_el.get("name").replace("\r", " ").replace("\n", " ") if "name" in curr_el.attrib else ""
event_definitions = [child.tag.lower().replace("eventdefinition","") for child in curr_el if child.tag.lower().endswith("eventdefinition")]
if len(event_definitions) > 0:
event_type = event_definitions[0]
if event_type.endswith("message"):
start_event = BPMN.MessageStartEvent(id=curr_el.get("id"), name=name, process=process)
else: # TODO: expand functionality, support more start event types
start_event = BPMN.NormalStartEvent(id=curr_el.get("id"), name=name, process=process)
else:
start_event = BPMN.NormalStartEvent(id=curr_el.get("id"), name=name, process=process)
bpmn_graph.add_node(start_event)
node = start_event
nodes_dict[id] = node
elif tag.endswith("endevent"):
elif tag.endswith("endevent"): # end node ending the (sub)process
id = curr_el.get("id")
name = curr_el.get("name").replace("\r", " ").replace("\n", " ") if "name" in curr_el else str(uuid.uuid4())
end_event = BPMN.EndEvent(name=name)
name = curr_el.get("name").replace("\r", " ").replace("\n", " ") if "name" in curr_el.attrib else ""
event_definitions = [child.tag.lower().replace("eventdefinition","") for child in curr_el if child.tag.lower().endswith("eventdefinition")]
if len(event_definitions) > 0:
event_type = event_definitions[0]
if event_type.endswith("message"):
end_event = BPMN.MessageEndEvent(id=curr_el.get("id"), name=name, process=process)
elif event_type.endswith("terminate"):
end_event = BPMN.TerminateEndEvent(id=curr_el.get("id"), name=name, process=process)
elif event_type.endswith("error"):
end_event = BPMN.ErrorEndEvent(id=curr_el.get("id"), name=name, process=process)
elif event_type.endswith("cancel"):
end_event = BPMN.CancelEndEvent(id=curr_el.get("id"), name=name, process=process)
else: # TODO: expand functionality, support more start event types
end_event = BPMN.NormalEndEvent(id=curr_el.get("id"), name=name, process=process)
else:
end_event = BPMN.NormalEndEvent(id=curr_el.get("id"), name=name, process=process)
bpmn_graph.add_node(end_event)
node = end_event
nodes_dict[id] = node
elif tag.endswith("event"):
elif tag.endswith("intermediatecatchevent"): # intermediate event that happens (externally) and can be catched
id = curr_el.get("id")
name = curr_el.get("name").replace("\r", " ").replace("\n", " ") if "name" in curr_el.attrib else ""
event_definitions = [child.tag.lower().replace("eventdefinition","") for child in curr_el if child.tag.lower().endswith("eventdefinition")]
if len(event_definitions) > 0:
event_type = event_definitions[0]
if event_type.endswith("message"):
intermediate_catch_event = BPMN.MessageIntermediateCatchEvent(id=curr_el.get("id"), name=name, process=process)
elif event_type.endswith("error"):
intermediate_catch_event = BPMN.ErrorIntermediateCatchEvent(id=curr_el.get("id"), name=name, process=process)
elif event_type.endswith("cancel"):
intermediate_catch_event = BPMN.CancelIntermediateCatchEvent(id=curr_el.get("id"), name=name, process=process)
else:
intermediate_catch_event = BPMN.IntermediateCatchEvent(id=curr_el.get("id"), name=name, process=process)
else:
intermediate_catch_event = BPMN.IntermediateCatchEvent(id=curr_el.get("id"), name=name, process=process)
bpmn_graph.add_node(intermediate_catch_event)
node = intermediate_catch_event
nodes_dict[id] = node
elif tag.endswith("intermediatethrowevent"): # intermediate event that is activated through the (sub)process
id = curr_el.get("id")
name = curr_el.get("name").replace("\r", " ").replace("\n", " ") if "name" in curr_el else str(uuid.uuid4())
this_type = str(curr_el.tag)
this_type = this_type[this_type.index("}") + 1:]
other_event = BPMN.OtherEvent(name=name, type=this_type)
bpmn_graph.add_node(other_event)
node = other_event
name = curr_el.get("name").replace("\r", " ").replace("\n", " ") if "name" in curr_el.attrib else ""
event_definitions = [child.tag.lower().replace("eventdefinition","") for child in curr_el if child.tag.lower().endswith("eventdefinition")]
if len(event_definitions) > 0:
event_type = event_definitions[0]
if event_type.endswith("message"):
intermediate_throw_event = BPMN.MessageIntermediateThrowEvent(id=curr_el.get("id"), name=name, process=process)
else:
intermediate_throw_event = BPMN.NormalIntermediateThrowEvent(id=curr_el.get("id"), name=name, process=process)
else:
intermediate_throw_event = BPMN.NormalIntermediateThrowEvent(id=curr_el.get("id"), name=name, process=process)
bpmn_graph.add_node(intermediate_throw_event)
node = intermediate_throw_event
nodes_dict[id] = node
elif tag.endswith("edge"):
elif tag.endswith("boundaryevent"):
id = curr_el.get("id")
ref_activity = curr_el.get("attachedToRef")
name = curr_el.get("name").replace("\r", " ").replace("\n", " ") if "name" in curr_el.attrib else ""
event_definitions = [child.tag.lower().replace("eventdefinition","") for child in curr_el if child.tag.lower().endswith("eventdefinition")]
if len(event_definitions) > 0:
event_type = event_definitions[0]
if event_type.endswith("message"):
boundary_event = BPMN.MessageBoundaryEvent(id=curr_el.get("id"), name=name, process=process, activity=ref_activity)
elif event_type.endswith("error"):
boundary_event = BPMN.ErrorBoundaryEvent(id=curr_el.get("id"), name=name, process=process, activity=ref_activity)
elif event_type.endswith("cancel"):
boundary_event = BPMN.CancelBoundaryEvent(id=curr_el.get("id"), name=name, process=process, activity=ref_activity)
else:
boundary_event = BPMN.BoundaryEvent(id=curr_el.get("id"), name=name, process=process, activity=ref_activity)
else:
boundary_event = BPMN.BoundaryEvent(id=curr_el.get("id"), name=name, process=process, activity=ref_activity)
bpmn_graph.add_node(boundary_event)
node = boundary_event
nodes_dict[id] = node
elif tag.endswith("edge"): # related to the x, y information of an arc
bpmnElement = curr_el.get("bpmnElement")
flow = bpmnElement
elif tag.endswith("exclusivegateway"):
id = curr_el.get("id")
name = curr_el.get("name").replace("\r", "").replace("\n", "") if "name" in curr_el else str(uuid.uuid4())
exclusive_gateway = BPMN.ExclusiveGateway(name=name)
name = curr_el.get("name").replace("\r", "").replace("\n", "") if "name" in curr_el.attrib else ""
try:
direction = BPMN.Gateway.Direction[curr_el.get("gatewayDirection").upper()]
exclusive_gateway = BPMN.ExclusiveGateway(id=curr_el.get("id"), name=name, gateway_direction=direction, process=process)
except:
exclusive_gateway = BPMN.ExclusiveGateway(id=curr_el.get("id"), name=name, gateway_direction=BPMN.Gateway.Direction.UNSPECIFIED, process=process)
bpmn_graph.add_node(exclusive_gateway)
node = exclusive_gateway
nodes_dict[id] = node
elif tag.endswith("parallelgateway"):
id = curr_el.get("id")
name = curr_el.get("name").replace("\r", "").replace("\n", "") if "name" in curr_el else str(uuid.uuid4())
parallel_gateway = BPMN.ParallelGateway(name=name)
name = curr_el.get("name").replace("\r", "").replace("\n", "") if "name" in curr_el.attrib else ""
try:
direction = BPMN.Gateway.Direction[curr_el.get("gatewayDirection").upper()]
parallel_gateway = BPMN.ParallelGateway(id=curr_el.get("id"), name=name, gateway_direction=direction, process=process)
except:
parallel_gateway = BPMN.ParallelGateway(id=curr_el.get("id"), name=name, gateway_direction=BPMN.Gateway.Direction.UNSPECIFIED, process=process)
bpmn_graph.add_node(parallel_gateway)
node = parallel_gateway
nodes_dict[id] = node
elif tag.endswith("inclusivegateway"):
id = curr_el.get("id")
name = curr_el.get("name").replace("\r", "").replace("\n", "") if "name" in curr_el else str(uuid.uuid4())
inclusive_gateway = BPMN.InclusiveGateway(name=name)
name = curr_el.get("name").replace("\r", "").replace("\n", "") if "name" in curr_el.attrib else ""
try:
direction = BPMN.Gateway.Direction[curr_el.get("gatewayDirection").upper()]
inclusive_gateway = BPMN.InclusiveGateway(id=curr_el.get("id"), name=name, gateway_direction=direction, process=process)
except:
inclusive_gateway = BPMN.InclusiveGateway(id=curr_el.get("id"), name=name, gateway_direction=BPMN.Gateway.Direction.UNSPECIFIED, process=process)
bpmn_graph.add_node(inclusive_gateway)
node = inclusive_gateway
nodes_dict[id] = node
elif tag.endswith("incoming"):
elif tag.endswith("incoming"): # incoming flow of a node
if node is not None:
incoming_dict[curr_el.text.strip()] = node
elif tag.endswith("outgoing"):
elif tag.endswith("outgoing"): # outgoing flow of a node
if node is not None:
outgoing_dict[curr_el.text.strip()] = node
elif tag.endswith("sequenceflow"):
elif tag.endswith("sequenceflow"): # normal sequence flow between two nodes
seq_flow_id = curr_el.get("id")
source_ref = curr_el.get("sourceRef")
target_ref = curr_el.get("targetRef")
# fix 28/04/2021: do not assume anymore to read the nodes before the edges
incoming_dict[seq_flow_id] = target_ref
outgoing_dict[seq_flow_id] = source_ref
elif tag.endswith("waypoint"):
incoming_dict[seq_flow_id] = (target_ref, process)
outgoing_dict[seq_flow_id] = (source_ref, process)
#elif tag.endswith("messageflow"): # TODO: implement different treatment of message flows
# seq_flow_id = curr_el.get("id")
# source_ref = curr_el.get("sourceRef")
# target_ref = curr_el.get("targetRef")
# incoming_dict[seq_flow_id] = target_ref
# outgoing_dict[seq_flow_id] = source_ref
elif tag.endswith("waypoint"): # contains information of x, y values of an edge
if flow is not None:
x = float(curr_el.get("x"))
y = float(curr_el.get("y"))
if not flow in flow_info:
flow_info[flow] = []
flow_info[flow].append((x, y))
elif tag.endswith("label"):
elif tag.endswith("label"): # label of a node, mostly at the end of a shape object
bpmn_element = None
elif tag.endswith("bounds"):
elif tag.endswith("bounds"): # contains information of width, height, x, y of a node
if bpmn_element is not None:
x = float(curr_el.get("x"))
y = float(curr_el.get("y"))
width = float(curr_el.get("width"))
height = float(curr_el.get("height"))
nodes_bounds[bpmn_element] = {"x": x, "y": y, "width": width, "height": height}

for child in curr_el:
bpmn_graph = parse_element(bpmn_graph, counts, child, list(parents) + [child], incoming_dict, outgoing_dict,
nodes_dict, nodes_bounds, flow_info, process=process, node=node,
bpmn_element=bpmn_element,
flow=flow, rec_depth=rec_depth + 1)
# afterprocessing when the xml tree has been recursively parsed already
if rec_depth == 0:
# bpmn_graph.set_process_id(process)
for seq_flow_id in incoming_dict:
if incoming_dict[seq_flow_id] in nodes_dict:
incoming_dict[seq_flow_id] = nodes_dict[incoming_dict[seq_flow_id]]
if incoming_dict[seq_flow_id][0] in nodes_dict:
incoming_dict[(seq_flow_id)] = (nodes_dict[incoming_dict[seq_flow_id][0]], incoming_dict[seq_flow_id][1])
for seq_flow_id in outgoing_dict:
if outgoing_dict[seq_flow_id] in nodes_dict:
outgoing_dict[seq_flow_id] = nodes_dict[outgoing_dict[seq_flow_id]]
if outgoing_dict[seq_flow_id][0] in nodes_dict:
outgoing_dict[seq_flow_id] = (nodes_dict[outgoing_dict[seq_flow_id][0]], incoming_dict[seq_flow_id][1])
for flow_id in flow_info:
if flow_id in outgoing_dict and flow_id in incoming_dict:
flow = BPMN.Flow(outgoing_dict[flow_id], incoming_dict[flow_id])
flow = BPMN.SequenceFlow(outgoing_dict[flow_id][0], incoming_dict[flow_id][0], id=flow_id, name="", process=outgoing_dict[flow_id][1])
bpmn_graph.add_flow(flow)
flow.del_waypoints()
for waypoint in flow_info[flow_id]:
Expand Down
Loading

0 comments on commit fefcd45

Please sign in to comment.