├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── MANIFEST.in ├── Pipfile ├── README.md ├── README.rst ├── __init__.py ├── bpmn_python ├── __init__.py ├── bpmn_diagram_exception.py ├── bpmn_diagram_export.py ├── bpmn_diagram_import.py ├── bpmn_diagram_layouter.py ├── bpmn_diagram_metrics.py ├── bpmn_diagram_rep.py ├── bpmn_diagram_visualizer.py ├── bpmn_import_utils.py ├── bpmn_process_csv_export.py ├── bpmn_process_csv_import.py ├── bpmn_python_consts.py ├── diagram_layout_metrics.py ├── graph │ ├── __init__.py │ └── classes │ │ ├── __init__.py │ │ ├── activities │ │ ├── __init__.py │ │ ├── activity_type.py │ │ ├── subprocess_type.py │ │ └── task_type.py │ │ ├── base_element_type.py │ │ ├── condition_expression_type.py │ │ ├── events │ │ ├── __init__.py │ │ ├── catch_event_type.py │ │ ├── end_event_type.py │ │ ├── event_type.py │ │ ├── intermediate_catch_event_type.py │ │ ├── intermediate_throw_event_type.py │ │ ├── start_event_type.py │ │ └── throw_event_type.py │ │ ├── flow_element_type.py │ │ ├── flow_node_type.py │ │ ├── gateways │ │ ├── __init__.py │ │ ├── exclusive_gateway_type.py │ │ ├── gateway_type.py │ │ ├── inclusive_gateway_type.py │ │ └── parallel_gateway_type.py │ │ ├── lane_set_type.py │ │ ├── lane_type.py │ │ ├── message_flow_type.py │ │ ├── participant_type.py │ │ ├── root_element │ │ ├── __init__.py │ │ ├── callable_element_type.py │ │ ├── event_definition_type.py │ │ ├── process_type.py │ │ └── root_element_type.py │ │ └── sequence_flow_type.py └── grid_cell_class.py ├── docs ├── Makefile ├── image │ ├── camunda-complex-example.png │ ├── camunda-simple-example.png │ ├── manual-complex.png │ ├── manual-simple.png │ ├── signavio-complex-example.png │ └── signavio-simple-example.png ├── requirements-docs.txt └── source │ ├── api │ ├── bpmn_diagram_exception.rst │ ├── bpmn_diagram_export.rst │ ├── bpmn_diagram_import.rst │ ├── bpmn_diagram_layouter.rst │ ├── bpmn_diagram_metrics.rst │ ├── bpmn_diagram_rep.rst │ ├── bpmn_diagram_visualizer.rst │ ├── bpmn_process_csv_export.rst │ ├── bpmn_process_csv_import.rst │ └── grid_cell_class.rst │ ├── api_reference.rst │ ├── conf.py │ └── index.rst ├── requirements.txt ├── setup.cfg ├── setup.py ├── tests ├── conftest.py ├── csv_export │ └── csv_export_test.py ├── csv_import │ ├── csv_import_test.py │ └── input │ │ ├── airline-checkin.csv │ │ ├── order-processing.csv │ │ └── pizza-order.csv ├── examples │ ├── csv_export │ │ ├── bank-account-process.bpmn │ │ ├── checkin-process.bpmn │ │ ├── credit-process.bpmn │ │ ├── order-processing.bpmn │ │ ├── pizza-order.bpmn │ │ └── tram-process.bpmn │ ├── metrics │ │ ├── crossing_point_test.bpmn │ │ └── cycles_test.bpmn │ └── xml_import_export │ │ ├── bpmn_editor_simple_example.xml │ │ ├── camunda_complex_example.bpmn │ │ ├── camunda_simple_example.bpmn │ │ ├── default-conditional-flow-example.bpmn │ │ ├── lanes.bpmn │ │ ├── signavio_complex_example.bpmn │ │ └── signavio_simple_example.bpmn ├── layouter │ └── layouter_test.py ├── metrics │ ├── layout_metrics_test.py │ └── test_bpmn_diagram_metrics.py └── xml_import_export │ ├── bpmn_editor_import_test.py │ ├── camunda_complex_test.py │ ├── camunda_simple_test.py │ ├── default_conditional_flow_test.py │ ├── lanes_test.py │ ├── manually_generated_complex_diagram.py │ ├── manually_generated_simple_diagram.py │ ├── signavio_complex_test.py │ └── signavio_simple_test.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | ENV/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | local/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *,cover 48 | .hypothesis/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | 57 | # Sphinx documentation 58 | docs/_build/ 59 | 60 | # PyBuilder 61 | target/ 62 | 63 | #Ipython Notebook 64 | .ipynb_checkpoints 65 | 66 | #Other 67 | tests/xml_import_export/output/** 68 | .idea 69 | tests/output/ 70 | tests/layouter/output/ 71 | tests/csv_export/output/ 72 | tests/xml_import_export/output/ 73 | tests/csv_import/output/ 74 | **/*.sw[po] 75 | Pipfile.lock 76 | _trial_temp 77 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 3 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 4 | 5 | ## [0.0.19] - 2019-01-05 6 | ### Added 7 | - basic changelog with release dates 8 | ### Changed 9 | - setup.py entries to make description in pypi more readable 10 | - setup.py install requires now reads needed modules from requirements.txt 11 | 12 | ## [0.0.18] - 2017-07-14 13 | ## [0.0.17] - 2017-07-14 14 | ## [0.0.16] - 2017-07-13 15 | ## [0.0.15] - 2017-07-07 16 | ## [0.0.14] - 2017-06-28 17 | ## [0.0.12] - 2017-06-23 18 | ## [0.0.11] - 2017-03-28 19 | ## [0.0.10] - 2017-03-28 20 | ## [0.0.9] - 2017-03-28 21 | ## [0.0.8] - 2017-03-28 22 | ## [0.0.7] - 2017-03-28 23 | ## [0.0.6] - 2017-03-28 24 | ## [0.0.5] - 2017-03-28 25 | ## [0.0.4] - 2017-03-28 26 | ## [0.0.3] - 2017-02-20 27 | ## [0.0.2] - 2016-09-18 28 | ## [0.0.1] - 2016-09-08 29 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include README.rst 3 | include LICENSE 4 | include CHANGELOG.md 5 | include requirements.txt 6 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | pytest = "*" 8 | pytest-cov = "*" 9 | tox = "*" 10 | 11 | [packages] 12 | bpmn-python = {editable = true,path = "."} 13 | 14 | [requires] 15 | python_version = "3.7" 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bpmn-python 2 | Project for creating a Python library that allows to import/export BPMN diagram (as an XML file) and provides a simple visualization capabilities 3 | 4 | Project structure 5 | * bpmn_python - main module of project, includes all source code 6 | * tests - unit tests for package 7 | * examples - examples of XML files used in tests 8 | * docs - documentation for package 9 | 10 | 11 | ## Development 12 | 13 | Requirements: [pipenv](https://pipenv.readthedocs.io/en/latest/), Python 3.7. If you do not have Python 3.7 installed, consider using [pyenv](https://github.com/pyenv/pyenv). After setting up, it integrates with pipenv allowing latter to automatically pull correct Python version for use in virtual environment. 14 | 15 | To set up local development environment, clone the repository, enter it and execute: 16 | ```bash 17 | pipenv install --dev -e . 18 | pipenv shell 19 | ``` 20 | 21 | Run tests with HTML coverage report: 22 | ```bash 23 | pytest --cov-report html --cov=bpmn_python 24 | ``` 25 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | bpmn-python 2 | =========== 3 | 4 | Project for creating a Python library that allows to import/export BPMN 5 | diagram (as an XML file) and provides a simple visualization 6 | capabilities 7 | 8 | Project structure \* bpmn\_python - main module of project, includes all 9 | source code \* tests - unit tests for package \* examples - examples of 10 | XML files used in tests \* docs - documentation for package 11 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Package init file 4 | """ 5 | import bpmn_python 6 | -------------------------------------------------------------------------------- /bpmn_python/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Package init file 4 | """ 5 | __all__ = ["bpmn_diagram_export", "bpmn_diagram_import", "bpmn_diagram_layouter", 6 | "bpmn_diagram_exception", "bpmn_diagram_metrics", "bpmn_diagram_visualizer", "bpmn_import_utils", 7 | "bpmn_process_csv_export", "diagram_layout_metrics", "grid_cell_class", "bpmn_diagram_rep"] 8 | -------------------------------------------------------------------------------- /bpmn_python/bpmn_diagram_exception.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Custom exception class for bpmn_python 4 | """ 5 | 6 | 7 | class BpmnPythonError(Exception): 8 | """ 9 | Custom BpmnPythonError exception 10 | """ 11 | def __init__(self, value): 12 | self.value = value 13 | 14 | def __str__(self): 15 | return repr(self.value) 16 | -------------------------------------------------------------------------------- /bpmn_python/bpmn_diagram_metrics.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Package provides set of functions for calculating complexity metrics 4 | of BPMN 2.0 XML models represented by objects of BpmnDiagramGraph class. 5 | """ 6 | 7 | from collections import Counter 8 | 9 | from math import sqrt 10 | 11 | GATEWAY_TYPES = ['inclusiveGateway', 'exclusiveGateway', 'parallelGateway', 'eventBasedGateway', 'complexGateway'] 12 | EVENT_TYPES = ['startEvent', 'endEvent', 'intermediateCatchEvent', 'intermediateThrowEvent'] 13 | 14 | 15 | def get_nodes_count(bpmn_graph, node_type=None): 16 | """ 17 | Gets the count of nodes of the requested type. 18 | If no type is provided, 19 | the count of all nodes in BPMN diagram graph is returned. 20 | 21 | :param bpmn_graph: an instance of BpmnDiagramGraph representing BPMN model. 22 | :param node_type: string with valid BPMN XML tag name 23 | (e.g. 'task', 'sequenceFlow'). 24 | """ 25 | 26 | return len(bpmn_graph.get_nodes(node_type=node_type)) 27 | 28 | 29 | def get_all_gateways(bpmn_graph): 30 | """ 31 | Returns a list with all gateways in diagram 32 | 33 | :param bpmn_graph: an instance of BpmnDiagramGraph representing BPMN model. 34 | :return: a list with all gateways in diagram 35 | """ 36 | gateways = filter(lambda node: node[1]['type'] in GATEWAY_TYPES, bpmn_graph.get_nodes()) 37 | 38 | return gateways 39 | 40 | 41 | def get_gateway_counts(bpmn_graph): 42 | """ 43 | Returns the count of the different types of gateways 44 | in the BPMNDiagramGraph instance. 45 | 46 | :param bpmn_graph: an instance of BpmnDiagramGraph representing BPMN model. 47 | :return: count of the different types of gateways in the BPMNDiagramGraph instance 48 | """ 49 | 50 | return {gateway_type: get_nodes_count(bpmn_graph, node_type=gateway_type) 51 | for gateway_type in GATEWAY_TYPES} 52 | 53 | 54 | def get_events_counts(bpmn_graph): 55 | """ 56 | Returns the count of the different types of event elements 57 | in the BPMNDiagramGraph instance. 58 | 59 | :param bpmn_graph: an instance of BpmnDiagramGraph representing BPMN model. 60 | :return: count of the different types of event elements in the BPMNDiagramGraph instance 61 | """ 62 | 63 | return {event_type: get_nodes_count(bpmn_graph, node_type=event_type) 64 | for event_type in EVENT_TYPES} 65 | 66 | 67 | def get_activities_counts(bpmn_graph): 68 | """ 69 | Returns the count of the different types of activities 70 | in the BPMNDiagramGraph instance. 71 | 72 | :param bpmn_graph: an instance of BpmnDiagramGraph representing BPMN model. 73 | :return: count of the different types of activities in the BPMNDiagramGraph instance 74 | """ 75 | 76 | return { 77 | "task": get_nodes_count(bpmn_graph, 78 | node_type="task"), 79 | "subProcess": get_nodes_count(bpmn_graph, 80 | node_type="subProcess"), 81 | } 82 | 83 | 84 | def all_activities_count(bpmn_graph): 85 | """ 86 | Returns the total count of all activities in the BPMNDiagramGraph instance. 87 | 88 | :param bpmn_graph: an instance of BpmnDiagramGraph representing BPMN model. 89 | :return: total count of the activities in the BPMNDiagramGraph instance 90 | """ 91 | 92 | return sum([ 93 | count for name, count in get_activities_counts(bpmn_graph).items() 94 | ]) 95 | 96 | 97 | def all_gateways_count(bpmn_graph): 98 | """ 99 | Returns the total count of all gateway elements 100 | in the BPMNDiagramGraph instance. 101 | 102 | :param bpmn_graph: an instance of BpmnDiagramGraph representing BPMN model. 103 | :return: total count of the gateway elements in the BPMNDiagramGraph instance 104 | """ 105 | 106 | return sum([ 107 | count for name, count in get_gateway_counts(bpmn_graph).items() 108 | ]) 109 | 110 | 111 | def all_control_flow_elements_count(bpmn_graph): 112 | """ 113 | Returns the total count of all control flow elements 114 | in the BPMNDiagramGraph instance. 115 | 116 | 117 | :param bpmn_graph: an instance of BpmnDiagramGraph representing BPMN model. 118 | :return: total count of the control flow elements in the BPMNDiagramGraph instance 119 | """ 120 | 121 | gateway_counts = get_gateway_counts(bpmn_graph) 122 | events_counts = get_events_counts(bpmn_graph) 123 | control_flow_elements_counts = gateway_counts.copy() 124 | control_flow_elements_counts.update(events_counts) 125 | 126 | return sum([ 127 | count for name, count in control_flow_elements_counts.items() 128 | ]) 129 | 130 | 131 | def all_events_count(bpmn_graph): 132 | """ 133 | Returns the total count of all events elements 134 | in the BPMNDiagramGraph instance. 135 | 136 | :param bpmn_graph: an instance of BpmnDiagramGraph representing BPMN model. 137 | :return: total count of the events elements elements in the BPMNDiagramGraph instance 138 | """ 139 | 140 | return sum([ 141 | count for name, count in get_events_counts(bpmn_graph).items() 142 | ]) 143 | 144 | 145 | def TNSE_metric(bpmn_graph): 146 | """ 147 | Returns the value of the TNSE metric (Total Number of Start Events of the Model) 148 | for the BPMNDiagramGraph instance. 149 | 150 | :param bpmn_graph: an instance of BpmnDiagramGraph representing BPMN model. 151 | """ 152 | 153 | return get_nodes_count(bpmn_graph, node_type='startEvent') 154 | 155 | 156 | def TNIE_metric(bpmn_graph): 157 | """ 158 | Returns the value of the TNIE metric (Total Number of Intermediate Events of the Model) 159 | for the BPMNDiagramGraph instance. 160 | 161 | :param bpmn_graph: an instance of BpmnDiagramGraph representing BPMN model. 162 | """ 163 | 164 | return get_nodes_count(bpmn_graph, node_type='intermediateCatchEvent') + \ 165 | get_nodes_count(bpmn_graph, node_type='intermediateThrowEvent') 166 | 167 | 168 | def TNEE_metric(bpmn_graph): 169 | """ 170 | Returns the value of the TNEE metric (Total Number of End Events of the Model) 171 | for the BPMNDiagramGraph instance. 172 | 173 | :param bpmn_graph: an instance of BpmnDiagramGraph representing BPMN model. 174 | """ 175 | 176 | return get_nodes_count(bpmn_graph, node_type='endEvent') 177 | 178 | 179 | def TNE_metric(bpmn_graph): 180 | """ 181 | Returns the value of the TNE metric (Total Number of Events of the Model) 182 | for the BPMNDiagramGraph instance. 183 | 184 | :param bpmn_graph: an instance of BpmnDiagramGraph representing BPMN model. 185 | """ 186 | 187 | events_counts = get_events_counts(bpmn_graph) 188 | 189 | return sum( 190 | [count for _, count in events_counts.items()] 191 | ) 192 | 193 | 194 | def NOA_metric(bpmn_graph): 195 | """ 196 | Returns the value of the NOA metric (Number of Activities) 197 | for the BPMNDiagramGraph instance. 198 | 199 | :param bpmn_graph: an instance of BpmnDiagramGraph representing BPMN model. 200 | """ 201 | 202 | activities_counts = get_activities_counts(bpmn_graph) 203 | 204 | return activities_counts["task"] + activities_counts["subProcess"] 205 | 206 | 207 | def NOAC_metric(bpmn_graph): 208 | """ 209 | Returns the value of the NOAC metric (Number of Activities and control flow elements) 210 | for the BPMNDiagramGraph instance. 211 | 212 | :param bpmn_graph: an instance of BpmnDiagramGraph representing BPMN model. 213 | """ 214 | 215 | activities_count = all_activities_count(bpmn_graph) 216 | control_flow_count = all_control_flow_elements_count(bpmn_graph) 217 | 218 | return activities_count + control_flow_count 219 | 220 | 221 | def NOAJS_metric(bpmn_graph): 222 | """ 223 | Returns the value of the NOAJS metric (Number of Activities, joins and splits) 224 | for the BPMNDiagramGraph instance. 225 | 226 | :param bpmn_graph: an instance of BpmnDiagramGraph representing BPMN model. 227 | """ 228 | 229 | activities_count = all_activities_count(bpmn_graph) 230 | gateways_count = all_gateways_count(bpmn_graph) 231 | 232 | return activities_count + gateways_count 233 | 234 | 235 | def NumberOfNodes_metric(bpmn_graph): 236 | """ 237 | Returns the value of the Number of Nodes metric 238 | ("Number of activities and routing elements in a model") 239 | for the BPMNDiagramGraph instance. 240 | 241 | :param bpmn_graph: an instance of BpmnDiagramGraph representing BPMN model. 242 | """ 243 | 244 | activities_count = all_activities_count(bpmn_graph) 245 | control_flow_count = all_control_flow_elements_count(bpmn_graph) 246 | 247 | return activities_count + control_flow_count 248 | 249 | 250 | def GatewayHeterogenity_metric(bpmn_graph): 251 | """ 252 | Returns the value of the Gateway Heterogenity metric 253 | ("Number of different types of gateways used in a mode") 254 | for the BPMNDiagramGraph instance. 255 | 256 | :param bpmn_graph: an instance of BpmnDiagramGraph representing BPMN model. 257 | """ 258 | 259 | gateway_counts = get_gateway_counts(bpmn_graph) 260 | present_gateways = [gateway_name 261 | for gateway_name, count in gateway_counts.items() 262 | if count > 0] 263 | 264 | return len(present_gateways) 265 | 266 | 267 | def CoefficientOfNetworkComplexity_metric(bpmn_graph): 268 | """ 269 | Returns the value of the Coefficient of Network Complexity metric 270 | ("Ratio of the total number of arcs in a process model to its total number of nodes.") 271 | for the BPMNDiagramGraph instance. 272 | 273 | :param bpmn_graph: an instance of BpmnDiagramGraph representing BPMN model. 274 | """ 275 | 276 | return float(len(bpmn_graph.get_flows())) / float(len(bpmn_graph.get_nodes())) 277 | 278 | 279 | def AverageGatewayDegree_metric(bpmn_graph): 280 | """ 281 | Returns the value of the Average Gateway Degree metric 282 | ("Average of the number of both incoming and outgoing arcs of the gateway nodes in the process model") 283 | for the BPMNDiagramGraph instance. 284 | 285 | :param bpmn_graph: an instance of BpmnDiagramGraph representing BPMN model. 286 | """ 287 | 288 | gateways_ids = [gateway[0] for gateway in get_all_gateways(bpmn_graph)] 289 | all_nodes_degrees = bpmn_graph.diagram_graph.degree() 290 | gateways_degree_values = [all_nodes_degrees[gateway_id] for gateway_id in gateways_ids] 291 | 292 | return float(sum(gateways_degree_values)) / float(len(gateways_degree_values)) 293 | 294 | 295 | def DurfeeSquare_metric(bpmn_graph): 296 | """ 297 | Returns the value of the Durfee Square metric 298 | ("Durfee Square equals d if there are d types of elements 299 | which occur at least d times in the model (each), 300 | and the other types of elements occur no more than d times (each)") 301 | for the BPMNDiagramGraph instance. 302 | 303 | :param bpmn_graph: an instance of BpmnDiagramGraph representing BPMN model. 304 | """ 305 | 306 | all_types_count = Counter([node[1]['type'] for node in bpmn_graph.get_nodes() if node[1]['type']]) 307 | length = len(all_types_count) 308 | 309 | histogram = [0] * (length + 1) 310 | for _, count in all_types_count.items(): 311 | histogram[min(count, length)] += 1 312 | 313 | sum_ = 0 314 | for i, count in reversed(list(enumerate(histogram))): 315 | sum_ += count 316 | if sum_ >= i: 317 | return i 318 | 319 | return 0 320 | 321 | 322 | def PerfectSquare_metric(bpmn_graph): 323 | """ 324 | Returns the value of the Perfect Square metric 325 | ("Given a set of element types ranked 326 | in decreasing order of the number of their instances, 327 | the PSM is the (unique) largest number 328 | such that the top p types occur(together) 329 | at least p2 times.") 330 | for the BPMNDiagramGraph instance. 331 | 332 | :param bpmn_graph: an instance of BpmnDiagramGraph representing BPMN model. 333 | """ 334 | 335 | all_types_count = Counter([node[1]['type'] for node in bpmn_graph.get_nodes() if node[1]['type']]) 336 | sorted_counts = [count for _, count in all_types_count.most_common()] 337 | 338 | potential_perfect_square = min(len(sorted_counts), int(sqrt(sum(sorted_counts)))) 339 | 340 | for i in range(potential_perfect_square, 0, -1): 341 | if sum(sorted_counts[:potential_perfect_square]) >= potential_perfect_square * potential_perfect_square: 342 | return potential_perfect_square 343 | else: 344 | potential_perfect_square -= 1 345 | 346 | return 0 347 | -------------------------------------------------------------------------------- /bpmn_python/bpmn_diagram_visualizer.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | BPMN diagram visualization methods 4 | """ 5 | import matplotlib.pyplot as plt 6 | import networkx as nx 7 | import pydotplus 8 | import bpmn_python.bpmn_python_consts as consts 9 | 10 | from networkx.drawing.nx_pydot import write_dot 11 | 12 | 13 | def visualize_diagram(bpmn_diagram): 14 | """ 15 | Shows a simple visualization of diagram 16 | 17 | :param bpmn_diagram: an instance of BPMNDiagramGraph class. 18 | """ 19 | g = bpmn_diagram.diagram_graph 20 | pos = bpmn_diagram.get_nodes_positions() 21 | nx.draw_networkx_nodes(g, pos, node_shape='s', node_color='white', 22 | nodelist=bpmn_diagram.get_nodes_id_list_by_type(consts.Consts.task)) 23 | nx.draw_networkx_nodes(g, pos, node_shape='s', node_color='white', 24 | nodelist=bpmn_diagram.get_nodes_id_list_by_type(consts.Consts.subprocess)) 25 | nx.draw_networkx_nodes(g, pos, node_shape='d', node_color='white', 26 | nodelist=bpmn_diagram.get_nodes_id_list_by_type(consts.Consts.complex_gateway)) 27 | nx.draw_networkx_nodes(g, pos, node_shape='o', node_color='white', 28 | nodelist=bpmn_diagram.get_nodes_id_list_by_type(consts.Consts.event_based_gateway)) 29 | nx.draw_networkx_nodes(g, pos, node_shape='d', node_color='white', 30 | nodelist=bpmn_diagram.get_nodes_id_list_by_type(consts.Consts.inclusive_gateway)) 31 | nx.draw_networkx_nodes(g, pos, node_shape='d', node_color='white', 32 | nodelist=bpmn_diagram.get_nodes_id_list_by_type(consts.Consts.exclusive_gateway)) 33 | nx.draw_networkx_nodes(g, pos, node_shape='d', node_color='white', 34 | nodelist=bpmn_diagram.get_nodes_id_list_by_type(consts.Consts.parallel_gateway)) 35 | nx.draw_networkx_nodes(g, pos, node_shape='o', node_color='white', 36 | nodelist=bpmn_diagram.get_nodes_id_list_by_type(consts.Consts.start_event)) 37 | nx.draw_networkx_nodes(g, pos, node_shape='o', node_color='white', 38 | nodelist=bpmn_diagram.get_nodes_id_list_by_type(consts.Consts.intermediate_catch_event)) 39 | nx.draw_networkx_nodes(g, pos, node_shape='o', node_color='white', 40 | nodelist=bpmn_diagram.get_nodes_id_list_by_type(consts.Consts.end_event)) 41 | nx.draw_networkx_nodes(g, pos, node_shape='o', node_color='white', 42 | nodelist=bpmn_diagram.get_nodes_id_list_by_type(consts.Consts.intermediate_throw_event)) 43 | 44 | node_labels = {} 45 | for node in g.nodes(data=True): 46 | node_labels[node[0]] = node[1].get(consts.Consts.node_name) 47 | nx.draw_networkx_labels(g, pos, node_labels) 48 | 49 | nx.draw_networkx_edges(g, pos) 50 | 51 | edge_labels = {} 52 | for edge in g.edges(data=True): 53 | edge_labels[(edge[0], edge[1])] = edge[2].get(consts.Consts.name) 54 | nx.draw_networkx_edge_labels(g, pos, edge_labels) 55 | 56 | plt.show() 57 | 58 | 59 | def bpmn_diagram_to_dot_file(bpmn_diagram, file_name): 60 | """ 61 | Convert diagram graph to dot file 62 | 63 | :param bpmn_diagram: an instance of BPMNDiagramGraph class, 64 | :param file_name: name of generated file. 65 | """ 66 | g = bpmn_diagram.diagram_graph 67 | write_dot(g, file_name + ".dot") 68 | 69 | 70 | def bpmn_diagram_to_png(bpmn_diagram, file_name): 71 | """ 72 | Create a png picture for given diagram 73 | 74 | :param bpmn_diagram: an instance of BPMNDiagramGraph class, 75 | :param file_name: name of generated file. 76 | """ 77 | g = bpmn_diagram.diagram_graph 78 | graph = pydotplus.Dot() 79 | 80 | for node in g.nodes(data=True): 81 | 82 | if node[1].get(consts.Consts.type) == consts.Consts.task: 83 | n = pydotplus.Node(name=node[0], shape="box", style="rounded", label=node[1].get(consts.Consts.node_name)) 84 | elif node[1].get(consts.Consts.type) == consts.Consts.exclusive_gateway: 85 | n = pydotplus.Node(name=node[0], shape="diamond", label=node[1].get(consts.Consts.node_name)) 86 | else: 87 | n = pydotplus.Node(name=node[0], label=node[1].get(consts.Consts.node_name)) 88 | graph.add_node(n) 89 | 90 | for edge in g.edges(data=True): 91 | e = pydotplus.Edge(src=edge[0], dst=edge[1], label=edge[2].get(consts.Consts.name)) 92 | graph.add_edge(e) 93 | 94 | graph.write(file_name + ".png", format='png') 95 | -------------------------------------------------------------------------------- /bpmn_python/bpmn_import_utils.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class including utility method used in diagram importing 4 | """ 5 | 6 | import bpmn_python.bpmn_python_consts as consts 7 | 8 | 9 | class BpmnImportUtils(object): 10 | """ 11 | Class including utility method used in diagram importing 12 | """ 13 | 14 | def __init__(self): 15 | pass 16 | 17 | @staticmethod 18 | def remove_namespace_from_tag_name(tag_name): 19 | """ 20 | Helper function, removes namespace annotation from tag name. 21 | 22 | :param tag_name: string with tag name. 23 | """ 24 | return tag_name.split(':')[-1] 25 | 26 | @staticmethod 27 | def iterate_elements(parent): 28 | """ 29 | Helper function that iterates over child Nodes/Elements of parent Node/Element. 30 | 31 | :param parent: object of Element class, representing parent element. 32 | """ 33 | element = parent.firstChild 34 | while element is not None: 35 | yield element 36 | element = element.nextSibling 37 | 38 | @staticmethod 39 | def generate_nodes_clasification(bpmn_diagram): 40 | """ 41 | Diagram elements classification. Implementation based on article "A Simple Algorithm for Automatic Layout of 42 | BPMN Processes". 43 | Assigns a classification to the diagram element according to specific element parameters. 44 | - Element - every element of the process which is not an edge, 45 | - Start Event - all types of start events, 46 | - End Event - all types of end events, 47 | - Join - an element with more than one incoming edge, 48 | - Split - an element with more than one outgoing edge. 49 | 50 | :param bpmn_diagram: BPMNDiagramGraph class instance representing a BPMN process diagram. 51 | :return: a dictionary of classification labels. Key - node id. Values - a list of labels. 52 | """ 53 | nodes_classification = {} 54 | 55 | classification_element = "Element" 56 | classification_start_event = "Start Event" 57 | classification_end_event = "End Event" 58 | 59 | task_list = bpmn_diagram.get_nodes(consts.Consts.task) 60 | for element in task_list: 61 | classification_labels = [classification_element] 62 | BpmnImportUtils.split_join_classification(element, classification_labels, nodes_classification) 63 | 64 | subprocess_list = bpmn_diagram.get_nodes(consts.Consts.subprocess) 65 | for element in subprocess_list: 66 | classification_labels = [classification_element] 67 | BpmnImportUtils.split_join_classification(element, classification_labels, nodes_classification) 68 | 69 | complex_gateway_list = bpmn_diagram.get_nodes(consts.Consts.complex_gateway) 70 | for element in complex_gateway_list: 71 | classification_labels = [classification_element] 72 | BpmnImportUtils.split_join_classification(element, classification_labels, nodes_classification) 73 | 74 | event_based_gateway_list = bpmn_diagram.get_nodes(consts.Consts.event_based_gateway) 75 | for element in event_based_gateway_list: 76 | classification_labels = [classification_element] 77 | BpmnImportUtils.split_join_classification(element, classification_labels, nodes_classification) 78 | 79 | inclusive_gateway_list = bpmn_diagram.get_nodes(consts.Consts.inclusive_gateway) 80 | for element in inclusive_gateway_list: 81 | classification_labels = [classification_element] 82 | BpmnImportUtils.split_join_classification(element, classification_labels, nodes_classification) 83 | 84 | exclusive_gateway_list = bpmn_diagram.get_nodes(consts.Consts.exclusive_gateway) 85 | for element in exclusive_gateway_list: 86 | classification_labels = [classification_element] 87 | BpmnImportUtils.split_join_classification(element, classification_labels, nodes_classification) 88 | 89 | parallel_gateway_list = bpmn_diagram.get_nodes(consts.Consts.parallel_gateway) 90 | for element in parallel_gateway_list: 91 | classification_labels = [classification_element] 92 | BpmnImportUtils.split_join_classification(element, classification_labels, nodes_classification) 93 | 94 | start_event_list = bpmn_diagram.get_nodes(consts.Consts.start_event) 95 | for element in start_event_list: 96 | classification_labels = [classification_element, classification_start_event] 97 | BpmnImportUtils.split_join_classification(element, classification_labels, nodes_classification) 98 | 99 | intermediate_catch_event_list = bpmn_diagram.get_nodes(consts.Consts.intermediate_catch_event) 100 | for element in intermediate_catch_event_list: 101 | classification_labels = [classification_element] 102 | BpmnImportUtils.split_join_classification(element, classification_labels, nodes_classification) 103 | 104 | end_event_list = bpmn_diagram.get_nodes(consts.Consts.end_event) 105 | for element in end_event_list: 106 | classification_labels = [classification_element, classification_end_event] 107 | BpmnImportUtils.split_join_classification(element, classification_labels, nodes_classification) 108 | 109 | intermediate_throw_event_list = bpmn_diagram.get_nodes(consts.Consts.intermediate_throw_event) 110 | for element in intermediate_throw_event_list: 111 | classification_labels = [classification_element] 112 | BpmnImportUtils.split_join_classification(element, classification_labels, nodes_classification) 113 | 114 | return nodes_classification 115 | 116 | @staticmethod 117 | def split_join_classification(element, classification_labels, nodes_classification): 118 | """ 119 | Add the "Split", "Join" classification, if the element qualifies for. 120 | 121 | :param element: an element from BPMN diagram, 122 | :param classification_labels: list of labels attached to the element, 123 | :param nodes_classification: dictionary of classification labels. Key - node id. Value - a list of labels. 124 | """ 125 | classification_join = "Join" 126 | classification_split = "Split" 127 | if len(element[1][consts.Consts.incoming_flow]) >= 2: 128 | classification_labels.append(classification_join) 129 | if len(element[1][consts.Consts.outgoing_flow]) >= 2: 130 | classification_labels.append(classification_split) 131 | nodes_classification[element[0]] = classification_labels 132 | -------------------------------------------------------------------------------- /bpmn_python/bpmn_python_consts.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Package used to unify the different constant values used in entire project 4 | """ 5 | 6 | 7 | class Consts(object): 8 | """ 9 | Class used to unify the different constant values used in entire project 10 | """ 11 | # BPMN 2.0 element attribute names 12 | id = "id" 13 | name = "name" 14 | # Flow nodes cannot use "name" parameter in dictionary, due to the errors with PyDot 15 | node_name = "node_name" 16 | gateway_direction = "gatewayDirection" 17 | default = "default" 18 | instantiate = "instantiate" 19 | event_gateway_type = "eventGatewayType" 20 | source_ref = "sourceRef" 21 | target_ref = "targetRef" 22 | triggered_by_event = "triggeredByEvent" 23 | parallel_multiple = "parallelMultiple" 24 | cancel_activity = "cancelActivity" 25 | attached_to_ref = "attachedToRef" 26 | is_interrupting = "isInterrupting" 27 | is_closed = "isClosed" 28 | is_executable = "isExecutable" 29 | is_expanded = "isExpanded" 30 | is_horizontal = "isHorizontal" 31 | is_collection = "isCollection" 32 | process_type = "processType" 33 | sequence_flow = "sequenceFlow" 34 | condition_expression = "conditionExpression" 35 | message_flow = "messageFlow" 36 | message_flows = "messageFlows" 37 | 38 | # CSV literals 39 | csv_order = "Order" 40 | csv_activity = "Activity" 41 | csv_condition = "Condition" 42 | csv_who = "Who" 43 | csv_subprocess = "Subprocess" 44 | csv_terminated = "Terminated" 45 | 46 | # BPMN 2.0 diagram interchange element attribute names 47 | bpmn_element = "bpmnElement" 48 | height = "height" 49 | width = "width" 50 | x = "x" 51 | y = "y" 52 | 53 | # BPMN 2.0 element names 54 | definitions = "definitions" 55 | collaboration = "collaboration" 56 | participant = "participant" 57 | participants = "participants" 58 | process = "process" 59 | process_ref = "processRef" 60 | lane = "lane" 61 | lanes = "lanes" 62 | lane_set = "laneSet" 63 | child_lane_set = "childLaneSet" 64 | flow_node_ref = "flowNodeRef" 65 | flow_node_refs = "flowNodeRefs" 66 | task = "task" 67 | user_task = "userTask" 68 | service_task = "serviceTask" 69 | manual_task = "manualTask" 70 | subprocess = "subProcess" 71 | data_object = "dataObject" 72 | complex_gateway = "complexGateway" 73 | event_based_gateway = "eventBasedGateway" 74 | inclusive_gateway = "inclusiveGateway" 75 | exclusive_gateway = "exclusiveGateway" 76 | parallel_gateway = "parallelGateway" 77 | start_event = "startEvent" 78 | intermediate_catch_event = "intermediateCatchEvent" 79 | end_event = "endEvent" 80 | intermediate_throw_event = "intermediateThrowEvent" 81 | boundary_event = "boundaryEvent" 82 | 83 | # BPMN 2.0 diagram interchange element names 84 | bpmn_shape = "BPMNShape" 85 | bpmn_edge = "BPMNEdge" 86 | 87 | # BPMN 2.0 child element names 88 | incoming_flow = "incoming" 89 | incoming_flow_list = "incoming_flow_list" 90 | outgoing_flow = "outgoing" 91 | outgoing_flow_list = "outgoing_flow_list" 92 | waypoint = "waypoint" 93 | waypoints = "waypoints" 94 | 95 | # Additional parameter names 96 | type = "type" 97 | event_definitions = "event_definitions" 98 | node_ids = "node_ids" 99 | definition_type = "definition_type" 100 | 101 | grid_column_width = 2 102 | -------------------------------------------------------------------------------- /bpmn_python/diagram_layout_metrics.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Collection of different metrics used to compare diagram layout quality 4 | """ 5 | import copy 6 | import bpmn_python.bpmn_python_consts as consts 7 | 8 | 9 | def count_crossing_points(bpmn_graph): 10 | """ 11 | 12 | :param bpmn_graph: 13 | :return: 14 | """ 15 | flows = bpmn_graph.get_flows() 16 | segments = get_flows_segments(flows) 17 | 18 | crossing_point_num = 0 19 | while segments: 20 | segment_one = segments.pop() 21 | for segment_two in segments: 22 | if segments_common_points(segment_one, segment_two) is False and do_intersect(segment_one, segment_two): 23 | crossing_point_num += 1 24 | 25 | return crossing_point_num 26 | 27 | 28 | def compute_determinant(p1, p2, p3): 29 | """ 30 | 31 | :param p1: 32 | :param p2: 33 | :param p3: 34 | :return: 35 | """ 36 | det = float(p1[0]) * float(p2[1]) + float(p2[0]) * float(p3[1]) + float(p3[0]) * float(p1[1]) 37 | det -= float(p1[0]) * float(p3[1]) + float(p2[0]) * float(p1[1]) + float(p3[0]) * float(p2[1]) 38 | return det 39 | 40 | 41 | def check_integer_sign(value): 42 | """ 43 | 44 | :param value: 45 | :return: 46 | """ 47 | return value >= 0 48 | 49 | 50 | def get_flows_segments(flows): 51 | """ 52 | 53 | :param flows: 54 | """ 55 | source_param_name = "source" 56 | target_param_name = "target" 57 | 58 | segments = [] 59 | for flow in flows: 60 | waypoints = copy.deepcopy(flow[2][consts.Consts.waypoints]) 61 | source = waypoints.pop(0) 62 | while len(waypoints) > 0: 63 | target = waypoints.pop(0) 64 | segments.append({source_param_name: {consts.Consts.x: float(source[0]), consts.Consts.y: float(source[1])}, 65 | target_param_name: {consts.Consts.x: float(target[0]), consts.Consts.y: float(target[1])}}) 66 | source = target 67 | return segments 68 | 69 | 70 | def segments_common_points(segment_one, segment_two): 71 | """ 72 | 73 | :param segment_one: 74 | :param segment_two: 75 | :return: 76 | """ 77 | source_param = "source" 78 | target_param = "target" 79 | return points_are_equal(segment_one[source_param], segment_two[source_param]) \ 80 | or points_are_equal(segment_one[source_param], segment_two[target_param]) \ 81 | or points_are_equal(segment_one[target_param], segment_two[source_param]) \ 82 | or points_are_equal(segment_one[target_param], segment_two[target_param]) 83 | 84 | 85 | def points_are_equal(p1, p2): 86 | """ 87 | 88 | :param p1: 89 | :param p2: 90 | :return: 91 | """ 92 | return p1[consts.Consts.x] == p2[consts.Consts.x] and p1[consts.Consts.y] == p2[consts.Consts.y] 93 | 94 | 95 | def do_intersect(segment_one, segment_two): 96 | """ 97 | 98 | :param segment_one: 99 | :param segment_two: 100 | :return: 101 | """ 102 | source_param = "source" 103 | target_param = "target" 104 | # Find the four orientations needed for general and special cases 105 | o1 = orientation(segment_one[source_param], segment_one[target_param], segment_two[source_param]) 106 | o2 = orientation(segment_one[source_param], segment_one[target_param], segment_two[target_param]) 107 | o3 = orientation(segment_two[source_param], segment_two[target_param], segment_one[source_param]) 108 | o4 = orientation(segment_two[source_param], segment_two[target_param], segment_one[target_param]) 109 | 110 | if o1 != o2 and o3 != o4: 111 | return True 112 | 113 | # Special Cases 114 | if o1 == 0 and lies_on_segment(segment_one[source_param], segment_one[target_param], segment_two[source_param]): 115 | return True 116 | 117 | if o2 == 0 and lies_on_segment(segment_one[source_param], segment_one[target_param], segment_two[target_param]): 118 | return True 119 | 120 | if o3 == 0 and lies_on_segment(segment_two[source_param], segment_two[target_param], segment_one[source_param]): 121 | return True 122 | 123 | if o4 == 0 and lies_on_segment(segment_two[source_param], segment_two[target_param], segment_one[target_param]): 124 | return True 125 | 126 | # Neither of special cases 127 | return False 128 | 129 | 130 | def orientation(p1, p2, p3): 131 | """ 132 | Finds orientation of three points p1, p2, p3. 133 | The function returns following values 134 | 0 --> p1, p2 and p3 are collinear 135 | 1 --> Clockwise 136 | 2 --> Counterclockwise 137 | :param p1: tuple representing two dimensional point 138 | :param p2: tuple representing two dimensional point 139 | :param p3: tuple representing two dimensional point 140 | """ 141 | val = (p2[consts.Consts.y] - p1[consts.Consts.y]) * (p3[consts.Consts.x] - p2[consts.Consts.x]) \ 142 | - (p2[consts.Consts.x] - p1[consts.Consts.x]) * (p3[consts.Consts.y] - p2[consts.Consts.y]) 143 | 144 | if val == 0: 145 | return 0 # collinear 146 | elif val > 0: 147 | return 1 # clockwise 148 | else: 149 | return 2 # counterclockwise 150 | 151 | 152 | def lies_on_segment(p1, p2, p3): 153 | """ 154 | 155 | :param p1: 156 | :param p2: 157 | :param p3: 158 | :return: 159 | """ 160 | return min(p1[consts.Consts.x], p2[consts.Consts.x]) <= p3[consts.Consts.x] \ 161 | <= max(p1[consts.Consts.x], p2[consts.Consts.x])\ 162 | and min(p1[consts.Consts.y], p2[consts.Consts.y]) <= p3[consts.Consts.y] \ 163 | <= max(p1[consts.Consts.y], p2[consts.Consts.y]) 164 | 165 | 166 | def count_segments(bpmn_graph): 167 | """ 168 | 169 | :param bpmn_graph: 170 | """ 171 | flows = bpmn_graph.get_flows() 172 | segments = get_flows_segments(flows) 173 | return len(segments) 174 | 175 | 176 | def compute_longest_path(bpmn_graph): 177 | """ 178 | 179 | :param bpmn_graph: 180 | """ 181 | incoming_flows_list_param_name = "incoming" 182 | 183 | nodes = copy.deepcopy(bpmn_graph.get_nodes()) 184 | no_incoming_flow_nodes = [] 185 | for node in nodes: 186 | incoming_list = node[1][incoming_flows_list_param_name] 187 | if len(incoming_list) == 0: 188 | no_incoming_flow_nodes.append(node) 189 | 190 | longest_path = [] 191 | for node in no_incoming_flow_nodes: 192 | (output_path, output_path_len) = find_longest_path([], node, bpmn_graph) 193 | if output_path_len > len(longest_path): 194 | longest_path = output_path 195 | return longest_path, len(longest_path) 196 | 197 | 198 | def find_longest_path(previous_nodes, node, bpmn_graph): 199 | """ 200 | 201 | :param previous_nodes: 202 | :param node: 203 | :param bpmn_graph: 204 | :return: 205 | """ 206 | outgoing_flows_list_param_name = "outgoing" 207 | outgoing_flows_list = node[1][outgoing_flows_list_param_name] 208 | longest_path = [] 209 | 210 | if len(outgoing_flows_list) == 0: 211 | tmp_previous_nodes = copy.deepcopy(previous_nodes) 212 | tmp_previous_nodes.append(node) 213 | return tmp_previous_nodes, len(tmp_previous_nodes) 214 | else: 215 | tmp_previous_nodes = copy.deepcopy(previous_nodes) 216 | tmp_previous_nodes.append(node) 217 | for outgoing_flow_id in outgoing_flows_list: 218 | flow = bpmn_graph.get_flow_by_id(outgoing_flow_id) 219 | outgoing_node = bpmn_graph.get_node_by_id(flow[2][consts.Consts.target_ref]) 220 | if outgoing_node not in previous_nodes: 221 | (output_path, output_path_len) = find_longest_path(tmp_previous_nodes, outgoing_node, bpmn_graph) 222 | if output_path_len > len(longest_path): 223 | longest_path = output_path 224 | return longest_path, len(longest_path) 225 | 226 | 227 | def compute_longest_path_tasks(bpmn_graph): 228 | """ 229 | 230 | :param bpmn_graph: 231 | """ 232 | incoming_flows_list_param_name = "incoming" 233 | 234 | nodes = copy.deepcopy(bpmn_graph.get_nodes()) 235 | no_incoming_flow_nodes = [] 236 | for node in nodes: 237 | incoming_list = node[1][incoming_flows_list_param_name] 238 | if len(incoming_list) == 0: 239 | no_incoming_flow_nodes.append(node) 240 | 241 | longest_path = [] 242 | for node in no_incoming_flow_nodes: 243 | (all_nodes, qualified_nodes) = find_longest_path_tasks([], [], node, bpmn_graph) 244 | if len(qualified_nodes) > len(longest_path): 245 | longest_path = qualified_nodes 246 | return longest_path, len(longest_path) 247 | 248 | 249 | def find_longest_path_tasks(path, qualified_nodes, node, bpmn_graph): 250 | """ 251 | 252 | :param path: 253 | :param qualified_nodes: 254 | :param node: 255 | :param bpmn_graph: 256 | :return: 257 | """ 258 | node_names = {"task", "subProcess"} 259 | outgoing_flows_list = node[1][consts.Consts.outgoing_flow] 260 | 261 | if len(outgoing_flows_list) == 0: 262 | tmp_path = copy.deepcopy(path) 263 | tmp_path.append(node) 264 | tmp_qualified_nodes = copy.deepcopy(qualified_nodes) 265 | if node[1][consts.Consts.type] in node_names: 266 | tmp_qualified_nodes.append(node) 267 | return tmp_path, tmp_qualified_nodes 268 | else: 269 | longest_qualified_nodes = [] 270 | longest_path = copy.deepcopy(path) 271 | longest_path.append(node) 272 | for outgoing_flow_id in outgoing_flows_list: 273 | flow = bpmn_graph.get_flow_by_id(outgoing_flow_id) 274 | outgoing_node = bpmn_graph.get_node_by_id(flow[2][consts.Consts.target_ref]) 275 | tmp_path = copy.deepcopy(path) 276 | tmp_path.append(node) 277 | tmp_qualified_nodes = copy.deepcopy(qualified_nodes) 278 | if node[1]["type"] in node_names: 279 | tmp_qualified_nodes.append(node) 280 | 281 | if outgoing_node not in path: 282 | (path_all_nodes, path_qualified_nodes) = find_longest_path_tasks(tmp_path, tmp_qualified_nodes, 283 | outgoing_node, bpmn_graph) 284 | if len(path_qualified_nodes) > len(longest_qualified_nodes): 285 | longest_qualified_nodes = path_qualified_nodes 286 | longest_path = path_all_nodes 287 | else: 288 | if len(tmp_qualified_nodes) > len(longest_qualified_nodes): 289 | longest_qualified_nodes = tmp_qualified_nodes 290 | longest_path = tmp_path 291 | return longest_path, longest_qualified_nodes 292 | -------------------------------------------------------------------------------- /bpmn_python/graph/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Package init file 4 | """ 5 | __all__ = ["classes"] 6 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Package init file 4 | """ 5 | __all__ = ["activities", "events", "gateways", "root_element", "base_element_type", "condition_expression_type", 6 | "flow_element_type", "flow_node_type", "lane_set_type", "lane_type", "message_flow_type", "participant_type", 7 | "sequence_flow_type"] 8 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/activities/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Package init file 4 | """ 5 | __all__ = ["activity_type", "subprocess_type", "task_type"] 6 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/activities/activity_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tActivity of BPMN 2.0 graph 4 | """ 5 | import graph.classes.flow_node_type as flow_node 6 | 7 | 8 | class Activity(flow_node.FlowNode): 9 | """ 10 | Class used for representing tActivity of BPMN 2.0 graph 11 | Fields (except inherited): 12 | - default: ID of default flow of gateway. Must be either None (default is optional according to BPMN 2.0 XML Schema) 13 | or String. 14 | """ 15 | 16 | def __init__(self): 17 | """ 18 | Default constructor, initializes object fields with new instances. 19 | """ 20 | super(Activity, self).__init__() 21 | self.__default = None 22 | 23 | def get_default(self): 24 | """ 25 | Getter for 'default' field. 26 | :return:a value of 'default' field. 27 | """ 28 | return self.__default 29 | 30 | def set_default(self, value): 31 | """ 32 | Setter for 'default' field. 33 | :param value - a new value of 'default' field. Must be either None (default is optional according to 34 | BPMN 2.0 XML Schema) or String. 35 | """ 36 | if value is None: 37 | self.__default = value 38 | elif not isinstance(value, str): 39 | raise TypeError("Default must be set to a String") 40 | else: 41 | self.__default = value 42 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/activities/subprocess_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tSubProcess of BPMN 2.0 graph 4 | """ 5 | import graph.classes.activities.activity_type as activity 6 | import graph.classes.flow_element_type as flow_element 7 | import graph.classes.lane_set_type as lane_set 8 | 9 | 10 | class SubProcess(activity.Activity): 11 | """ 12 | Class used for representing tSubProcess of BPMN 2.0 graph 13 | Fields (except inherited): 14 | - lane_set_list: a list of LaneSet objects. 15 | - flow_element_list: a list of FlowElement objects. 16 | - triggered_by_event: a boolean value.. 17 | """ 18 | 19 | def __init__(self): 20 | """ 21 | Default constructor, initializes object fields with new instances. 22 | """ 23 | super(SubProcess, self).__init__() 24 | self.__triggered_by_event = False 25 | self.__lane_set_list = [] 26 | self.__flow_element_list = [] 27 | 28 | def triggered_by_event(self): 29 | """ 30 | Getter for 'triggered_by_event' field. 31 | :return: value of 'triggered_by_event' field. 32 | """ 33 | return self.__triggered_by_event 34 | 35 | def set_triggered_by_event(self, value): 36 | """ 37 | Setter for 'triggered_by_event' field. 38 | :param value - a new value of 'triggered_by_event' field. Must be a boolean type. Does not accept None value. 39 | """ 40 | if value is None or not isinstance(value, bool): 41 | raise TypeError("TriggeredByEvent must be set to a bool") 42 | else: 43 | self.__triggered_by_event = value 44 | 45 | def get_lane_set_list(self): 46 | """ 47 | Getter for 'lane_set_list' field. 48 | :return:a value of 'lane_set_list' field. 49 | """ 50 | return self.__lane_set_list 51 | 52 | def set_lane_set_list(self, value): 53 | """ 54 | Setter for 'lane_set_list' field. 55 | :param value - a new value of 'lane_set_list' field. Must be a list 56 | """ 57 | if value is None or not isinstance(value, list): 58 | raise TypeError("LaneSetList new value must be a list") 59 | else: 60 | for element in value: 61 | if not isinstance(element, lane_set.LaneSet): 62 | raise TypeError("LaneSetList elements in variable must be of LaneSet class") 63 | self.__lane_set_list = value 64 | 65 | def get_flow_element_list(self): 66 | """ 67 | Getter for 'flow_element_list' field. 68 | :return:a value of 'flow_element_list' field. 69 | """ 70 | return self.__flow_element_list 71 | 72 | def set_flow_element_list(self, value): 73 | """ 74 | Setter for 'flow_element_list' field. 75 | :param value - a new value of 'flow_element_list' field. Must be a list 76 | """ 77 | if value is None or not isinstance(value, list): 78 | raise TypeError("FlowElementList new value must be a list") 79 | else: 80 | for element in value: 81 | if not isinstance(element, flow_element.FlowElement): 82 | raise TypeError("FlowElementList elements in variable must be of FlowElement class") 83 | self.__flow_element_list = value 84 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/activities/task_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tTask of BPMN 2.0 graph 4 | """ 5 | import graph.classes.activities.activity_type as activity 6 | 7 | 8 | class Task(activity.Activity): 9 | """ 10 | Class used for representing tTask of BPMN 2.0 graph 11 | """ 12 | 13 | def __init__(self): 14 | """ 15 | Default constructor, initializes object fields with new instances. 16 | """ 17 | super(Task, self).__init__() 18 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/base_element_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tBaseElement of BPMN 2.0 graph 4 | """ 5 | 6 | 7 | class BaseElement(object): 8 | """ 9 | Class used for representing tBaseElement of BPMN 2.0 graph. 10 | Fields: 11 | - id: an ID of element. Must be either None (ID is optional according to BPMN 2.0 XML Schema) or String. 12 | """ 13 | 14 | def __init__(self): 15 | """ 16 | Default constructor, initializes object fields with new instances. 17 | """ 18 | self.__id = None 19 | 20 | def get_id(self): 21 | """ 22 | Getter for 'id' field. 23 | :return: value of 'id' field. 24 | """ 25 | return self.__id 26 | 27 | def set_id(self, value): 28 | """ 29 | Setter for 'id' field. 30 | :param value - a new value of 'id' field. Must be either None (ID is optional according to BPMN 2.0 XML Schema) 31 | or String type. 32 | """ 33 | if value is None: 34 | self.__id = value 35 | if not isinstance(value, str): 36 | raise TypeError("ID must be set to a String") 37 | else: 38 | self.__id = value 39 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/condition_expression_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing condition expression in sequence flow 4 | """ 5 | 6 | 7 | class ConditionExpression(object): 8 | """ 9 | Class used for representing condition expression in sequence flow 10 | Fields: 11 | - condition: condition expression. Required field. Must be a String. 12 | """ 13 | 14 | def __init__(self): 15 | """ 16 | Default constructor, initializes object fields with new instances. 17 | """ 18 | super(ConditionExpression, self).__init__() 19 | self.__condition = None 20 | 21 | def get_condition(self): 22 | """ 23 | Getter for 'condition' field. 24 | :return:a value of 'condition' field. 25 | """ 26 | return self.__condition 27 | 28 | def set_condition(self, value): 29 | """ 30 | Setter for 'condition' field. 31 | :param value - a new value of 'condition' field. Required field. Must be a String. 32 | """ 33 | if value is None or not isinstance(value, str): 34 | raise TypeError("Condition is required and must be set to a String") 35 | else: 36 | self.__condition = value 37 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/events/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Package init file 4 | """ 5 | __all__ = ["catch_event_type", "end_event_type", "event_type", "intermediate_catch_event_type", 6 | "intermediate_throw_event_type", "start_event_type", "throw_event_type"] 7 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/events/catch_event_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tCatchEvent of BPMN 2.0 graph 4 | """ 5 | import graph.classes.events.event_type as event 6 | import graph.classes.root_element.event_definition_type as event_definition 7 | 8 | 9 | class CatchEvent(event.Event): 10 | """ 11 | Class used for representing tCatchEvent of BPMN 2.0 graph 12 | Fields (except inherited): 13 | - parallel_multiple: a boolean value. default value "false". 14 | - event_definition_list: a list of EventDefinition objects. Optional value. 15 | """ 16 | 17 | def __init__(self): 18 | """ 19 | Default constructor, initializes object fields with new instances. 20 | """ 21 | super(CatchEvent, self).__init__() 22 | self.__parallel_multiple = False 23 | self.__event_definition_list = [] 24 | 25 | def parallel_multiple(self): 26 | """ 27 | Getter for 'parallel_multiple' field. 28 | :return: value of 'parallel_multiple' field. 29 | """ 30 | return self.__parallel_multiple 31 | 32 | def set_parallel_multiple(self, value): 33 | """ 34 | Setter for 'parallel_multiple' field. 35 | :param value - a new value of 'parallel_multiple' field. Must be a boolean type. Does not accept None value. 36 | """ 37 | if value is None or not isinstance(value, bool): 38 | raise TypeError("ParallelMultiple must be set to a bool") 39 | else: 40 | self.__parallel_multiple = value 41 | 42 | def get_event_definition_list(self): 43 | """ 44 | Getter for 'event_definition_list' field. 45 | :return: value of 'event_definition_list' field. 46 | """ 47 | return self.__event_definition_list 48 | 49 | def set_event_definition_list(self, value): 50 | """ 51 | Setter for 'event_definition_list' field. 52 | :param value - a new value of 'event_definition_list' field. Must be a list of EventDefinition objects 53 | """ 54 | if value is None or not isinstance(value, list): 55 | raise TypeError("EventDefinitionList new value must be a list") 56 | else: 57 | for element in value: 58 | if not isinstance(element, event_definition.EventDefinition): 59 | raise TypeError("EventDefinitionList elements in variable must be of Lane class") 60 | self.__event_definition_list = value 61 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/events/end_event_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tEndEvent of BPMN 2.0 graph 4 | """ 5 | import graph.classes.events.throw_event_type as throw_event 6 | 7 | 8 | class EndEvent(throw_event.ThrowEvent): 9 | """ 10 | Class used for representing tEndEvent of BPMN 2.0 graph 11 | """ 12 | 13 | def __init__(self): 14 | """ 15 | Default constructor, initializes object fields with new instances. 16 | """ 17 | super(EndEvent, self).__init__() 18 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/events/event_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tEvent of BPMN 2.0 graph 4 | """ 5 | import graph.classes.flow_node_type as flow_node 6 | 7 | 8 | class Event(flow_node.FlowNode): 9 | """ 10 | Class used for representing tEvent of BPMN 2.0 graph 11 | """ 12 | 13 | def __init__(self): 14 | """ 15 | Default constructor, initializes object fields with new instances. 16 | """ 17 | super(Event, self).__init__() 18 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/events/intermediate_catch_event_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tIntermediateCatchEvent of BPMN 2.0 graph 4 | """ 5 | import graph.classes.events.catch_event_type as catch_event 6 | 7 | 8 | class IntermediateCatchEvent(catch_event.CatchEvent): 9 | """ 10 | Class used for representing tIntermediateCatchEvent of BPMN 2.0 graph 11 | """ 12 | 13 | def __init__(self): 14 | """ 15 | Default constructor, initializes object fields with new instances. 16 | """ 17 | super(IntermediateCatchEvent, self).__init__() 18 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/events/intermediate_throw_event_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tIntermediateThrowEvent of BPMN 2.0 graph 4 | """ 5 | import graph.classes.events.throw_event_type as throw_event 6 | 7 | 8 | class IntermediateThrowEvent(throw_event.ThrowEvent): 9 | """ 10 | Class used for representing tIntermediateThrowEvent of BPMN 2.0 graph 11 | """ 12 | 13 | def __init__(self): 14 | """ 15 | Default constructor, initializes object fields with new instances. 16 | """ 17 | super(IntermediateThrowEvent, self).__init__() 18 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/events/start_event_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tStartEvent of BPMN 2.0 graph 4 | """ 5 | import graph.classes.events.catch_event_type as catch_event 6 | 7 | 8 | class StartEvent(catch_event.CatchEvent): 9 | """ 10 | Class used for representing tStartEvent of BPMN 2.0 graph 11 | """ 12 | 13 | def __init__(self): 14 | """ 15 | Default constructor, initializes object fields with new instances. 16 | """ 17 | super(StartEvent, self).__init__() 18 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/events/throw_event_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tThrowEvent of BPMN 2.0 graph 4 | """ 5 | import graph.classes.events.event_type as event 6 | import graph.classes.root_element.event_definition_type as event_definition 7 | 8 | 9 | class ThrowEvent(event.Event): 10 | """ 11 | Class used for representing tThrowEvent of BPMN 2.0 graph 12 | Fields (except inherited): 13 | - event_definition_list: a list of EventDefinition objects. Optional value. 14 | """ 15 | 16 | def __init__(self): 17 | """ 18 | Default constructor, initializes object fields with new instances. 19 | """ 20 | super(ThrowEvent, self).__init__() 21 | self.__event_definition_list = [] 22 | 23 | def get_event_definition_list(self): 24 | """ 25 | Getter for 'event_definition_list' field. 26 | :return: value of 'event_definition_list' field. 27 | """ 28 | return self.__event_definition_list 29 | 30 | def set_event_definition_list(self, value): 31 | """ 32 | Setter for 'event_definition_list' field. 33 | :param value - a new value of 'event_definition_list' field. Must be a list of EventDefinition objects 34 | """ 35 | if value is None or not isinstance(value, list): 36 | raise TypeError("EventDefinitionList new value must be a list") 37 | else: 38 | for element in value: 39 | if not isinstance(element, event_definition.EventDefinition): 40 | raise TypeError("EventDefinitionList elements in variable must be of Lane class") 41 | self.__event_definition_list = value 42 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/flow_element_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tFlowElement of BPMN 2.0 graph 4 | """ 5 | import graph.classes.base_element_type as base_element 6 | 7 | 8 | class FlowElement(base_element.BaseElement): 9 | """ 10 | Class used for representing tFlowElement of BPMN 2.0 graph. 11 | Fields (except inherited): 12 | - name: name of element. Must be either None (name is optional according to BPMN 2.0 XML Schema) or String. 13 | """ 14 | 15 | def __init__(self): 16 | """ 17 | Default constructor, initializes object fields with new instances. 18 | """ 19 | super(FlowElement, self).__init__() 20 | self.__name = None 21 | 22 | def get_name(self): 23 | """ 24 | Getter for 'name' field. 25 | :return:a value of 'name' field. 26 | """ 27 | return self.__name 28 | 29 | def set_name(self, value): 30 | """ 31 | Setter for 'name' field. 32 | :param value - a new value of 'name' field. Must be either None (name is optional according to BPMN 2.0 XML 33 | Schema) or String. 34 | """ 35 | if value is None: 36 | self.__name = value 37 | elif not isinstance(value, str): 38 | raise TypeError("Name must be set to a String") 39 | else: 40 | self.__name = value 41 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/flow_node_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tFlowNode of BPMN 2.0 graph 4 | """ 5 | import bpmn_python.graph.classes.flow_element_type as flow_element_type 6 | 7 | 8 | class FlowNode(flow_element_type.FlowElement): 9 | """ 10 | Class used for representing tFlowNode of BPMN 2.0 graph 11 | """ 12 | 13 | def __init__(self): 14 | """ 15 | Default constructor, initializes object fields with new instances. 16 | Fields (except inherited): 17 | - incoming_list: a list of IDs (String type) of incoming flows. 18 | - outgoing_list: a list of IDs (String type) of outgoing flows. 19 | """ 20 | super(FlowNode, self).__init__() 21 | self.__incoming_list = [] 22 | self.__outgoing_list = [] 23 | 24 | def get_incoming(self): 25 | """ 26 | Getter for 'incoming' field. 27 | :return:a value of 'incoming' field. 28 | """ 29 | return self.__incoming_list 30 | 31 | def set_incoming(self, value): 32 | """ 33 | Setter for 'incoming' field. 34 | :param value - a new value of 'incoming' field. List of IDs (String type) of incoming flows. 35 | """ 36 | if not isinstance(value, list): 37 | raise TypeError("IncomingList new value must be a list") 38 | for element in value: 39 | if not isinstance(element, str): 40 | raise TypeError("IncomingList elements in variable must be of String class") 41 | self.__incoming_list = value 42 | 43 | def get_outgoing(self): 44 | """ 45 | Getter for 'outgoing' field. 46 | :return:a value of 'outgoing' field. 47 | """ 48 | return self.__outgoing_list 49 | 50 | def set_outgoing(self, value): 51 | """ 52 | Setter for 'outgoing' field. 53 | :param value - a new value of 'outgoing' field. Must be a list of IDs (String type) of outgoing flows. 54 | """ 55 | if not isinstance(value, list): 56 | raise TypeError("OutgoingList new value must be a list") 57 | for element in value: 58 | if not isinstance(element, str): 59 | raise TypeError("OutgoingList elements in variable must be of String class") 60 | self.__outgoing_list = value 61 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/gateways/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Package init file 4 | """ 5 | __all__ = ["exclusive_gateway_type", "gateway_type", "inclusive_gateway_type", "parallel_gateway_type"] 6 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/gateways/exclusive_gateway_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tInclusiveGateway of BPMN 2.0 graph 4 | """ 5 | import graph.classes.gateways.gateway_type as gateway 6 | 7 | 8 | class ExclusiveGateway(gateway.Gateway): 9 | """ 10 | Class used for representing tExclusiveGateway of BPMN 2.0 graph 11 | Fields (except inherited): 12 | - default: ID of default flow of gateway. Must be either None (default is optional according to BPMN 2.0 XML Schema) 13 | or String. 14 | """ 15 | 16 | def __init__(self): 17 | """ 18 | Default constructor, initializes object fields with new instances. 19 | """ 20 | super(ExclusiveGateway, self).__init__() 21 | self.__default = None 22 | 23 | def get_default(self): 24 | """ 25 | Getter for 'default' field. 26 | :return:a value of 'default' field. 27 | """ 28 | return self.__default 29 | 30 | def set_default(self, value): 31 | """ 32 | Setter for 'default' field. 33 | :param value - a new value of 'default' field. Must be either None (default is optional according to 34 | BPMN 2.0 XML Schema) or String. 35 | """ 36 | if value is None: 37 | self.__default = value 38 | elif not isinstance(value, str): 39 | raise TypeError("Default must be set to a String") 40 | else: 41 | self.__default = value 42 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/gateways/gateway_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tGateway of BPMN 2.0 graph 4 | """ 5 | import graph.classes.flow_node_type as flow_node 6 | 7 | 8 | class Gateway(flow_node.FlowNode): 9 | """ 10 | Class used for representing tGateway of BPMN 2.0 graph 11 | """ 12 | __gateway_directions_list = ["Unspecified", "Converging", "Diverging", "Mixed"] 13 | 14 | def __init__(self): 15 | """ 16 | Default constructor, initializes object fields with new instances. 17 | """ 18 | super(Gateway, self).__init__() 19 | self.__gateway_direction = "Unspecified" 20 | 21 | def get_gateway_direction(self): 22 | """ 23 | Getter for 'gateway_direction' field. 24 | :return:a value of 'gateway_direction' field. 25 | """ 26 | return self.__gateway_direction 27 | 28 | def set_gateway_direction(self, value): 29 | """ 30 | Setter for 'gateway_direction' field. 31 | :param value - a new value of 'gateway_direction' field. 32 | """ 33 | if value is None or not isinstance(value, str): 34 | raise TypeError("GatewayDirection must be set to a String") 35 | elif value not in Gateway.__gateway_directions_list: 36 | raise ValueError("GatewayDirection must be one of specified values: 'Unspecified', 'Converging', " 37 | "'Diverging', 'Mixed'") 38 | else: 39 | self.__gateway_direction = value 40 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/gateways/inclusive_gateway_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tInclusiveGateway of BPMN 2.0 graph 4 | """ 5 | import graph.classes.gateways.gateway_type as gateway 6 | 7 | 8 | class InclusiveGateway(gateway.Gateway): 9 | """ 10 | Class used for representing tInclusiveGateway of BPMN 2.0 graph 11 | Fields (except inherited): 12 | - default: ID of default flow of gateway. Must be either None (default is optional according to BPMN 2.0 XML Schema) 13 | or String. 14 | """ 15 | 16 | def __init__(self): 17 | """ 18 | Default constructor, initializes object fields with new instances. 19 | """ 20 | super(InclusiveGateway, self).__init__() 21 | self.__default = None 22 | 23 | def get_default(self): 24 | """ 25 | Getter for 'default' field. 26 | :return:a value of 'default' field. 27 | """ 28 | return self.__default 29 | 30 | def set_default(self, value): 31 | """ 32 | Setter for 'default' field. 33 | :param value - a new value of 'default' field. Must be either None (default is optional according to 34 | BPMN 2.0 XML Schema) or String. 35 | """ 36 | if value is None: 37 | self.__default = value 38 | elif not isinstance(value, str): 39 | raise TypeError("Default must be set to a String") 40 | else: 41 | self.__default = value 42 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/gateways/parallel_gateway_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tParallelGateway of BPMN 2.0 graph 4 | """ 5 | import graph.classes.gateways.gateway_type as gateway 6 | 7 | 8 | class ParallelGateway(gateway.Gateway): 9 | """ 10 | Class used for representing tParallelGateway of BPMN 2.0 graph 11 | """ 12 | 13 | def __init__(self): 14 | """ 15 | Default constructor, initializes object fields with new instances. 16 | """ 17 | super(ParallelGateway, self).__init__() 18 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/lane_set_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tSetLane of BPMN 2.0 graph 4 | """ 5 | import graph.classes.base_element_type as base_element 6 | import graph.classes.lane_type as lane 7 | 8 | 9 | class LaneSet(base_element.BaseElement): 10 | """ 11 | Class used for representing tSetLane of BPMN 2.0 graph. 12 | Fields (except inherited): 13 | - name: name of element. Must be either None (name is optional according to BPMN 2.0 XML Schema) or String. 14 | - lane_list: a list of Lane objects. 15 | """ 16 | 17 | def __init__(self): 18 | """ 19 | Default constructor, initializes object fields with new instances. 20 | """ 21 | super(LaneSet, self).__init__() 22 | self.__name = None 23 | self.__lane_list = [] 24 | 25 | def get_name(self): 26 | """ 27 | Getter for 'name' field. 28 | :return: value of 'name' field. 29 | """ 30 | return self.__name 31 | 32 | def set_name(self, value): 33 | """ 34 | Setter for 'name' field. 35 | :param value - a new value of 'name' field. Must be either None (name is optional according to BPMN 2.0 XML 36 | Schema) or String. 37 | """ 38 | if value is None: 39 | self.__name = value 40 | elif not isinstance(value, str): 41 | raise TypeError("Name must be set to a String") 42 | else: 43 | self.__name = value 44 | 45 | def get_lane_list(self): 46 | """ 47 | Getter for 'lane_list' field. 48 | :return: value of 'lane_list' field. 49 | """ 50 | return self.__lane_list 51 | 52 | def set_lane_list(self, value): 53 | """ 54 | Setter for 'lane_list' field. 55 | :param value - a new value of 'lane_list' field. Must be a list of Lane objects 56 | """ 57 | if value is None or not isinstance(value, list): 58 | raise TypeError("LaneList new value must be a list") 59 | else: 60 | for element in value: 61 | if not isinstance(element, lane.Lane): 62 | raise TypeError("LaneList elements in variable must be of Lane class") 63 | self.__lane_list = value 64 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/lane_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tLane of BPMN 2.0 graph 4 | """ 5 | import graph.classes.base_element_type as base_element 6 | import graph.classes.lane_set_type as lane_set 7 | 8 | 9 | class Lane(base_element.BaseElement): 10 | """ 11 | Class used for representing tLane of BPMN 2.0 graph. 12 | Fields (except inherited): 13 | - name: name of element. Must be either None (name is optional according to BPMN 2.0 XML Schema) or String. 14 | - flow_node_ref_list: a list of String objects (ID of referenced nodes). 15 | - child_lane_set: an object of LaneSet type. 16 | """ 17 | 18 | def __init__(self): 19 | """ 20 | Default constructor, initializes object fields with new instances. 21 | """ 22 | super(Lane, self).__init__() 23 | self.__name = None 24 | self.__flow_node_ref_list = [] 25 | self.__child_lane_set = None 26 | 27 | def get_name(self): 28 | """ 29 | Getter for 'name' field. 30 | :return: value of 'name' field. 31 | """ 32 | return self.__name 33 | 34 | def set_name(self, value): 35 | """ 36 | Setter for 'name' field. 37 | :param value - a new value of 'name' field. Must be either None (name is optional according to BPMN 2.0 XML 38 | Schema) or String. 39 | """ 40 | if value is None: 41 | self.__name = value 42 | elif not isinstance(value, str): 43 | raise TypeError("Name must be set to a String") 44 | else: 45 | self.__name = value 46 | 47 | def get_flow_node_ref_list(self): 48 | """ 49 | Getter for 'flow_node_ref' field. 50 | :return:a value of 'flow_node_ref' field. 51 | """ 52 | return self.__flow_node_ref_list 53 | 54 | def set_flow_node_ref_list(self, value): 55 | """ 56 | Setter for 'flow_node_ref' field. 57 | :param value - a new value of 'flow_node_ref' field. Must be a list of String objects (ID of referenced nodes). 58 | """ 59 | if value is None or not isinstance(value, list): 60 | raise TypeError("FlowNodeRefList new value must be a list") 61 | else: 62 | for element in value: 63 | if not isinstance(element, str): 64 | raise TypeError("FlowNodeRefList elements in variable must be of String class") 65 | self.__flow_node_ref_list = value 66 | 67 | def get_child_lane_set(self): 68 | """ 69 | Getter for 'child_lane_set' field. 70 | :return:a value of 'child_lane_set' field. 71 | """ 72 | return self.__child_lane_set 73 | 74 | def set_child_lane_set(self, value): 75 | """ 76 | Setter for 'child_lane_set' field. 77 | :param value - a new value of 'child_lane_set' field. Must be an object of LaneSet type. 78 | """ 79 | if value is None: 80 | self.__child_lane_set = value 81 | elif not isinstance(value, lane_set.LaneSet): 82 | raise TypeError("ChildLaneSet must be a LaneSet") 83 | else: 84 | self.__child_lane_set = value 85 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/message_flow_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tMessageFlow of BPMN 2.0 graph 4 | """ 5 | import graph.classes.base_element_type as base_element 6 | 7 | 8 | class MessageFlow(base_element.BaseElement): 9 | """ 10 | Class used for representing tMessageFlow of BPMN 2.0 graph 11 | """ 12 | 13 | def __init__(self, source_ref, target_ref): 14 | """ 15 | Default constructor, initializes object fields with new instances. 16 | Fields (except inherited): 17 | - name: name of element. Must be either None (name is optional according to BPMN 2.0 XML Schema) or String. 18 | - source_ref: an ID of source node. Required field. Must be a String type. 19 | - target_ref: an ID of target node. Required field. Must be a String type. 20 | - message_ref: an ID of referenced message element. Must be either None (message_ref is optional according to 21 | BPMN 2.0 XML Schema) or String. 22 | """ 23 | if source_ref is None or not isinstance(source_ref, str): 24 | raise TypeError("SourceRef is required and must be set to a String") 25 | if target_ref is None or not isinstance(source_ref, str): 26 | raise TypeError("TargetRef is required and must be set to a String") 27 | 28 | super(MessageFlow, self).__init__() 29 | self.__name = None 30 | self.__source_ref = source_ref 31 | self.__target_ref = target_ref 32 | self.__message_ref = None 33 | 34 | def get_name(self): 35 | """ 36 | Getter for 'name' field. 37 | :return:a value of 'name' field. 38 | """ 39 | return self.__name 40 | 41 | def set_name(self, value): 42 | """ 43 | Setter for 'name' field. 44 | :param value - a new value of 'name' field. Must be either None (name is optional according to BPMN 2.0 XML 45 | Schema) or String. 46 | """ 47 | if value is None: 48 | self.__name = value 49 | elif not isinstance(value, str): 50 | raise TypeError("Name must be set to a String") 51 | else: 52 | self.__name = value 53 | 54 | def get_source_ref(self): 55 | """ 56 | Getter for 'source_ref' field. 57 | :return: value of 'source_ref' field. 58 | """ 59 | return self.__source_ref 60 | 61 | def set_source_ref(self, value): 62 | """ 63 | Setter for 'source_ref' field. 64 | :param value - a new value of 'source_ref' field. Must be a String type. 65 | """ 66 | if value is None or not isinstance(value, str): 67 | raise TypeError("SourceRef is required and must be set to a String") 68 | else: 69 | self.__source_ref = value 70 | 71 | def get_target_ref(self): 72 | """ 73 | Getter for 'target_ref' field. 74 | :return: value of 'target_ref' field. 75 | """ 76 | return self.__target_ref 77 | 78 | def set_target_ref(self, value): 79 | """ 80 | Setter for 'target_ref' field. 81 | :param value - a new value of 'target_ref' field. Must be a String type. 82 | """ 83 | if value is None or not isinstance(value, str): 84 | raise TypeError("TargetRef is required and must be set to a String") 85 | else: 86 | self.__target_ref = value 87 | 88 | def get_message_ref(self): 89 | """ 90 | Getter for 'message_ref' field. 91 | :return: value of 'message_ref' field. 92 | """ 93 | return self.__message_ref 94 | 95 | def set_message_ref(self, value): 96 | """ 97 | Setter for 'message_ref' field. 98 | :param value - a new value of 'message_ref' field. Must be either None (message_ref is optional according to 99 | BPMN 2.0 XML Schema) or String. 100 | """ 101 | if value is None: 102 | self.__message_ref = value 103 | if not isinstance(value, str): 104 | raise TypeError("MessageRef must be set to a String") 105 | else: 106 | self.__message_ref = value 107 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/participant_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tParticipant of BPMN 2.0 graph 4 | """ 5 | import graph.classes.base_element_type as base_element 6 | 7 | 8 | class Participant(base_element.BaseElement): 9 | """ 10 | Class used for representing tParticipant of BPMN 2.0 graph 11 | Fields (except inherited): 12 | - name: name of element. Must be either None (name is optional according to BPMN 2.0 XML Schema) or String. 13 | - process_ref: an ID of referenced message element. Must be either None (process_ref is optional according to 14 | BPMN 2.0 XML Schema) or String. 15 | """ 16 | 17 | def __init__(self): 18 | """ 19 | Default constructor, initializes object fields with new instances. 20 | """ 21 | super(Participant, self).__init__() 22 | self.__name = None 23 | self.__process_ref = None 24 | 25 | def get_name(self): 26 | """ 27 | Getter for 'name' field. 28 | :return:a value of 'name' field. 29 | """ 30 | return self.__name 31 | 32 | def set_name(self, value): 33 | """ 34 | Setter for 'name' field. 35 | :param value - a new value of 'name' field. Must be either None (name is optional according to BPMN 2.0 XML 36 | Schema) or String. 37 | """ 38 | if value is None: 39 | self.__name = value 40 | elif not isinstance(value, str): 41 | raise TypeError("Name must be set to a String") 42 | else: 43 | self.__name = value 44 | 45 | def get_process_ref(self): 46 | """ 47 | Getter for 'process_ref' field. 48 | :return:a value of 'process_ref' field. 49 | """ 50 | return self.__process_ref 51 | 52 | def set_process_ref(self, value): 53 | """ 54 | Setter for 'process_ref' field. 55 | :param value - a new value of 'process_ref' field. Must be either None (process_ref is optional according to 56 | BPMN 2.0 XML Schema) or String. 57 | """ 58 | if not isinstance(value, str): 59 | raise TypeError("ProcessRef must be set to a String") 60 | self.__process_ref = value 61 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/root_element/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Package init file 4 | """ 5 | __all__ = ["callable_element_type", "event_definition_type", "process_type", "root_element_type"] 6 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/root_element/callable_element_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tCallableElement of BPMN 2.0 graph 4 | """ 5 | import graph.classes.root_element.root_element_type as root_element 6 | 7 | 8 | class CallableElement(root_element.RootElement): 9 | """ 10 | Class used for representing tCallableElement of BPMN 2.0 graph. 11 | """ 12 | 13 | def __init__(self): 14 | """ 15 | Default constructor, initializes object fields with new instances. 16 | """ 17 | super(CallableElement, self).__init__() 18 | self.__name = None 19 | 20 | def get_name(self): 21 | """ 22 | Getter for 'name' field. 23 | :return:a value of 'name' field. 24 | """ 25 | return self.__name 26 | 27 | def set_name(self, value): 28 | """ 29 | Setter for 'name' field. 30 | :param value - a new value of 'name' field. Must be either None (name is optional according to BPMN 2.0 XML 31 | Schema) or String. 32 | """ 33 | if value is None: 34 | self.__name = value 35 | elif not isinstance(value, str): 36 | raise TypeError("Name must be set to a String") 37 | else: 38 | self.__name = value 39 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/root_element/event_definition_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tEventDefinition of BPMN 2.0 graph 4 | """ 5 | import graph.classes.root_element.root_element_type as root_element 6 | 7 | 8 | class EventDefinition(root_element.RootElement): 9 | """ 10 | Class used for representing tEventDefinition of BPMN 2.0 graph. 11 | """ 12 | 13 | def __init__(self): 14 | """ 15 | Default constructor, initializes object fields with new instances. 16 | """ 17 | super(EventDefinition, self).__init__() 18 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/root_element/process_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tProcess of BPMN 2.0 graph 4 | """ 5 | import graph.classes.flow_element_type as flow_element 6 | import graph.classes.lane_set_type as lane_set 7 | import graph.classes.root_element.callable_element_type as callable_element 8 | 9 | 10 | class Process(callable_element.CallableElement): 11 | """ 12 | Class used for representing tProcess of BPMN 2.0 graph. 13 | """ 14 | __process_type_list = ["None", "Public", "Private"] 15 | 16 | def __init__(self): 17 | """ 18 | Default constructor, initializes object fields with new instances. 19 | """ 20 | super(Process, self).__init__() 21 | self.__process_type = "None" 22 | self.__is_closed = False 23 | self.__is_executable = False 24 | self.__lane_set_list = [] 25 | self.__flow_element_list = [] 26 | 27 | def get_process_type(self): 28 | """ 29 | Getter for 'process_type' field. 30 | :return:a value of 'process_type' field. 31 | """ 32 | return self.__process_type 33 | 34 | def set_process_type(self, value): 35 | """ 36 | Setter for 'process_type' field. 37 | :param value - a new value of 'process_type' field. 38 | """ 39 | if value is None or not isinstance(value, str): 40 | raise TypeError("ProcessType must be set to a String") 41 | elif value not in Process.__process_type_list: 42 | raise ValueError("ProcessType must be one of specified values: 'None', 'Public', 'Private'") 43 | else: 44 | self.__process_type = value 45 | 46 | def is_closed(self): 47 | """ 48 | Getter for 'is_closed' field. 49 | :return: value of 'is_closed' field. 50 | """ 51 | return self.__is_closed 52 | 53 | def set_is_closed(self, value): 54 | """ 55 | Setter for 'is_closed' field. 56 | :param value - a new value of 'is_closed' field. Must be a boolean type. Does not accept None value. 57 | """ 58 | if value is None or not isinstance(value, bool): 59 | raise TypeError("IsClosed must be set to a bool") 60 | else: 61 | self.__is_closed = value 62 | 63 | def is_executable(self): 64 | """ 65 | Getter for 'is_executable' field. 66 | :return: value of 'is_executable' field. 67 | """ 68 | return self.__is_executable 69 | 70 | def set_is_executable(self, value): 71 | """ 72 | Setter for 'is_executable' field. 73 | :param value - a new value of 'is_executable' field. Must be a boolean type. Does not accept None value. 74 | """ 75 | if value is None or not isinstance(value, bool): 76 | raise TypeError("IsExecutable must be set to a bool") 77 | else: 78 | self.__is_executable = value 79 | 80 | def get_lane_set_list(self): 81 | """ 82 | Getter for 'lane_set_list' field. 83 | :return:a value of 'lane_set_list' field. 84 | """ 85 | return self.__lane_set_list 86 | 87 | def set_lane_set_list(self, value): 88 | """ 89 | Setter for 'lane_set_list' field. 90 | :param value - a new value of 'lane_set_list' field. Must be a list 91 | """ 92 | if value is None or not isinstance(value, list): 93 | raise TypeError("LaneSetList new value must be a list") 94 | else: 95 | for element in value: 96 | if not isinstance(element, lane_set.LaneSet): 97 | raise TypeError("LaneSetList elements in variable must be of LaneSet class") 98 | self.__lane_set_list = value 99 | 100 | def get_flow_element_list(self): 101 | """ 102 | Getter for 'flow_element_list' field. 103 | :return:a value of 'flow_element_list' field. 104 | """ 105 | return self.__flow_element_list 106 | 107 | def set_flow_element_list(self, value): 108 | """ 109 | Setter for 'flow_element_list' field. 110 | :param value - a new value of 'flow_element_list' field. Must be a list 111 | """ 112 | if value is None or not isinstance(value, list): 113 | raise TypeError("FlowElementList new value must be a list") 114 | else: 115 | for element in value: 116 | if not isinstance(element, flow_element.FlowElement): 117 | raise TypeError("FlowElementList elements in variable must be of FlowElement class") 118 | self.__flow_element_list = value 119 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/root_element/root_element_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tRootElement of BPMN 2.0 graph 4 | """ 5 | import graph.classes.base_element_type as base_element 6 | 7 | 8 | class RootElement(base_element.BaseElement): 9 | """ 10 | Class used for representing tRootElement of BPMN 2.0 graph. 11 | """ 12 | 13 | def __init__(self): 14 | """ 15 | Default constructor, initializes object fields with new instances. 16 | """ 17 | super(RootElement, self).__init__() 18 | -------------------------------------------------------------------------------- /bpmn_python/graph/classes/sequence_flow_type.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Class used for representing tSequenceFlow of BPMN 2.0 graph 4 | """ 5 | import graph.classes.condition_expression_type as condition_expression 6 | import graph.classes.flow_element_type as flow_element 7 | 8 | 9 | class SequenceFlow(flow_element.FlowElement): 10 | """ 11 | Class used for representing tSequenceFlow of BPMN 2.0 graph. 12 | Fields (except inherited): 13 | - source_ref: an ID of source node. Required field. Must be a String type. 14 | - target_ref: an ID of target node. Required field. Must be a String type. 15 | - condition_expression: an expression used as condition of flow (conditional flow). Must be either None 16 | (condition_expression is optional according to BPMN 2.0 XML Schema) or String. 17 | - is_immediate: a boolean value. 18 | """ 19 | 20 | def __init__(self, source_ref, target_ref): 21 | """ 22 | Default constructor, initializes object fields with new instances. 23 | """ 24 | if source_ref is None or not isinstance(source_ref, str): 25 | raise TypeError("SourceRef is required and must be set to a String") 26 | if target_ref is None or not isinstance(source_ref, str): 27 | raise TypeError("TargetRef is required and must be set to a String") 28 | 29 | super(SequenceFlow, self).__init__() 30 | self.__source_ref = source_ref 31 | self.__target_ref = target_ref 32 | self.__condition_expression = None 33 | self.__is_immediate = None 34 | 35 | def get_source_ref(self): 36 | """ 37 | Getter for 'source_ref' field. 38 | :return: value of 'source_ref' field. 39 | """ 40 | return self.__source_ref 41 | 42 | def set_source_ref(self, value): 43 | """ 44 | Setter for 'source_ref' field. 45 | :param value - a new value of 'source_ref' field. Required field. Must be a String type. 46 | """ 47 | if value is None or not isinstance(value, str): 48 | raise TypeError("SourceRef is required and must be set to a String") 49 | else: 50 | self.__source_ref = value 51 | 52 | def get_target_ref(self): 53 | """ 54 | Getter for 'target_ref' field. 55 | :return: value of 'target_ref' field. 56 | """ 57 | return self.__target_ref 58 | 59 | def set_target_ref(self, value): 60 | """ 61 | Setter for 'target_ref' field. 62 | :param value - a new value of 'target_ref' field. Required field. Must be a String type. 63 | """ 64 | if value is None or not isinstance(value, str): 65 | raise TypeError("TargetRef is required and must be set to a String") 66 | else: 67 | self.__target_ref = value 68 | 69 | def is_immediate(self): 70 | """ 71 | Getter for 'is_immediate' field. 72 | :return: value of 'is_immediate' field. 73 | """ 74 | return self.__is_immediate 75 | 76 | def set_is_immediate(self, value): 77 | """ 78 | Setter for 'is_immediate' field. 79 | :param value - a new value of 'is_immediate' field. Must be a boolean type. 80 | """ 81 | if value is None: 82 | self.__is_immediate = value 83 | elif not isinstance(value, bool): 84 | raise TypeError("IsImediate must be set to a bool") 85 | else: 86 | self.__is_immediate = value 87 | 88 | def get_condition_expression(self): 89 | """ 90 | Getter for 'condition_expression' field. 91 | :return: value of 'condition_expression' field. Must be either None (condition_expression is optional according 92 | to BPMN 2.0 XML Schema) or String. 93 | """ 94 | return self.__condition_expression 95 | 96 | def set_condition_expression(self, value): 97 | """ 98 | Setter for 'condition_expression' field. 99 | :param value - a new value of 'condition_expression' field. 100 | """ 101 | if value is None: 102 | self.__condition_expression = value 103 | if not isinstance(value, condition_expression.ConditionExpression): 104 | raise TypeError("ConditionExpression must be set to an instance of class ConditionExpression") 105 | else: 106 | self.__condition_expression = value 107 | -------------------------------------------------------------------------------- /bpmn_python/grid_cell_class.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | GridCell represents a single cell with node element on two-dimensional grid layout. It is used in diagram layouting 4 | process 5 | """ 6 | 7 | 8 | class GridCell(object): 9 | """ 10 | Helper class used for Grid cell representation. Contains cell coordinates (row and column) and reference to fow node 11 | """ 12 | 13 | def __init__(self, row, col, node_id): 14 | self.row = row 15 | self.col = col 16 | self.node_id = node_id 17 | 18 | def __str__(self): 19 | return repr(self.row + " " + self.col + " " + self.node_id) -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = source 8 | BUILDDIR = build 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/image/camunda-complex-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KrzyHonk/bpmn-python/6e5e28e3d656dbf5bd3d85d78fe8e3f2fb462629/docs/image/camunda-complex-example.png -------------------------------------------------------------------------------- /docs/image/camunda-simple-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KrzyHonk/bpmn-python/6e5e28e3d656dbf5bd3d85d78fe8e3f2fb462629/docs/image/camunda-simple-example.png -------------------------------------------------------------------------------- /docs/image/manual-complex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KrzyHonk/bpmn-python/6e5e28e3d656dbf5bd3d85d78fe8e3f2fb462629/docs/image/manual-complex.png -------------------------------------------------------------------------------- /docs/image/manual-simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KrzyHonk/bpmn-python/6e5e28e3d656dbf5bd3d85d78fe8e3f2fb462629/docs/image/manual-simple.png -------------------------------------------------------------------------------- /docs/image/signavio-complex-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KrzyHonk/bpmn-python/6e5e28e3d656dbf5bd3d85d78fe8e3f2fb462629/docs/image/signavio-complex-example.png -------------------------------------------------------------------------------- /docs/image/signavio-simple-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KrzyHonk/bpmn-python/6e5e28e3d656dbf5bd3d85d78fe8e3f2fb462629/docs/image/signavio-simple-example.png -------------------------------------------------------------------------------- /docs/requirements-docs.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx_autodoc_typehints 3 | sphinxcontrib_trio 4 | sphinx_rtd_theme 5 | -------------------------------------------------------------------------------- /docs/source/api/bpmn_diagram_exception.rst: -------------------------------------------------------------------------------- 1 | BPMN digram exceptions 2 | ====================== 3 | 4 | .. automodule:: bpmn_python.bpmn_diagram_exception 5 | .. autoclass:: BpmnPythonError 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/source/api/bpmn_diagram_export.rst: -------------------------------------------------------------------------------- 1 | BPMN diagram export 2 | =================== 3 | 4 | .. automodule:: bpmn_python.bpmn_diagram_export 5 | .. autoclass:: BpmnDiagramGraphExport 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/source/api/bpmn_diagram_import.rst: -------------------------------------------------------------------------------- 1 | BPMN diagram import 2 | =================== 3 | 4 | .. automodule:: bpmn_python.bpmn_diagram_import 5 | .. autoclass:: BpmnDiagramGraphImport 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/source/api/bpmn_diagram_layouter.rst: -------------------------------------------------------------------------------- 1 | BPMN diagram layouter 2 | ===================== 3 | 4 | .. automodule:: bpmn_python.bpmn_diagram_layouter 5 | :members: 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/source/api/bpmn_diagram_metrics.rst: -------------------------------------------------------------------------------- 1 | BPMN diagram metrics 2 | ==================== 3 | 4 | .. automodule:: bpmn_python.bpmn_diagram_metrics 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/api/bpmn_diagram_rep.rst: -------------------------------------------------------------------------------- 1 | BPMN diagram representation 2 | =========================== 3 | 4 | .. automodule:: bpmn_python.bpmn_diagram_rep 5 | .. autoclass:: BpmnDiagramGraph 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/source/api/bpmn_diagram_visualizer.rst: -------------------------------------------------------------------------------- 1 | BPMN diagram visualization 2 | ========================== 3 | 4 | .. automodule:: bpmn_python.bpmn_diagram_visualizer 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/api/bpmn_process_csv_export.rst: -------------------------------------------------------------------------------- 1 | BPMN process CSV export 2 | =================== 3 | 4 | .. automodule:: bpmn_python.bpmn_process_csv_export 5 | .. autoclass:: BpmnDiagramGraphCsvExport 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/source/api/bpmn_process_csv_import.rst: -------------------------------------------------------------------------------- 1 | BPMN process CSV import 2 | ======================= 3 | 4 | .. automodule:: bpmn_python.bpmn_process_csv_import 5 | .. autoclass:: BpmnDiagramGraphCSVImport 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/source/api/grid_cell_class.rst: -------------------------------------------------------------------------------- 1 | GridCell representation 2 | ======================= 3 | 4 | .. automodule:: bpmn_python.grid_cell_class 5 | .. autoclass:: GridCell 6 | :members: 7 | -------------------------------------------------------------------------------- /docs/source/api_reference.rst: -------------------------------------------------------------------------------- 1 | Api reference 2 | ============= 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | api/bpmn_diagram_import 9 | api/bpmn_diagram_export 10 | 11 | api/bpmn_process_csv_import 12 | api/bpmn_process_csv_export 13 | 14 | api/bpmn_diagram_exception 15 | api/bpmn_diagram_layouter 16 | api/bpmn_diagram_metrics 17 | api/bpmn_diagram_rep 18 | api/bpmn_diagram_visualizer 19 | 20 | api/grid_cell_class 21 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | import os 16 | import sys 17 | sys.path.insert(0, os.path.abspath('../bpmn_python')) 18 | 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = 'BPMN Python' 23 | copyright = '2019, Izbela Smietana, Krzysztof Honkisz' 24 | author = 'Izbela Smietana, Krzysztof Honkisz' 25 | 26 | # The short X.Y version 27 | version = '' 28 | # The full version, including alpha/beta/rc tags 29 | release = '0.0.19' 30 | 31 | 32 | # -- General configuration --------------------------------------------------- 33 | 34 | # If your documentation needs a minimal Sphinx version, state it here. 35 | # 36 | # needs_sphinx = '1.0' 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 40 | # ones. 41 | extensions = [ 42 | 'sphinx.ext.autodoc', 43 | 'sphinx.ext.intersphinx', 44 | 'sphinx.ext.imgmath', 45 | 'sphinx.ext.viewcode', 46 | 'sphinx.ext.githubpages', 47 | ] 48 | 49 | # Add any paths that contain templates here, relative to this directory. 50 | templates_path = ['_templates'] 51 | 52 | # The suffix(es) of source filenames. 53 | # You can specify multiple suffix as a list of string: 54 | # 55 | # source_suffix = ['.rst', '.md'] 56 | source_suffix = '.rst' 57 | 58 | # The master toctree document. 59 | master_doc = 'index' 60 | 61 | # The language for content autogenerated by Sphinx. Refer to documentation 62 | # for a list of supported languages. 63 | # 64 | # This is also used if you do content translation via gettext catalogs. 65 | # Usually you set "language" from the command line for these cases. 66 | language = None 67 | 68 | # List of patterns, relative to source directory, that match files and 69 | # directories to ignore when looking for source files. 70 | # This pattern also affects html_static_path and html_extra_path. 71 | exclude_patterns = [] 72 | 73 | # The name of the Pygments (syntax highlighting) style to use. 74 | pygments_style = None 75 | 76 | 77 | # -- Options for HTML output ------------------------------------------------- 78 | 79 | # The theme to use for HTML and HTML Help pages. See the documentation for 80 | # a list of builtin themes. 81 | # 82 | html_theme = 'sphinx_rtd_theme' 83 | 84 | # Theme options are theme-specific and customize the look and feel of a theme 85 | # further. For a list of options available for each theme, see the 86 | # documentation. 87 | # 88 | # html_theme_options = {} 89 | 90 | # Add any paths that contain custom static files (such as style sheets) here, 91 | # relative to this directory. They are copied after the builtin static files, 92 | # so a file named "default.css" will overwrite the builtin "default.css". 93 | html_static_path = ['_static'] 94 | 95 | # Custom sidebar templates, must be a dictionary that maps document names 96 | # to template names. 97 | # 98 | # The default sidebars (for documents that don't match any pattern) are 99 | # defined by theme itself. Builtin themes are using these templates by 100 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 101 | # 'searchbox.html']``. 102 | # 103 | # html_sidebars = {} 104 | 105 | 106 | # -- Options for HTMLHelp output --------------------------------------------- 107 | 108 | # Output file base name for HTML help builder. 109 | htmlhelp_basename = 'BPMNPythondoc' 110 | 111 | 112 | # -- Options for LaTeX output ------------------------------------------------ 113 | 114 | latex_elements = { 115 | # The paper size ('letterpaper' or 'a4paper'). 116 | # 117 | # 'papersize': 'letterpaper', 118 | 119 | # The font size ('10pt', '11pt' or '12pt'). 120 | # 121 | # 'pointsize': '10pt', 122 | 123 | # Additional stuff for the LaTeX preamble. 124 | # 125 | # 'preamble': '', 126 | 127 | # Latex figure (float) alignment 128 | # 129 | # 'figure_align': 'htbp', 130 | } 131 | 132 | # Grouping the document tree into LaTeX files. List of tuples 133 | # (source start file, target name, title, 134 | # author, documentclass [howto, manual, or own class]). 135 | latex_documents = [ 136 | (master_doc, 'BPMNPython.tex', 'BPMN Python Documentation', 137 | 'Izbela Smietana, Krzysztof Honkisz', 'manual'), 138 | ] 139 | 140 | 141 | # -- Options for manual page output ------------------------------------------ 142 | 143 | # One entry per manual page. List of tuples 144 | # (source start file, name, description, authors, manual section). 145 | man_pages = [ 146 | (master_doc, 'bpmnpython', 'BPMN Python Documentation', 147 | [author], 1) 148 | ] 149 | 150 | 151 | # -- Options for Texinfo output ---------------------------------------------- 152 | 153 | # Grouping the document tree into Texinfo files. List of tuples 154 | # (source start file, target name, title, author, 155 | # dir menu entry, description, category) 156 | texinfo_documents = [ 157 | (master_doc, 'BPMNPython', 'BPMN Python Documentation', 158 | author, 'BPMNPython', 'One line description of project.', 159 | 'Miscellaneous'), 160 | ] 161 | 162 | 163 | # -- Options for Epub output ------------------------------------------------- 164 | 165 | # Bibliographic Dublin Core info. 166 | epub_title = project 167 | 168 | # The unique identifier of the text. This can be a ISBN number 169 | # or the project homepage. 170 | # 171 | # epub_identifier = '' 172 | 173 | # A unique identification for the text. 174 | # 175 | # epub_uid = '' 176 | 177 | # A list of files that should not be packed into the epub file. 178 | epub_exclude_files = ['search.html'] 179 | 180 | 181 | # -- Extension configuration ------------------------------------------------- 182 | 183 | # -- Options for intersphinx extension --------------------------------------- 184 | 185 | # Example configuration for intersphinx: refer to the Python standard library. 186 | intersphinx_mapping = {'https://docs.python.org/': None} 187 | 188 | # -- Extension configuration ------------------------------------------------- 189 | extensions = [ 190 | 'sphinx.ext.autodoc', 191 | 'sphinx_autodoc_typehints', 192 | 'sphinxcontrib_trio' 193 | ] 194 | autoclass_content = 'both' 195 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. BPMN Python documentation master file, created by 2 | sphinx-quickstart on Sat Jan 5 14:14:58 2019. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to BPMN Python's documentation! 7 | ======================================= 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | self 14 | api_reference 15 | 16 | 17 | 18 | Indices and tables 19 | ================== 20 | 21 | * :ref:`genindex` 22 | * :ref:`modindex` 23 | * :ref:`search` 24 | 25 | 26 | 27 | Overview 28 | -------- 29 | 30 | `bpmn_python` is Python client library for importing/exporting BPMN diagrams from an XML file with basic visualization capabilities using `matplotlib `_ 31 | 32 | 33 | Key features 34 | ------------ 35 | 36 | - parse bpmn representation from xml files 37 | - tinker around and visualize bpmn models 38 | 39 | Installation 40 | ------------ 41 | 42 | .. code-block:: bash 43 | 44 | pip install bpmn_python 45 | 46 | 47 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | six 2 | pandas 3 | networkx 4 | matplotlib 5 | pydotplus 6 | pydot -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import os 3 | 4 | from setuptools import setup, find_packages 5 | 6 | 7 | def read(fname): 8 | """ 9 | Utility function to read the README file. Used for the long_description. 10 | :param fname: 11 | :return: 12 | """ 13 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 14 | 15 | 16 | setup( 17 | name="bpmn_python", 18 | version="0.0.19-SNAPSHOT", 19 | author="Izbela Smietana, Krzysztof Honkisz", 20 | # author_email = "honkiszkrzystof@gmail.com", 21 | description=("Python library that allows to import/export BPMN diagram (as an XML file) and provides a simple " 22 | "visualization capabilities."), 23 | license="GNU GENERAL PUBLIC LICENSE", 24 | keywords=["bpmn", "xml"], 25 | url="https://github.com/KrzyHonk/bpmn-python", 26 | download_url="https://github.com/KrzyHonk/bpmn-python/tarball/0.0.19-SNAPSHOT", 27 | python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', 28 | packages=find_packages(exclude=['docs', 'tests*']), 29 | install_requires=read('requirements.txt').split('\n'), 30 | long_description="%s\n%s" % ( 31 | read('README.md'), 32 | read('CHANGELOG.md') 33 | ), 34 | long_description_content_type='text/markdown', 35 | classifiers=[ 36 | "License :: OSI Approved :: GNU General Public License (GPL)", 37 | "Programming Language :: Python", 38 | "Programming Language :: Python :: 2", 39 | "Programming Language :: Python :: 3", 40 | "Topic :: Scientific/Engineering :: Information Analysis", 41 | "Topic :: Text Processing :: Markup :: XML" 42 | ] 43 | ) 44 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | 6 | @pytest.fixture(scope="module", autouse=True) 7 | def change_path_to_script_location(request): 8 | test_dir = os.path.dirname(request.module.__file__) 9 | os.chdir(test_dir) 10 | 11 | 12 | @pytest.fixture(scope="session", autouse=True) 13 | def reset_path(request): 14 | initial_dir = os.getcwd() 15 | yield 16 | os.chdir(initial_dir) 17 | -------------------------------------------------------------------------------- /tests/csv_export/csv_export_test.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Unit tests for exporting process to CSV functionality. 4 | """ 5 | 6 | import os 7 | import unittest 8 | 9 | import bpmn_python.bpmn_diagram_rep as diagram 10 | 11 | 12 | class CsvExportTests(unittest.TestCase): 13 | """ 14 | This class contains test for manual diagram generation functionality. 15 | """ 16 | output_directory = "./output/test-csv-export/" 17 | example_directory = "../examples/csv_export/" 18 | 19 | def test_csv_export_bank_account_example(self): 20 | # TODO not working correctly, problem with nested splits 21 | bpmn_graph = diagram.BpmnDiagramGraph() 22 | bpmn_graph.load_diagram_from_xml_file(os.path.abspath(self.example_directory + "bank-account-process.bpmn")) 23 | bpmn_graph.export_csv_file(self.output_directory, "bank-account-process.csv") 24 | 25 | def test_csv_export_checkin_process_example(self): 26 | bpmn_graph = diagram.BpmnDiagramGraph() 27 | bpmn_graph.load_diagram_from_xml_file(os.path.abspath(self.example_directory + "checkin-process.bpmn")) 28 | bpmn_graph.export_csv_file(self.output_directory, "checkin-process.csv") 29 | 30 | def test_csv_export_credit_process_example(self): 31 | bpmn_graph = diagram.BpmnDiagramGraph() 32 | bpmn_graph.load_diagram_from_xml_file(os.path.abspath(self.example_directory + "credit-process.bpmn")) 33 | bpmn_graph.export_csv_file(self.output_directory, "credit-process.csv") 34 | 35 | def test_csv_export_order_processing_example(self): 36 | # TODO not working correctly, problem with nested splits 37 | bpmn_graph = diagram.BpmnDiagramGraph() 38 | bpmn_graph.load_diagram_from_xml_file(os.path.abspath(self.example_directory + "order-processing.bpmn")) 39 | bpmn_graph.export_csv_file(self.output_directory, "order-processing.csv") 40 | 41 | def test_csv_export_pizza_order_example(self): 42 | bpmn_graph = diagram.BpmnDiagramGraph() 43 | bpmn_graph.load_diagram_from_xml_file(os.path.abspath(self.example_directory + "pizza-order.bpmn")) 44 | bpmn_graph.export_csv_file(self.output_directory, "pizza-order.csv") 45 | 46 | def test_csv_export_tram_process_example(self): 47 | bpmn_graph = diagram.BpmnDiagramGraph() 48 | bpmn_graph.load_diagram_from_xml_file(os.path.abspath(self.example_directory + "tram-process.bpmn")) 49 | # TODO Problem with the loops 50 | #bpmn_graph.export_csv_file(self.output_directory, "tram-process.csv") 51 | 52 | def test_csv_export_manual_simple_diagram(self): 53 | bpmn_graph = diagram.BpmnDiagramGraph() 54 | bpmn_graph.create_new_diagram_graph(diagram_name="diagram1") 55 | process_id = bpmn_graph.add_process_to_diagram() 56 | [start_id, _] = bpmn_graph.add_start_event_to_diagram(process_id, start_event_name="Start event", 57 | start_event_definition="timer") 58 | [task1_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="Task 1") 59 | [subprocess1_id, _] = bpmn_graph.add_subprocess_to_diagram(process_id, subprocess_name="Subprocess 1") 60 | [subprocess2_id, _] = bpmn_graph.add_subprocess_to_diagram(process_id, subprocess_name="Subprocess 2") 61 | [task2_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="Task 2") 62 | [end_id, _] = bpmn_graph.add_end_event_to_diagram(process_id, end_event_name="End event", 63 | end_event_definition="message") 64 | 65 | bpmn_graph.add_sequence_flow_to_diagram(process_id, start_id, task1_id, 66 | sequence_flow_name="start_to_task_one") 67 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task1_id, subprocess1_id, 68 | sequence_flow_name="task_one_to_subprocess_one") 69 | bpmn_graph.add_sequence_flow_to_diagram(process_id, subprocess1_id, subprocess2_id, 70 | sequence_flow_name="subprocess_one_to_subprocess_two") 71 | bpmn_graph.add_sequence_flow_to_diagram(process_id, subprocess2_id, task2_id, 72 | sequence_flow_name="subprocess_two_to_task_two") 73 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task2_id, end_id, 74 | sequence_flow_name="task_two_to_end") 75 | 76 | bpmn_graph.export_csv_file(self.output_directory, "simple_diagram.csv") 77 | bpmn_graph.export_xml_file(self.output_directory, "simple_diagram.bpmn") 78 | 79 | def test_csv_export_diagram_with_exclusive_parallel_gateway(self): 80 | bpmn_graph = diagram.BpmnDiagramGraph() 81 | bpmn_graph.create_new_diagram_graph(diagram_name="diagram1") 82 | process_id = bpmn_graph.add_process_to_diagram() 83 | [start_id, _] = bpmn_graph.add_start_event_to_diagram(process_id, start_event_name="Start event", 84 | start_event_definition="timer") 85 | [task1_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="Task 1") 86 | 87 | [exclusive_gate_fork_id, _] = bpmn_graph.add_exclusive_gateway_to_diagram(process_id, 88 | gateway_name="Exclusive gate fork") 89 | [task2_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="Task 2") 90 | [task3_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="Task 3") 91 | [task6_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="Task 6") 92 | [exclusive_gate_join_id, _] = bpmn_graph.add_exclusive_gateway_to_diagram(process_id, 93 | gateway_name="Exclusive gate join") 94 | 95 | [parallel_gate_fork_id, _] = bpmn_graph.add_parallel_gateway_to_diagram(process_id, 96 | gateway_name="Parallel gateway fork") 97 | [task4_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="Task 4") 98 | [task5_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="Task 5") 99 | [parallel_gate_join_id, _] = bpmn_graph.add_parallel_gateway_to_diagram(process_id, 100 | gateway_name="Parallel gateway join") 101 | 102 | [end_id, _] = bpmn_graph.add_end_event_to_diagram(process_id, end_event_name="End event", 103 | end_event_definition="message") 104 | 105 | bpmn_graph.add_sequence_flow_to_diagram(process_id, start_id, task1_id, 106 | sequence_flow_name="Start to one") 107 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task1_id, exclusive_gate_fork_id, 108 | sequence_flow_name="Task one to exclusive fork") 109 | bpmn_graph.add_sequence_flow_to_diagram(process_id, exclusive_gate_fork_id, task2_id, 110 | sequence_flow_name="Exclusive fork to task two") 111 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task2_id, task3_id, 112 | sequence_flow_name="Task two to task three") 113 | bpmn_graph.add_sequence_flow_to_diagram(process_id, exclusive_gate_fork_id, parallel_gate_fork_id, 114 | sequence_flow_name="Exclusive fork to parallel fork") 115 | bpmn_graph.add_sequence_flow_to_diagram(process_id, parallel_gate_fork_id, task4_id, 116 | sequence_flow_name="Parallel fork to task four") 117 | bpmn_graph.add_sequence_flow_to_diagram(process_id, parallel_gate_fork_id, task5_id, 118 | sequence_flow_name="Parallel fork to task five") 119 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task4_id, parallel_gate_join_id, 120 | sequence_flow_name="Task four to parallel join") 121 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task5_id, parallel_gate_join_id, 122 | sequence_flow_name="Task five to parallel join") 123 | bpmn_graph.add_sequence_flow_to_diagram(process_id, parallel_gate_join_id, task6_id, 124 | sequence_flow_name="Parallel join to task six") 125 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task3_id, exclusive_gate_join_id, 126 | sequence_flow_name="Task three to exclusive join") 127 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task6_id, exclusive_gate_join_id, 128 | sequence_flow_name="Task six to exclusive join") 129 | bpmn_graph.add_sequence_flow_to_diagram(process_id, exclusive_gate_join_id, end_id, 130 | sequence_flow_name="Exclusive join to end event") 131 | 132 | bpmn_graph.export_csv_file(self.output_directory, "exclusive_parallel_gateways_diagram.csv") 133 | bpmn_graph.export_xml_file(self.output_directory, "exclusive_parallel_gateways_diagram.bpmn") 134 | 135 | def test_csv_export_diagram_with_inclusive_parallel_gateway(self): 136 | bpmn_graph = diagram.BpmnDiagramGraph() 137 | bpmn_graph.create_new_diagram_graph(diagram_name="diagram1") 138 | process_id = bpmn_graph.add_process_to_diagram() 139 | [start_id, _] = bpmn_graph.add_start_event_to_diagram(process_id, start_event_name="Start event", 140 | start_event_definition="timer") 141 | [task1_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="Task 1") 142 | 143 | [exclusive_gate_fork_id, _] = bpmn_graph.add_inclusive_gateway_to_diagram(process_id, 144 | gateway_name="Inclusive gate fork") 145 | [task2_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="Task 2") 146 | [task3_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="Task 3") 147 | [task6_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="Task 6") 148 | [exclusive_gate_join_id, _] = bpmn_graph.add_inclusive_gateway_to_diagram(process_id, 149 | gateway_name="Inclusive gate join") 150 | 151 | [parallel_gate_fork_id, _] = bpmn_graph.add_parallel_gateway_to_diagram(process_id, 152 | gateway_name="Parallel gateway fork") 153 | [task4_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="Task 4") 154 | [task5_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="Task 5") 155 | [parallel_gate_join_id, _] = bpmn_graph.add_parallel_gateway_to_diagram(process_id, 156 | gateway_name="Parallel gateway join") 157 | 158 | [end_id, _] = bpmn_graph.add_end_event_to_diagram(process_id, end_event_name="End event", 159 | end_event_definition="message") 160 | 161 | bpmn_graph.add_sequence_flow_to_diagram(process_id, start_id, task1_id, 162 | sequence_flow_name="Start to one") 163 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task1_id, exclusive_gate_fork_id, 164 | sequence_flow_name="Task one to exclusive fork") 165 | bpmn_graph.add_sequence_flow_to_diagram(process_id, exclusive_gate_fork_id, task2_id, 166 | sequence_flow_name="Condition: approved") 167 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task2_id, task3_id, 168 | sequence_flow_name="Task two to task three") 169 | bpmn_graph.add_sequence_flow_to_diagram(process_id, exclusive_gate_fork_id, parallel_gate_fork_id, 170 | sequence_flow_name="Condition: rejected") 171 | bpmn_graph.add_sequence_flow_to_diagram(process_id, parallel_gate_fork_id, task4_id, 172 | sequence_flow_name="Parallel fork to task four") 173 | bpmn_graph.add_sequence_flow_to_diagram(process_id, parallel_gate_fork_id, task5_id, 174 | sequence_flow_name="Parallel fork to task five") 175 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task4_id, parallel_gate_join_id, 176 | sequence_flow_name="Task four to parallel join") 177 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task5_id, parallel_gate_join_id, 178 | sequence_flow_name="Task five to parallel join") 179 | bpmn_graph.add_sequence_flow_to_diagram(process_id, parallel_gate_join_id, task6_id, 180 | sequence_flow_name="Parallel join to task six") 181 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task3_id, exclusive_gate_join_id, 182 | sequence_flow_name="Task three to exclusive join") 183 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task6_id, exclusive_gate_join_id, 184 | sequence_flow_name="Task six to exclusive join") 185 | bpmn_graph.add_sequence_flow_to_diagram(process_id, exclusive_gate_join_id, end_id, 186 | sequence_flow_name="Exclusive join to end event") 187 | 188 | bpmn_graph.export_csv_file(self.output_directory, "inclusive_parallel_gateways_diagram.csv") 189 | bpmn_graph.export_xml_file(self.output_directory, "inclusive_parallel_gateways_diagram.bpmn") 190 | 191 | 192 | if __name__ == '__main__': 193 | unittest.main() 194 | -------------------------------------------------------------------------------- /tests/csv_import/csv_import_test.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Unit tests for exporting process to CSV functionality. 4 | """ 5 | 6 | import filecmp 7 | import os 8 | import unittest 9 | 10 | import bpmn_python.bpmn_diagram_rep as diagram 11 | 12 | 13 | class CsvExportTests(unittest.TestCase): 14 | """ 15 | This class contains test for manual diagram generation functionality. 16 | """ 17 | output_directory = "./output/" 18 | input_directory = "./input/" 19 | 20 | def test_csv_import_csv_export(self): 21 | processes = ["pizza-order", "airline-checkin", "order-processing"] 22 | 23 | for process in processes: 24 | bpmn_graph = diagram.BpmnDiagramGraph() 25 | bpmn_graph.load_diagram_from_csv_file(os.path.abspath(self.input_directory + process + ".csv")) 26 | bpmn_graph.export_csv_file(self.output_directory, process + ".csv") 27 | cmp_result = filecmp.cmp(self.input_directory + process + ".csv", self.output_directory, process + ".csv") 28 | # unittest.TestCase.assertTrue(self, cmp_result) # unfortunatelly csv export has bugs 29 | bpmn_graph.export_xml_file_no_di(self.output_directory, process + ".bpmn") 30 | 31 | 32 | if __name__ == '__main__': 33 | unittest.main() 34 | -------------------------------------------------------------------------------- /tests/csv_import/input/airline-checkin.csv: -------------------------------------------------------------------------------- 1 | Order,Activity,Condition,Who,Subprocess,Terminated 2 | 0,start,,,, 3 | 1,Validate Passenger Ticket & Identification,,Check In Counter,, 4 | 2a,Confirm Itinerary,Validity,,yes, 5 | 2b,Reject Passenger,else,,,yes 6 | 3,Ask Passenger for Prohibited Objects,,,, 7 | 4a,Remove Prohibited Objects,Prohibited Objects,,, 8 | 4b,goto 5,else,,, 9 | 5,Ask Passenger for Baggages,,,, 10 | 6,Weight Baggages,,,, 11 | 7,Calculate Additional Fees,,,, 12 | 8,Inform Passenger of Additional Fees,,,, 13 | 9,Collect Payment of Fees,,,, 14 | 10a,Generate and Print Boarding Pass,,,, 15 | 10b1,Generate and Print Baggage Tags,,,, 16 | 10b2,Identify and Move Baggages,,,, 17 | 11,"Hand out Boarding Pass, Ticket and Identification",,,,yes -------------------------------------------------------------------------------- /tests/csv_import/input/order-processing.csv: -------------------------------------------------------------------------------- 1 | Order,Activity,Condition,Who,Subprocess,Terminated 2 | 0,start,,,, 3 | 1,Receive Order,,,, 4 | 2a1,Fill Order,Accepted,,, 5 | 2a2a1,Send Invoice,,,, 6 | 2a2a2,Make Payment,,,, 7 | 2a2a3,Accept Payment,,,, 8 | 2a2b,Ship Order,,,, 9 | 2b,goto 3,Rejected,,, 10 | 3,Close Order,,,,yes -------------------------------------------------------------------------------- /tests/csv_import/input/pizza-order.csv: -------------------------------------------------------------------------------- 1 | Order,Activity,Condition,Who,Subprocess,Terminated 2 | 0,Receive pizza order,,,, 3 | 1,Answer customer call,,,yes, 4 | 2,Assign the Order,,,, 5 | 3,Prepare the Pizza,,,, 6 | 4,Cook the Pizza,,,, 7 | 5a1,Package the Pizza,,,, 8 | 5b1,Assign the Delivery,,,yes, 9 | 6,Deliver the Pizza,,,, 10 | 7,Receive Payment,,,, 11 | 8,,,,,yes 12 | -------------------------------------------------------------------------------- /tests/examples/metrics/crossing_point_test.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | sid-19A536E5-4417-4A2E-A752-853AE4AFC249 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | sid-19A536E5-4417-4A2E-A752-853AE4AFC249 20 | sid-825EB574-A785-47A1-BE52-782DD0537EED 21 | 22 | 23 | 24 | 25 | 26 | sid-825EB574-A785-47A1-BE52-782DD0537EED 27 | sid-3465A198-0810-4824-9607-F511302F6A3F 28 | sid-3FD51905-6FF2-44BD-BE0D-2EDDC1FF89CD 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | sid-3465A198-0810-4824-9607-F511302F6A3F 37 | sid-5181181C-4CD8-42B2-AC4C-5CFC05C639BC 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | sid-3FD51905-6FF2-44BD-BE0D-2EDDC1FF89CD 46 | sid-CCF0AC4F-620F-4BF0-85E1-179B722C58C3 47 | 48 | 49 | 50 | 51 | 52 | sid-5181181C-4CD8-42B2-AC4C-5CFC05C639BC 53 | sid-CCF0AC4F-620F-4BF0-85E1-179B722C58C3 54 | sid-13EFCF49-FB3D-45DC-801C-923D7B8EBB7A 55 | 56 | 57 | 58 | 59 | 60 | sid-13EFCF49-FB3D-45DC-801C-923D7B8EBB7A 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /tests/examples/xml_import_export/bpmn_editor_simple_example.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /tests/examples/xml_import_export/camunda_simple_example.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SequenceFlow_0li1b0t 6 | 7 | 8 | SequenceFlow_0li1b0t 9 | SequenceFlow_0zl3cow 10 | 11 | 12 | 13 | SequenceFlow_0zl3cow 14 | SequenceFlow_0yk155p 15 | SequenceFlow_1foquue 16 | 17 | 18 | 19 | SequenceFlow_0yk155p 20 | SequenceFlow_0g72tcb 21 | 22 | 23 | 24 | SequenceFlow_1foquue 25 | SequenceFlow_0n4g88a 26 | 27 | 28 | 29 | SequenceFlow_0n4g88a 30 | SequenceFlow_0g72tcb 31 | SequenceFlow_15jrpi8 32 | 33 | 34 | 35 | 36 | SequenceFlow_15jrpi8 37 | SequenceFlow_0uvjgsv 38 | 39 | 40 | 41 | SequenceFlow_0uvjgsv 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /tests/examples/xml_import_export/default-conditional-flow-example.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | sid-F7BF2C58-D4B4-48BD-B614-83A3FB477A15 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | sid-F7BF2C58-D4B4-48BD-B614-83A3FB477A15 20 | sid-8E374108-901D-4CF5-80EA-B6B4C30B3BE0 21 | sid-EF041DBC-8714-4D69-9098-1A4BF0BC56F4 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | sid-EF041DBC-8714-4D69-9098-1A4BF0BC56F4 30 | sid-FA8F2F2D-D5A9-4C92-9B2F-5DFBF1B9B713 31 | 32 | 33 | 34 | 35 | 36 | sid-FA8F2F2D-D5A9-4C92-9B2F-5DFBF1B9B713 37 | sid-9AE25A5F-D224-40C4-9E9A-4548440BABBC 38 | sid-561AE46F-B492-4175-A227-49CF380CAED0 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | sid-8E374108-901D-4CF5-80EA-B6B4C30B3BE0 47 | sid-9AE25A5F-D224-40C4-9E9A-4548440BABBC 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | sid-561AE46F-B492-4175-A227-49CF380CAED0 56 | sid-BE1B3A8A-8FB0-4DEA-8FAE-811767F6287C 57 | 58 | 59 | 60 | 61 | 62 | sid-BE1B3A8A-8FB0-4DEA-8FAE-811767F6287C 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | x == 1 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /tests/examples/xml_import_export/signavio_simple_example.bpmn: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | sid-D4C8D513-A3D2-430F-836C-930BE005590E 13 | sid-28CB40A0-E0AD-4C80-8BAD-2499511C4516 14 | 15 | 16 | 17 | 18 | 19 | sid-D4C8D513-A3D2-430F-836C-930BE005590E 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | sid-ED4CB419-74A8-48D8-A512-E590069C69CC 28 | sid-A439209E-6F49-4B01-A3F6-B1EF6CE59433 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | sid-8CA3CBB6-5B23-48F3-9AF4-B2CEBE1E03C9 37 | sid-67F91195-7B66-4198-9C8A-7FF8D2892E9F 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | sid-4604D1B4-A8F1-4664-8953-063F1CB53E8D 46 | sid-1C498761-8113-4E90-9B0B-3EAD775FED3F 47 | 48 | 49 | 50 | 51 | 52 | sid-1C498761-8113-4E90-9B0B-3EAD775FED3F 53 | 54 | 55 | 56 | 57 | 58 | sid-28CB40A0-E0AD-4C80-8BAD-2499511C4516 59 | sid-ED4CB419-74A8-48D8-A512-E590069C69CC 60 | sid-8CA3CBB6-5B23-48F3-9AF4-B2CEBE1E03C9 61 | 62 | 63 | 64 | 65 | 66 | sid-A439209E-6F49-4B01-A3F6-B1EF6CE59433 67 | sid-67F91195-7B66-4198-9C8A-7FF8D2892E9F 68 | sid-4604D1B4-A8F1-4664-8953-063F1CB53E8D 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /tests/layouter/layouter_test.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Test unit, using simple graph made in BPMNEditor editor for import/export operation 4 | """ 5 | import unittest 6 | 7 | import bpmn_python.bpmn_diagram_layouter as layouter 8 | import bpmn_python.bpmn_diagram_rep as diagram 9 | 10 | class BPMNEditorTests(unittest.TestCase): 11 | """ 12 | This class contains test for bpmn-python package functionality using an example BPMN diagram created in BPMNEditor. 13 | """ 14 | output_directory = "./output/layouter/" 15 | 16 | def test_layouter_manually_created_diagram_simple_case(self): 17 | """ 18 | Test for importing a simple BPMNEditor diagram example (as BPMN 2.0 XML) into inner representation 19 | and generating layout for it 20 | """ 21 | output_file = "layouter_simple_case.xml" 22 | bpmn_graph = diagram.BpmnDiagramGraph() 23 | bpmn_graph.create_new_diagram_graph(diagram_name="diagram1") 24 | process_id = bpmn_graph.add_process_to_diagram() 25 | [start_id, _] = bpmn_graph.add_start_event_to_diagram(process_id, start_event_name="start_event") 26 | [task1_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="task1") 27 | bpmn_graph.add_sequence_flow_to_diagram(process_id, start_id, task1_id, "start_to_one") 28 | 29 | [task2_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="task2") 30 | [end_id, _] = bpmn_graph.add_end_event_to_diagram(process_id, end_event_name="end_event") 31 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task1_id, task2_id, "one_to_two") 32 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task2_id, end_id, "two_to_end") 33 | 34 | layouter.generate_layout(bpmn_graph) 35 | bpmn_graph.export_xml_file(self.output_directory, output_file) 36 | 37 | def test_layouter_manually_created_diagram_split_join_case(self): 38 | """ 39 | Test for importing a simple BPMNEditor diagram example (as BPMN 2.0 XML) into inner representation 40 | and generating layout for it 41 | """ 42 | output_file = "layouter_split_join_case.xml" 43 | bpmn_graph = diagram.BpmnDiagramGraph() 44 | bpmn_graph.create_new_diagram_graph(diagram_name="diagram1") 45 | process_id = bpmn_graph.add_process_to_diagram() 46 | [start_id, _] = bpmn_graph.add_start_event_to_diagram(process_id, start_event_name="start_event") 47 | [task1_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="task1") 48 | bpmn_graph.add_sequence_flow_to_diagram(process_id, start_id, task1_id, "start_to_one") 49 | 50 | [exclusive_gate_fork_id, _] = bpmn_graph.add_exclusive_gateway_to_diagram(process_id, 51 | gateway_name="exclusive_gate_fork") 52 | [task1_ex_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="task1_ex") 53 | [task2_ex_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="task2_ex") 54 | [exclusive_gate_join_id, _] = bpmn_graph.add_exclusive_gateway_to_diagram(process_id, 55 | gateway_name="exclusive_gate_join") 56 | 57 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task1_id, exclusive_gate_fork_id, "one_to_ex_fork") 58 | bpmn_graph.add_sequence_flow_to_diagram(process_id, exclusive_gate_fork_id, task1_ex_id, "ex_fork_to_ex_one") 59 | bpmn_graph.add_sequence_flow_to_diagram(process_id, exclusive_gate_fork_id, task2_ex_id, "ex_fork_to_ex_two") 60 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task1_ex_id, exclusive_gate_join_id, "ex_one_to_ex_join") 61 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task2_ex_id, exclusive_gate_join_id, "ex_two_to_ex_join") 62 | 63 | [task2_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="task2") 64 | [end_id, _] = bpmn_graph.add_end_event_to_diagram(process_id, end_event_name="end_event") 65 | bpmn_graph.add_sequence_flow_to_diagram(process_id, exclusive_gate_join_id, task2_id, "ex_join_to_two") 66 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task2_id, end_id, "two_to_end") 67 | 68 | layouter.generate_layout(bpmn_graph) 69 | bpmn_graph.export_xml_file(self.output_directory, output_file) 70 | 71 | def test_layouter_manually_created_diagram_cycle_case(self): 72 | """ 73 | Test for importing a simple BPMNEditor diagram example (as BPMN 2.0 XML) into inner representation 74 | and generating layout for it 75 | """ 76 | output_file = "layouter_cycle_case.xml" 77 | bpmn_graph = diagram.BpmnDiagramGraph() 78 | bpmn_graph.create_new_diagram_graph(diagram_name="diagram1") 79 | process_id = bpmn_graph.add_process_to_diagram() 80 | [start_id, _] = bpmn_graph.add_start_event_to_diagram(process_id, start_event_name="start_event") 81 | [task1_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="task1") 82 | bpmn_graph.add_sequence_flow_to_diagram(process_id, start_id, task1_id, "start_to_one") 83 | 84 | [exclusive_gate_fork_id, _] = bpmn_graph.add_exclusive_gateway_to_diagram(process_id, 85 | gateway_name="exclusive_gate_fork") 86 | [task1_ex_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="task1_ex") 87 | [task2_ex_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="task2_ex") 88 | [exclusive_gate_join_id, _] = bpmn_graph.add_exclusive_gateway_to_diagram(process_id, 89 | gateway_name="exclusive_gate_join") 90 | 91 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task1_id, exclusive_gate_fork_id, "one_to_ex_fork") 92 | bpmn_graph.add_sequence_flow_to_diagram(process_id, exclusive_gate_fork_id, task1_ex_id, "ex_fork_to_ex_one") 93 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task2_ex_id, exclusive_gate_fork_id, "ex_two_to_ex_fork") 94 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task1_ex_id, exclusive_gate_join_id, "ex_one_to_ex_join") 95 | bpmn_graph.add_sequence_flow_to_diagram(process_id, exclusive_gate_join_id, task2_ex_id, "ex_join_to_ex_two") 96 | 97 | [task2_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="task2") 98 | [end_id, _] = bpmn_graph.add_end_event_to_diagram(process_id, end_event_name="end_event") 99 | bpmn_graph.add_sequence_flow_to_diagram(process_id, exclusive_gate_join_id, task2_id, "ex_join_to_two") 100 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task2_id, end_id, "two_to_end") 101 | 102 | layouter.generate_layout(bpmn_graph) 103 | bpmn_graph.export_xml_file(self.output_directory, output_file) 104 | 105 | 106 | if __name__ == '__main__': 107 | unittest.main() 108 | -------------------------------------------------------------------------------- /tests/metrics/layout_metrics_test.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Layout metrics computing tests 4 | """ 5 | import os 6 | import unittest 7 | 8 | import bpmn_python.diagram_layout_metrics as metrics 9 | import bpmn_python.bpmn_diagram_rep as diagram 10 | 11 | 12 | class MetricsTests(unittest.TestCase): 13 | crossing_points_example_path = "../examples/metrics/crossing_point_test.bpmn" 14 | cycles_example_path = "../examples/metrics/cycles_test.bpmn" 15 | 16 | @staticmethod 17 | def load_example_diagram(filepath): 18 | bpmn_graph = diagram.BpmnDiagramGraph() 19 | bpmn_graph.load_diagram_from_xml_file(os.path.abspath(filepath)) 20 | return bpmn_graph 21 | 22 | def test_count_crossing_points(self): 23 | bpmn_graph = MetricsTests.load_example_diagram(self.crossing_points_example_path) 24 | cross_points = metrics.count_crossing_points(bpmn_graph) 25 | self.assertEqual(cross_points, 6, "Crossing points count does not match") 26 | 27 | def test_count_segments(self): 28 | bpmn_graph = MetricsTests.load_example_diagram(self.crossing_points_example_path) 29 | segments_count = metrics.count_segments(bpmn_graph) 30 | self.assertEqual(segments_count, 25, "Segments count does not match") 31 | 32 | def test_compute_longest_path(self): 33 | bpmn_graph = MetricsTests.load_example_diagram(self.cycles_example_path) 34 | (longest_path, longest_path_len) = metrics.compute_longest_path(bpmn_graph) 35 | self.assertEqual(longest_path_len, 9, "Path length does not match") 36 | 37 | def test_compute_longest_path_tasks(self): 38 | bpmn_graph = MetricsTests.load_example_diagram(self.cycles_example_path) 39 | (longest_path, longest_path_len) = metrics.compute_longest_path_tasks(bpmn_graph) 40 | self.assertEqual(longest_path_len, 6, "Path length does not match") 41 | 42 | 43 | if __name__ == '__main__': 44 | unittest.main() 45 | -------------------------------------------------------------------------------- /tests/metrics/test_bpmn_diagram_metrics.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Unit tests to test the complexity metrics implemented of BPMN models 4 | """ 5 | import os 6 | import unittest 7 | 8 | from bpmn_python import bpmn_diagram_metrics as metrics 9 | from bpmn_python.bpmn_diagram_rep import BpmnDiagramGraph 10 | 11 | 12 | class BPMNComplexityMetricsTests(unittest.TestCase): 13 | 14 | def setUp(self): 15 | 16 | self.models = {name: BpmnDiagramGraph() 17 | for name in ['SIMPLE', 'COMPLEX', 'WITH_CYCLES', 'WITH_CROSSING_POINT'] 18 | } 19 | 20 | self.models['SIMPLE'].load_diagram_from_xml_file( 21 | os.path.abspath("../examples/xml_import_export/bpmn_editor_simple_example.xml")), 22 | self.models['COMPLEX'].load_diagram_from_xml_file( 23 | os.path.abspath("../examples/xml_import_export/camunda_complex_example.bpmn")), 24 | self.models['WITH_CYCLES'].load_diagram_from_xml_file( 25 | os.path.abspath("../examples/metrics/cycles_test.bpmn")), 26 | self.models['WITH_CROSSING_POINT'].load_diagram_from_xml_file( 27 | os.path.abspath("../examples/metrics/crossing_point_test.bpmn")), 28 | 29 | def testTNSEMetricForSimpleModel(self): 30 | 31 | self.assertEqual( 32 | metrics.TNSE_metric(self.models['SIMPLE']), 1 33 | ) 34 | 35 | def testTNSEMetricForComplexModel(self): 36 | 37 | self.assertEqual( 38 | metrics.TNSE_metric(self.models['COMPLEX']), 1 39 | ) 40 | 41 | def testTNSEForModelWithCycles(self): 42 | 43 | self.assertEqual( 44 | metrics.TNSE_metric(self.models['WITH_CYCLES']), 1 45 | ) 46 | 47 | def testTNSEForModelWithCrossingPoint(self): 48 | self.assertEqual( 49 | metrics.TNSE_metric(self.models['WITH_CROSSING_POINT']), 1 50 | ) 51 | 52 | def testTNIEMetricForSimpleModel(self): 53 | 54 | self.assertEqual( 55 | metrics.TNIE_metric(self.models['SIMPLE']), 0 56 | ) 57 | 58 | def testTNIEMetricForComplexModel(self): 59 | 60 | self.assertEqual( 61 | metrics.TNIE_metric(self.models['COMPLEX']), 2 62 | ) 63 | 64 | def testTNIEForModelWithCycles(self): 65 | 66 | self.assertEqual( 67 | metrics.TNIE_metric(self.models['WITH_CYCLES']), 0 68 | ) 69 | 70 | def testTNIEForModelWithCrossingPoint(self): 71 | self.assertEqual( 72 | metrics.TNIE_metric(self.models['WITH_CROSSING_POINT']), 0 73 | ) 74 | 75 | def testTNEEMetricForSimpleModel(self): 76 | 77 | self.assertEqual( 78 | metrics.TNEE_metric(self.models['SIMPLE']), 1 79 | ) 80 | 81 | def testTNEEMetricForComplexModel(self): 82 | 83 | self.assertEqual( 84 | metrics.TNEE_metric(self.models['COMPLEX']), 1 85 | ) 86 | 87 | def testTNEEForModelWithCycles(self): 88 | 89 | self.assertEqual( 90 | metrics.TNEE_metric(self.models['WITH_CYCLES']), 2 91 | ) 92 | 93 | def testTNEEForModelWithCrossingPoint(self): 94 | self.assertEqual( 95 | metrics.TNEE_metric(self.models['WITH_CROSSING_POINT']), 1 96 | ) 97 | 98 | def testTNEMetricForSimpleModel(self): 99 | 100 | self.assertEqual( 101 | metrics.TNE_metric(self.models['SIMPLE']), 2 102 | ) 103 | 104 | def testTNEMetricForComplexModel(self): 105 | 106 | self.assertEqual( 107 | metrics.TNE_metric(self.models['COMPLEX']), 4 108 | ) 109 | 110 | def testTNEForModelWithCycles(self): 111 | 112 | self.assertEqual( 113 | metrics.TNE_metric(self.models['WITH_CYCLES']), 3 114 | ) 115 | 116 | def testTNEForModelWithCrossingPoint(self): 117 | self.assertEqual( 118 | metrics.TNE_metric(self.models['WITH_CROSSING_POINT']), 2 119 | ) 120 | 121 | def testNOAMetricForSimpleModel(self): 122 | 123 | self.assertEqual( 124 | metrics.NOA_metric(self.models['SIMPLE']), 4 125 | ) 126 | 127 | def testNOAMetricForComplexModel(self): 128 | 129 | self.assertEqual( 130 | metrics.NOA_metric(self.models['COMPLEX']), 9 131 | ) 132 | 133 | def testNOAMetricForModelWithCycles(self): 134 | self.assertEqual( 135 | metrics.NOA_metric(self.models['WITH_CYCLES']), 11 136 | ) 137 | 138 | def testNOAMetricForModelWithCrossingPoint(self): 139 | self.assertEqual( 140 | metrics.NOA_metric(self.models['WITH_CROSSING_POINT']), 3 141 | ) 142 | 143 | def testNOACMetricForSimpleModel(self): 144 | self.assertEqual( 145 | metrics.NOAC_metric(self.models['SIMPLE']), 8 146 | ) 147 | 148 | def testNOACMetricForComplexModel(self): 149 | self.assertEqual( 150 | metrics.NOAC_metric(self.models['COMPLEX']), 21 151 | ) 152 | 153 | def testNOACMetricForModelWithCycles(self): 154 | self.assertEqual( 155 | metrics.NOAC_metric(self.models['WITH_CYCLES']), 16 156 | ) 157 | 158 | def testNOACMetricForModelWithCrossingPoint(self): 159 | self.assertEqual( 160 | metrics.NOAC_metric(self.models['WITH_CROSSING_POINT']), 7 161 | ) 162 | 163 | def testNOAJSMetricForSimpleModel(self): 164 | self.assertEqual( 165 | metrics.NOAJS_metric(self.models['SIMPLE']), 6 166 | ) 167 | 168 | def testNOAJSMetricForComplexModel(self): 169 | self.assertEqual( 170 | metrics.NOAJS_metric(self.models['COMPLEX']), 17 171 | ) 172 | 173 | def testNOAJSMetricForModelWithCycles(self): 174 | self.assertEqual( 175 | metrics.NOAJS_metric(self.models['WITH_CYCLES']), 13 176 | ) 177 | 178 | def testNOAJSMetricForModelWithCrossingPoint(self): 179 | self.assertEqual( 180 | metrics.NOAJS_metric(self.models['WITH_CROSSING_POINT']), 5 181 | ) 182 | 183 | def testNumberOfNodesForSimpleModel(self): 184 | 185 | self.assertEqual( 186 | metrics.NumberOfNodes_metric(self.models['SIMPLE']), 8 187 | ) 188 | 189 | def testNumberOfNodesForComplexModel(self): 190 | 191 | self.assertEqual( 192 | metrics.NumberOfNodes_metric(self.models['COMPLEX']), 21 193 | ) 194 | 195 | def testNumberOfNodesForModelWithCycles(self): 196 | self.assertEqual( 197 | metrics.NumberOfNodes_metric(self.models['WITH_CYCLES']), 16 198 | ) 199 | 200 | def testNumberOfNodesForModelWithCrossingPoint(self): 201 | self.assertEqual( 202 | metrics.NumberOfNodes_metric(self.models['WITH_CROSSING_POINT']), 7 203 | ) 204 | 205 | def testGatewayHeterogenityMetricForSimpleModel(self): 206 | 207 | self.assertEqual( 208 | metrics.GatewayHeterogenity_metric(self.models['SIMPLE']), 1 209 | ) 210 | 211 | def testGatewayHeterogenityMetricForComplexModel(self): 212 | 213 | self.assertEqual( 214 | metrics.GatewayHeterogenity_metric(self.models['COMPLEX']), 4 215 | ) 216 | 217 | def testGatewayHeterogenityMetricForModelWithCycles(self): 218 | self.assertEqual( 219 | metrics.GatewayHeterogenity_metric(self.models['WITH_CYCLES']), 1 220 | ) 221 | 222 | def testGatewayHeterogenityMetricForModelWithCrossingPoint(self): 223 | self.assertEqual( 224 | metrics.GatewayHeterogenity_metric(self.models['WITH_CROSSING_POINT']), 1 225 | ) 226 | 227 | def testCoefficientOfNetworkComplexityMetricForSimpleModel(self): 228 | 229 | self.assertAlmostEqual( 230 | metrics.CoefficientOfNetworkComplexity_metric(self.models['SIMPLE']), 1.0, 231 | places=3 232 | ) 233 | 234 | def testCoefficientOfNetworkComplexityMetricForComplexModel(self): 235 | 236 | self.assertAlmostEqual( 237 | metrics.CoefficientOfNetworkComplexity_metric(self.models['COMPLEX']), 1.143, 238 | places=3 239 | ) 240 | 241 | def testCoefficientOfNetworkComplexityMetricForModelWithCycles(self): 242 | self.assertAlmostEqual( 243 | metrics.CoefficientOfNetworkComplexity_metric(self.models['WITH_CYCLES']), 1.0, 244 | places=3 245 | ) 246 | 247 | def testCoefficientOfNetworkComplexityMetricForModelWithCrossingPoint(self): 248 | self.assertAlmostEqual( 249 | metrics.CoefficientOfNetworkComplexity_metric(self.models['WITH_CROSSING_POINT']), 1.0 250 | ) 251 | 252 | def testAverageGatewayDegreeMetricForSimpleModel(self): 253 | 254 | self.assertAlmostEqual( 255 | metrics.AverageGatewayDegree_metric(self.models['SIMPLE']), 3.0, 256 | places=3 257 | ) 258 | 259 | def testAverageGatewayDegreeMetricForComplexModel(self): 260 | 261 | self.assertAlmostEqual( 262 | metrics.AverageGatewayDegree_metric(self.models['COMPLEX']), 3.0, 263 | places=3 264 | ) 265 | 266 | def testAverageGatewayDegreeForModelWithCycles(self): 267 | self.assertAlmostEqual( 268 | metrics.AverageGatewayDegree_metric(self.models['WITH_CYCLES']), 3.5, 269 | places=3 270 | ) 271 | 272 | def testAverageGatewayDegreeForModelWithCrossingPoint(self): 273 | self.assertAlmostEqual( 274 | metrics.AverageGatewayDegree_metric(self.models['WITH_CROSSING_POINT']), 3.0 275 | ) 276 | 277 | def testDurfeeSquareMetricForSimpleModel(self): 278 | 279 | self.assertEqual( 280 | metrics.DurfeeSquare_metric(self.models['SIMPLE']), 2 281 | ) 282 | 283 | def testDurfeeSquareMetricForComplexModel(self): 284 | 285 | self.assertEqual( 286 | metrics.DurfeeSquare_metric(self.models['COMPLEX']), 2 287 | ) 288 | 289 | def testDurfeeSquareForModelWithCycles(self): 290 | 291 | self.assertEqual( 292 | metrics.DurfeeSquare_metric(self.models['WITH_CYCLES']), 2 293 | ) 294 | 295 | def testDurfeeSquareForModelWithCrossingPoint(self): 296 | self.assertEqual( 297 | metrics.DurfeeSquare_metric(self.models['WITH_CROSSING_POINT']), 2 298 | ) 299 | 300 | def testPerfectSquareMetricForSimpleModel(self): 301 | 302 | self.assertEqual( 303 | metrics.PerfectSquare_metric(self.models['SIMPLE']), 2 304 | ) 305 | 306 | def testPerfectSquareMetricForComplexModel(self): 307 | 308 | self.assertEqual( 309 | metrics.PerfectSquare_metric(self.models['COMPLEX']), 3 310 | ) 311 | 312 | def testPerfectSquareForModelWithCycles(self): 313 | 314 | self.assertEqual( 315 | metrics.PerfectSquare_metric(self.models['WITH_CYCLES']), 4 316 | ) 317 | 318 | def testPerfectSquareForModelWithCrossingPoint(self): 319 | self.assertEqual( 320 | metrics.PerfectSquare_metric(self.models['WITH_CROSSING_POINT']), 2 321 | ) 322 | 323 | def tearDown(self): 324 | pass 325 | 326 | 327 | if __name__ == '__main__': 328 | unittest.main() 329 | -------------------------------------------------------------------------------- /tests/xml_import_export/bpmn_editor_import_test.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Test unit, using simple graph made in BPMNEditor editor for import/export operation 4 | """ 5 | import os 6 | import unittest 7 | 8 | import bpmn_python.bpmn_diagram_visualizer as visualizer 9 | import bpmn_python.bpmn_diagram_rep as diagram 10 | 11 | 12 | class BPMNEditorTests(unittest.TestCase): 13 | """ 14 | This class contains test for bpmn-python package functionality using an example BPMN diagram created in BPMNEditor. 15 | """ 16 | output_directory = "./output/test-bpmneditor/" 17 | example_path = "../examples/xml_import_export/bpmn_editor_simple_example.xml" 18 | output_file_with_di = "BPMNEditor-example-output.xml" 19 | output_file_no_di = "BPMNEditor-example-output-no-di.xml" 20 | output_dot_file = "BPMNEditor-example" 21 | output_png_file = "BPMNEditor-example" 22 | 23 | def test_loadBPMNEditorDiagram(self): 24 | """ 25 | Test for importing a simple BPMNEditor diagram example (as BPMN 2.0 XML) into inner representation 26 | and later exporting it to XML file 27 | """ 28 | bpmn_graph = diagram.BpmnDiagramGraph() 29 | bpmn_graph.load_diagram_from_xml_file(os.path.abspath(self.example_path)) 30 | bpmn_graph.export_xml_file(self.output_directory, self.output_file_with_di) 31 | bpmn_graph.export_xml_file_no_di(self.output_directory, self.output_file_no_di) 32 | 33 | def test_loadBPMNEditorDiagramAndVisualize(self): 34 | """ 35 | Test for importing a simple BPMNEditor diagram example (as BPMN 2.0 XML) into inner representation 36 | and later exporting it to XML file. Includes test for visualization functionality. 37 | """ 38 | bpmn_graph = diagram.BpmnDiagramGraph() 39 | bpmn_graph.load_diagram_from_xml_file(os.path.abspath(self.example_path)) 40 | # Uncomment line below to get a simple view of created diagram 41 | # visualizer.visualize_diagram(bpmn_graph) 42 | visualizer.bpmn_diagram_to_dot_file(bpmn_graph, self.output_directory + self.output_dot_file) 43 | visualizer.bpmn_diagram_to_png(bpmn_graph, self.output_directory + self.output_png_file) 44 | bpmn_graph.export_xml_file(self.output_directory, self.output_file_with_di) 45 | bpmn_graph.export_xml_file_no_di(self.output_directory, self.output_file_no_di) 46 | 47 | 48 | if __name__ == '__main__': 49 | unittest.main() 50 | -------------------------------------------------------------------------------- /tests/xml_import_export/camunda_complex_test.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Test unit, using more complex graph made in bpmn.io editor for import/export operation 4 | """ 5 | import os 6 | import unittest 7 | 8 | import bpmn_python.bpmn_diagram_visualizer as visualizer 9 | import bpmn_python.bpmn_diagram_rep as diagram 10 | 11 | 12 | class CamundaComplexTests(unittest.TestCase): 13 | """ 14 | This class contains test for bpmn-python package functionality using a complex example of BPMN diagram 15 | created in bpmn-io (Camunda library implementation). 16 | """ 17 | output_directory = "./output/test-camunda/complex/" 18 | example_path = "../examples/xml_import_export/camunda_complex_example.bpmn" 19 | output_file_with_di = "camunda-complex-example-output.xml" 20 | output_file_no_di = "camunda-complex-example-output-no-di.xml" 21 | output_dot_file = "camunda-complex-example" 22 | output_png_file = "camunda-complex-example" 23 | 24 | def test_loadCamundaComplexDiagram(self): 25 | """ 26 | Test for importing a complex Camunda diagram example (as BPMN 2.0 XML) into inner representation 27 | and later exporting it to XML file 28 | """ 29 | bpmn_graph = diagram.BpmnDiagramGraph() 30 | bpmn_graph.load_diagram_from_xml_file(os.path.abspath(self.example_path)) 31 | bpmn_graph.export_xml_file(self.output_directory, self.output_file_with_di) 32 | bpmn_graph.export_xml_file_no_di(self.output_directory, self.output_file_no_di) 33 | 34 | def test_loadCamundaComplexDiagramAndVisualize(self): 35 | """ 36 | Test for importing a complex Camunda diagram example (as BPMN 2.0 XML) into inner representation 37 | and later exporting it to XML file. Includes test for visualization functionality. 38 | """ 39 | bpmn_graph = diagram.BpmnDiagramGraph() 40 | bpmn_graph.load_diagram_from_xml_file(os.path.abspath(self.example_path)) 41 | # Uncomment line below to get a simple view of created diagram 42 | # visualizer.visualize_diagram(bpmn_graph) 43 | visualizer.bpmn_diagram_to_dot_file(bpmn_graph, self.output_directory + self.output_dot_file) 44 | visualizer.bpmn_diagram_to_png(bpmn_graph, self.output_directory + self.output_png_file) 45 | bpmn_graph.export_xml_file(self.output_directory, self.output_file_with_di) 46 | bpmn_graph.export_xml_file_no_di(self.output_directory, self.output_file_no_di) 47 | 48 | if __name__ == '__main__': 49 | unittest.main() 50 | -------------------------------------------------------------------------------- /tests/xml_import_export/camunda_simple_test.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Test unit, using simple graph made in bpmn.io editor for import/export operation 4 | """ 5 | import os 6 | import unittest 7 | 8 | import bpmn_python.bpmn_diagram_visualizer as visualizer 9 | import bpmn_python.bpmn_diagram_rep as diagram 10 | 11 | 12 | class CamundaSimpleTests(unittest.TestCase): 13 | """ 14 | This class contains test for bpmn-python package functionality using a simple example of BPMN diagram 15 | created in bpmn-io (Camunda library implementation). 16 | """ 17 | output_directory = "./output/test-camunda/simple/" 18 | example_path = "../examples/xml_import_export/camunda_simple_example.bpmn" 19 | output_file_with_di = "camunda-example-output.xml" 20 | output_file_no_di = "camunda-example-output-no-di.xml" 21 | output_dot_file = "camunda-example" 22 | output_png_file = "camunda-example" 23 | 24 | def test_loadCamundaSimpleDiagram(self): 25 | """ 26 | Test for importing a simple Camunda diagram example (as BPMN 2.0 XML) into inner representation 27 | and later exporting it to XML file 28 | """ 29 | bpmn_graph = diagram.BpmnDiagramGraph() 30 | bpmn_graph.load_diagram_from_xml_file(os.path.abspath(self.example_path)) 31 | bpmn_graph.export_xml_file(self.output_directory, self.output_file_with_di) 32 | bpmn_graph.export_xml_file_no_di(self.output_directory, self.output_file_no_di) 33 | 34 | def test_loadCamundaSimpleDiagramAndVisualize(self): 35 | """ 36 | Test for importing a simple Camunda diagram example (as BPMN 2.0 XML) into inner representation 37 | and later exporting it to XML file. Includes test for visualization functionality. 38 | """ 39 | bpmn_graph = diagram.BpmnDiagramGraph() 40 | bpmn_graph.load_diagram_from_xml_file(os.path.abspath(self.example_path)) 41 | # Uncomment line below to get a simple view of created diagram 42 | # visualizer.visualize_diagram(bpmn_graph) 43 | visualizer.bpmn_diagram_to_dot_file(bpmn_graph, self.output_directory + self.output_dot_file) 44 | visualizer.bpmn_diagram_to_png(bpmn_graph, self.output_directory + self.output_png_file) 45 | bpmn_graph.export_xml_file(self.output_directory, self.output_file_with_di) 46 | bpmn_graph.export_xml_file_no_di(self.output_directory, self.output_file_no_di) 47 | 48 | if __name__ == '__main__': 49 | unittest.main() 50 | -------------------------------------------------------------------------------- /tests/xml_import_export/default_conditional_flow_test.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Test unit, using simple graph made in BPMNEditor editor for import/export operation 4 | """ 5 | import os 6 | import unittest 7 | 8 | import bpmn_python.bpmn_diagram_rep as diagram 9 | 10 | 11 | class DefaultConditionalFlowTests(unittest.TestCase): 12 | """ 13 | This class contains test for bpmn-python package functionality using an example BPMN diagram created in BPMNEditor. 14 | """ 15 | output_directory = "./output/test-flows/" 16 | example_path = "../examples/xml_import_export/default-conditional-flow-example.bpmn" 17 | output_file = "default-conditional-flow-example.bpmn" 18 | 19 | def test_loadBPMNEditorDiagramAndVisualize(self): 20 | """ 21 | Test for importing a simple BPMNEditor diagram example (as BPMN 2.0 XML) into inner representation 22 | and later exporting it to XML file. Includes test for visualization functionality. 23 | """ 24 | bpmn_graph = diagram.BpmnDiagramGraph() 25 | bpmn_graph.load_diagram_from_xml_file(os.path.abspath(self.example_path)) 26 | bpmn_graph.export_xml_file(self.output_directory, self.output_file) 27 | 28 | if __name__ == '__main__': 29 | unittest.main() 30 | -------------------------------------------------------------------------------- /tests/xml_import_export/lanes_test.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Test unit, using simple graph made in Signavio editor for import/export operation 4 | """ 5 | import os 6 | import unittest 7 | 8 | import bpmn_python.bpmn_diagram_rep as diagram 9 | 10 | 11 | class BPMNEditorTests(unittest.TestCase): 12 | """ 13 | This class contains test for bpmn-python package functionality using an example BPMN diagram, which contains 14 | multiple pool and lane elements. 15 | """ 16 | output_directory = "./output/test-lane/" 17 | example_path = "../examples/xml_import_export/lanes.bpmn" 18 | output_file_with_di = "lanes-example-output.xml" 19 | output_file_no_di = "lanes-example-output-no-di.xml" 20 | 21 | def test_load_lanes_example(self): 22 | """ 23 | Test for importing a simple BPMNEditor diagram example (as BPMN 2.0 XML) into inner representation 24 | and later exporting it to XML file 25 | """ 26 | bpmn_graph = diagram.BpmnDiagramGraph() 27 | bpmn_graph.load_diagram_from_xml_file(os.path.abspath(self.example_path)) 28 | bpmn_graph.export_xml_file(self.output_directory, self.output_file_with_di) 29 | bpmn_graph.export_xml_file_no_di(self.output_directory, self.output_file_no_di) 30 | 31 | if __name__ == '__main__': 32 | unittest.main() 33 | -------------------------------------------------------------------------------- /tests/xml_import_export/manually_generated_complex_diagram.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Test unit, creates a more complex graph using functions provided by package and exports it to XML and graphic format 4 | """ 5 | import unittest 6 | 7 | import bpmn_python.bpmn_diagram_layouter as layouter 8 | import bpmn_python.bpmn_diagram_visualizer as visualizer 9 | import bpmn_python.bpmn_diagram_rep as diagram 10 | 11 | 12 | class ManualGenerationComplexTests(unittest.TestCase): 13 | """ 14 | This class contains test for manual diagram generation functionality. 15 | """ 16 | output_directory = "./output/test-manual/complex/" 17 | output_file_with_di = "manually-generated-complex-output.xml" 18 | output_file_no_di = "manually-generated-complex-output-no-di.xml" 19 | output_dot_file = "manually-generated-example" 20 | output_png_file = "manually-generated-example" 21 | 22 | def test_create_diagram_manually(self): 23 | bpmn_graph = diagram.BpmnDiagramGraph() 24 | bpmn_graph.create_new_diagram_graph(diagram_name="diagram1") 25 | process_id = bpmn_graph.add_process_to_diagram() 26 | 27 | [start_id, _] = bpmn_graph.add_start_event_to_diagram(process_id, start_event_name="start_event") 28 | [task1_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="First task") 29 | [subprocess1_id, _] = bpmn_graph.add_subprocess_to_diagram(process_id, subprocess_name="Subprocess") 30 | bpmn_graph.add_sequence_flow_to_diagram(process_id, start_id, task1_id) 31 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task1_id, subprocess1_id) 32 | 33 | [parallel_gate_fork_id, _] = bpmn_graph.add_parallel_gateway_to_diagram(process_id, 34 | gateway_name="parallel_gate_fork") 35 | [task1_par_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="task1_par") 36 | [task2_par_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="task2_par") 37 | [parallel_gate_join_id, _] = bpmn_graph.add_parallel_gateway_to_diagram(process_id, 38 | gateway_name="parallel_gate_join") 39 | 40 | bpmn_graph.add_sequence_flow_to_diagram(process_id, subprocess1_id, parallel_gate_fork_id) 41 | bpmn_graph.add_sequence_flow_to_diagram(process_id, parallel_gate_fork_id, task1_par_id) 42 | bpmn_graph.add_sequence_flow_to_diagram(process_id, parallel_gate_fork_id, task2_par_id) 43 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task1_par_id, parallel_gate_join_id) 44 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task2_par_id, parallel_gate_join_id) 45 | 46 | [exclusive_gate_fork_id, _] = bpmn_graph.add_exclusive_gateway_to_diagram(process_id, 47 | gateway_name="exclusive_gate_fork") 48 | [task1_ex_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="task1_ex") 49 | [task2_ex_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="task2_ex") 50 | [exclusive_gate_join_id, _] = bpmn_graph.add_exclusive_gateway_to_diagram(process_id, 51 | gateway_name="exclusive_gate_join") 52 | 53 | bpmn_graph.add_sequence_flow_to_diagram(process_id, parallel_gate_join_id, exclusive_gate_fork_id) 54 | bpmn_graph.add_sequence_flow_to_diagram(process_id, exclusive_gate_fork_id, task1_ex_id) 55 | bpmn_graph.add_sequence_flow_to_diagram(process_id, exclusive_gate_fork_id, task2_ex_id) 56 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task1_ex_id, exclusive_gate_join_id) 57 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task2_ex_id, exclusive_gate_join_id) 58 | 59 | [inclusive_gate_fork_id, _] = bpmn_graph.add_inclusive_gateway_to_diagram(process_id, 60 | gateway_name="inclusive_gate_fork") 61 | [task1_in_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="task1_in") 62 | [task2_in_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="task2_in") 63 | [inclusive_gate_join_id, _] = bpmn_graph.add_inclusive_gateway_to_diagram(process_id, 64 | gateway_name="inclusive_gate_join") 65 | 66 | bpmn_graph.add_sequence_flow_to_diagram(process_id, exclusive_gate_join_id, inclusive_gate_fork_id) 67 | bpmn_graph.add_sequence_flow_to_diagram(process_id, inclusive_gate_fork_id, task1_in_id) 68 | bpmn_graph.add_sequence_flow_to_diagram(process_id, inclusive_gate_fork_id, task2_in_id) 69 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task1_in_id, inclusive_gate_join_id) 70 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task2_in_id, inclusive_gate_join_id) 71 | 72 | [end_id, _] = bpmn_graph.add_end_event_to_diagram(process_id, end_event_name="end_event") 73 | bpmn_graph.add_sequence_flow_to_diagram(process_id, inclusive_gate_join_id, end_id) 74 | 75 | layouter.generate_layout(bpmn_graph) 76 | 77 | bpmn_graph.export_xml_file(self.output_directory, self.output_file_with_di) 78 | bpmn_graph.export_xml_file_no_di(self.output_directory, self.output_file_no_di) 79 | # Uncomment line below to get a simple view of created diagram 80 | # visualizer.visualize_diagram(bpmn_graph) 81 | visualizer.bpmn_diagram_to_dot_file(bpmn_graph, self.output_directory + self.output_dot_file) 82 | visualizer.bpmn_diagram_to_png(bpmn_graph, self.output_directory + self.output_png_file) 83 | 84 | if __name__ == '__main__': 85 | unittest.main() 86 | -------------------------------------------------------------------------------- /tests/xml_import_export/manually_generated_simple_diagram.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Test unit, creates a simple graph using functions provided by package and exports it to XML and graphic format 4 | """ 5 | import unittest 6 | 7 | import bpmn_python.bpmn_diagram_layouter as layouter 8 | import bpmn_python.bpmn_diagram_visualizer as visualizer 9 | import bpmn_python.bpmn_diagram_rep as diagram 10 | 11 | 12 | class ManualGenerationSimpleTests(unittest.TestCase): 13 | """ 14 | This class contains test for manual diagram generation functionality. 15 | """ 16 | output_directory = "./output/test-manual/simple/" 17 | output_file_with_di = "manually-generated-output.xml" 18 | output_file_no_di = "manually-generated-output-no-di.xml" 19 | output_dot_file = "manually-generated-example" 20 | output_png_file = "manually-generated-example" 21 | 22 | def test_create_diagram_manually(self): 23 | bpmn_graph = diagram.BpmnDiagramGraph() 24 | bpmn_graph.create_new_diagram_graph(diagram_name="diagram1") 25 | process_id = bpmn_graph.add_process_to_diagram() 26 | [start_id, _] = bpmn_graph.add_start_event_to_diagram(process_id, start_event_name="start_event", 27 | start_event_definition="timer") 28 | [task1_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="task1") 29 | bpmn_graph.add_sequence_flow_to_diagram(process_id, start_id, task1_id, "start_to_one") 30 | 31 | [exclusive_gate_fork_id, _] = bpmn_graph.add_exclusive_gateway_to_diagram(process_id, 32 | gateway_name="exclusive_gate_fork") 33 | [task1_ex_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="task1_ex") 34 | [task2_ex_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="task2_ex") 35 | [exclusive_gate_join_id, _] = bpmn_graph.add_exclusive_gateway_to_diagram(process_id, 36 | gateway_name="exclusive_gate_join") 37 | 38 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task1_id, exclusive_gate_fork_id, "one_to_ex_fork") 39 | bpmn_graph.add_sequence_flow_to_diagram(process_id, exclusive_gate_fork_id, task1_ex_id, "ex_fork_to_ex_one") 40 | bpmn_graph.add_sequence_flow_to_diagram(process_id, exclusive_gate_fork_id, task2_ex_id, "ex_fork_to_ex_two") 41 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task1_ex_id, exclusive_gate_join_id, "ex_one_to_ex_join") 42 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task2_ex_id, exclusive_gate_join_id, "ex_two_to_ex_join") 43 | 44 | [task2_id, _] = bpmn_graph.add_task_to_diagram(process_id, task_name="task2") 45 | [end_id, _] = bpmn_graph.add_end_event_to_diagram(process_id, end_event_name="end_event", 46 | end_event_definition="message") 47 | bpmn_graph.add_sequence_flow_to_diagram(process_id, exclusive_gate_join_id, task2_id, "ex_join_to_two") 48 | bpmn_graph.add_sequence_flow_to_diagram(process_id, task2_id, end_id, "two_to_end") 49 | 50 | layouter.generate_layout(bpmn_graph) 51 | 52 | bpmn_graph.export_xml_file(self.output_directory, self.output_file_with_di) 53 | bpmn_graph.export_xml_file_no_di(self.output_directory, self.output_file_no_di) 54 | # Uncomment line below to get a simple view of created diagram 55 | # visualizer.visualize_diagram(bpmn_graph) 56 | visualizer.bpmn_diagram_to_dot_file(bpmn_graph, self.output_directory + self.output_dot_file) 57 | visualizer.bpmn_diagram_to_png(bpmn_graph, self.output_directory + self.output_png_file) 58 | 59 | 60 | if __name__ == '__main__': 61 | unittest.main() 62 | -------------------------------------------------------------------------------- /tests/xml_import_export/signavio_complex_test.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Test unit, using more complex graph made in Signavio editor for import/export operation 4 | """ 5 | import os 6 | import unittest 7 | 8 | import bpmn_python.bpmn_diagram_visualizer as visualizer 9 | import bpmn_python.bpmn_diagram_rep as diagram 10 | 11 | 12 | class SignavioComplexTests(unittest.TestCase): 13 | """ 14 | This class contains test for bpmn-python package functionality using a complex example of BPMN diagram 15 | created in Signavio Editor. 16 | """ 17 | output_directory = "./output/test-signavio/complex/" 18 | example_path = "../examples/xml_import_export/signavio_complex_example.bpmn" 19 | output_file_with_di = "signavio-complex-example-output.xml" 20 | output_file_no_di = "signavio-complex-example-output-no-di.xml" 21 | output_dot_file = "signavio-complex-example" 22 | output_png_file = "signavio-complex-example" 23 | 24 | def test_loadSignavioComplexDiagram(self): 25 | """ 26 | Test for importing a complex Signavio diagram example (as BPMN 2.0 XML) into inner representation 27 | and later exporting it to XML file 28 | """ 29 | bpmn_graph = diagram.BpmnDiagramGraph() 30 | bpmn_graph.load_diagram_from_xml_file(os.path.abspath(self.example_path)) 31 | bpmn_graph.export_xml_file(self.output_directory, self.output_file_with_di) 32 | bpmn_graph.export_xml_file_no_di(self.output_directory, self.output_file_no_di) 33 | 34 | def test_loadSignavioComplexDiagramAndVisualize(self): 35 | """ 36 | Test for importing a complex Signavio diagram example (as BPMN 2.0 XML) into inner representation 37 | and later exporting it to XML file. Includes test for visualization functionality. 38 | """ 39 | bpmn_graph = diagram.BpmnDiagramGraph() 40 | bpmn_graph.load_diagram_from_xml_file(os.path.abspath(self.example_path)) 41 | # Uncomment line below to get a simple view of created diagram 42 | # visualizer.visualize_diagram(bpmn_graph) 43 | visualizer.bpmn_diagram_to_dot_file(bpmn_graph, self.output_directory + self.output_dot_file) 44 | visualizer.bpmn_diagram_to_png(bpmn_graph, self.output_directory + self.output_png_file) 45 | bpmn_graph.export_xml_file(self.output_directory, self.output_file_with_di) 46 | bpmn_graph.export_xml_file_no_di(self.output_directory, self.output_file_no_di) 47 | 48 | if __name__ == '__main__': 49 | unittest.main() 50 | -------------------------------------------------------------------------------- /tests/xml_import_export/signavio_simple_test.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Test unit, using simple graph made in Signavio editor for import/export operation 4 | """ 5 | import os 6 | import unittest 7 | 8 | import bpmn_python.bpmn_diagram_visualizer as visualizer 9 | import bpmn_python.bpmn_diagram_rep as diagram 10 | 11 | 12 | class SignavioSimpleTests(unittest.TestCase): 13 | """ 14 | This class contains test for bpmn-python package functionality using a simple example of BPMN diagram 15 | created in Signavio Editor. 16 | """ 17 | output_directory = "./output/test-signavio/simple/" 18 | example_path = "../examples/xml_import_export/signavio_simple_example.bpmn" 19 | output_file_with_di = "signavio-example-output.xml" 20 | output_file_no_di = "signavio-example-output-no-di.xml" 21 | output_dot_file = "signavio-example" 22 | output_png_file = "signavio-example" 23 | 24 | def test_loadSignavioSimpleDiagram(self): 25 | """ 26 | Test for importing a simple Signavio diagram example (as BPMN 2.0 XML) into inner representation 27 | and later exporting it to XML file 28 | """ 29 | bpmn_graph = diagram.BpmnDiagramGraph() 30 | bpmn_graph.load_diagram_from_xml_file(os.path.abspath(self.example_path)) 31 | bpmn_graph.export_xml_file(self.output_directory, self.output_file_with_di) 32 | bpmn_graph.export_xml_file_no_di(self.output_directory, self.output_file_no_di) 33 | 34 | def test_loadSignavioSimpleDiagramAndVisualize(self): 35 | """ 36 | Test for importing a simple Signavio diagram example (as BPMN 2.0 XML) into inner representation 37 | and later exporting it to XML file. Includes test for visualization functionality. 38 | """ 39 | bpmn_graph = diagram.BpmnDiagramGraph() 40 | bpmn_graph.load_diagram_from_xml_file(os.path.abspath(self.example_path)) 41 | # Uncomment line below to get a simple view of created diagram 42 | # visualizer.visualize_diagram(bpmn_graph) 43 | visualizer.bpmn_diagram_to_dot_file(bpmn_graph, self.output_directory + self.output_dot_file) 44 | visualizer.bpmn_diagram_to_png(bpmn_graph, self.output_directory + self.output_png_file) 45 | bpmn_graph.export_xml_file(self.output_directory, self.output_file_with_di) 46 | bpmn_graph.export_xml_file_no_di(self.output_directory, self.output_file_no_di) 47 | 48 | if __name__ == '__main__': 49 | unittest.main() 50 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27,py35,py36,py37 3 | 4 | [testenv] 5 | deps = 6 | pytest 7 | -rrequirements.txt 8 | commands = 9 | pytest 10 | --------------------------------------------------------------------------------