#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ md_to_bpmn.py ============= Wandelt eine einfache Markdown-Prozessbeschreibung in eine standardkonforme BPMN-2.0-XML-Datei um (OMG-Standard, offen dokumentiert, kein Bezug zu internen PICTURE-UUIDs). Wird bei einem Arbeitsschritt "Verantwortlicher: Aufgabe" geschrieben (z. B. "S2 Task: Sachbearbeiter: Antrag prüfen"), erkennt das Skript den Teil vor dem ersten Doppelpunkt als Verantwortlichen und erzeugt daraus automatisch Swimlanes (Pool + Lanes). Ohne diese Angabe entsteht wie bisher ein Diagramm ohne Lanes. Unterstützt neben Task/Gateway/Start/Ende auch die übrigen für PICTURE relevanten Elemente aus Elementkatalog.md (Aufgaben-Varianten, typisierte Ereignisse, ereignisbasiertes Gateway, Datenobjekt/-speicher, Gruppe sowie geschlossene Pools mit Nachrichtenflüssen). Siehe Anleitung.md für die vollständige Syntax. Aufruf: python3 md_to_bpmn.py prozess.md ausgabe.bpmn """ import argparse import math import re import sys import xml.etree.ElementTree as ET from xml.dom import minidom from collections import defaultdict # --------------------------------------------------------------------------- # 1) DSL # --------------------------------------------------------------------------- SIMPLE_TYPE_ALIASES = { 'start': 'StartEvent', 'startereignis': 'StartEvent', 'ende': 'EndEvent', 'end': 'EndEvent', 'endereignis': 'EndEvent', 'zwischenereignis': 'IntermediateEvent', 'intermediate': 'IntermediateEvent', 'task': 'Task', 'aufgabe': 'Task', 'manuelle aufgabe': 'ManualTask', 'manual task': 'ManualTask', 'manualtask': 'ManualTask', 'user aufgabe': 'UserTask', 'user task': 'UserTask', 'usertask': 'UserTask', 'service aufgabe': 'ServiceTask', 'service task': 'ServiceTask', 'servicetask': 'ServiceTask', 'script aufgabe': 'ScriptTask', 'script task': 'ScriptTask', 'scripttask': 'ScriptTask', 'geschäftsregel aufgabe': 'BusinessRuleTask', 'geschaeftsregel aufgabe': 'BusinessRuleTask', 'business rule task': 'BusinessRuleTask', 'businessruletask': 'BusinessRuleTask', 'sendeaufgabe': 'SendTask', 'send task': 'SendTask', 'aufgabe sendend': 'SendTask', 'sendtask': 'SendTask', 'empfangsaufgabe': 'ReceiveTask', 'receive task': 'ReceiveTask', 'aufgabe empfangend': 'ReceiveTask', 'receivetask': 'ReceiveTask', 'gateway xor': 'XorGateway', 'xor': 'XorGateway', 'entscheidung': 'XorGateway', 'gateway or': 'OrGateway', 'gateway oder': 'OrGateway', 'or': 'OrGateway', 'oder': 'OrGateway', 'gateway and': 'AndGateway', 'gateway und': 'AndGateway', 'and': 'AndGateway', 'und': 'AndGateway', 'parallel': 'AndGateway', 'gateway ereignisbasiert': 'EventBasedGateway', 'gateway ebg': 'EventBasedGateway', 'ereignisbasierte verzweigung': 'EventBasedGateway', 'event based gateway': 'EventBasedGateway', 'hinweis': 'TextAnnotation', 'note': 'TextAnnotation', 'anmerkung': 'TextAnnotation', 'datenobjekt': 'DataObject', 'dokument': 'DataObject', 'data object': 'DataObject', 'datenspeicher': 'DataStore', 'data store': 'DataStore', 'gruppe': 'Group', 'group': 'Group', 'teilprozess': 'SubProcess', 'subprocess': 'SubProcess', 'geschlossener teilprozess': 'SubProcess', 'aufruf': 'CallActivity', 'aufrufelement': 'CallActivity', 'aufruf element': 'CallActivity', 'call activity': 'CallActivity', 'callactivity': 'CallActivity', } EVENT_USAGE_WORDS = { 'start': 'StartEvent', 'startereignis': 'StartEvent', 'zwischenereignis': 'IntermediateEvent', 'intermediate': 'IntermediateEvent', 'ende': 'EndEvent', 'end': 'EndEvent', 'endereignis': 'EndEvent', } EVENT_KIND_WORDS = { 'nachricht': 'message', 'message': 'message', 'zeit': 'timer', 'timer': 'timer', 'eskalation': 'escalation', 'escalation': 'escalation', 'bedingung': 'conditional', 'condition': 'conditional', 'conditional': 'conditional', 'link': 'link', 'fehler': 'error', 'error': 'error', 'signal': 'signal', 'terminierung': 'terminate', 'terminate': 'terminate', 'termination': 'terminate', } EVENT_DIRECTION_WORDS = { 'sendend': 'throw', 'send': 'throw', 'werfend': 'throw', 'throw': 'throw', 'empfangend': 'catch', 'receive': 'catch', 'fangend': 'catch', 'catch': 'catch', } EVENT_DEFINITION_TAG = { 'message': 'messageEventDefinition', 'timer': 'timerEventDefinition', 'escalation': 'escalationEventDefinition', 'conditional': 'conditionalEventDefinition', 'link': 'linkEventDefinition', 'error': 'errorEventDefinition', 'signal': 'signalEventDefinition', 'terminate': 'terminateEventDefinition', } UNCONNECTED_TYPES = {'TextAnnotation', 'DataObject', 'DataStore', 'Group'} # Für die Verteilung mehrerer Anschlusspunkte (Modellierregel 4) muss die tatsächliche # Kontur bekannt sein: ein Punkt auf dem rechteckigen Begrenzungsrahmen liegt bei einer # Raute (Gateway) oder einem Kreis (Ereignis) sonst neben der sichtbaren Form. SHAPE_KIND = { 'StartEvent': 'circle', 'EndEvent': 'circle', 'IntermediateEvent': 'circle', 'XorGateway': 'diamond', 'OrGateway': 'diamond', 'AndGateway': 'diamond', 'EventBasedGateway': 'diamond', } DEFAULT_SIZE = { 'StartEvent': (36, 36), 'EndEvent': (36, 36), 'IntermediateEvent': (36, 36), 'XorGateway': (50, 50), 'OrGateway': (50, 50), 'AndGateway': (50, 50), 'EventBasedGateway': (50, 50), 'Task': (100, 80), 'ManualTask': (100, 80), 'UserTask': (100, 80), 'ServiceTask': (100, 80), 'ScriptTask': (100, 80), 'BusinessRuleTask': (100, 80), 'SendTask': (100, 80), 'ReceiveTask': (100, 80), 'SubProcess': (100, 80), 'CallActivity': (100, 80), 'TextAnnotation': (160, 60), 'DataObject': (36, 50), 'DataStore': (50, 50), 'Group': (220, 140), } STEP_RE = re.compile( r'^(?P[A-Za-z0-9_.\-]+)\s+' r'(?P[A-Za-zÄÖÜäöüß]+(?:\s+[A-Za-zÄÖÜäöüß]+){0,7})' r'\s*:\s*(?P