@bpmn-nova/studio 0.3.0-preview → 0.3.2-preview
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +245 -20
- package/dist/canvas.js +7 -4
- package/dist/context-menu.js +2 -2
- package/dist/controller.js +84 -6
- package/dist/index.d.ts +138 -38
- package/dist/index.js +12 -11
- package/dist/interactions.js +1 -1
- package/dist/modules/bpmn-model/index.d.ts +11 -0
- package/dist/modules/bpmn-model/index.js +703 -0
- package/dist/modules/core/containment.js +590 -0
- package/dist/modules/core/gateway.js +72 -0
- package/dist/modules/core/history.js +32 -0
- package/dist/modules/core/index.d.ts +268 -0
- package/dist/modules/core/index.js +7 -0
- package/dist/modules/core/layout.js +644 -0
- package/dist/modules/core/model.js +287 -0
- package/dist/modules/core/runtime-transition-route.js +360 -0
- package/dist/modules/core/scope.js +99 -0
- package/dist/modules/designer/index.d.ts +79 -0
- package/dist/modules/designer/index.js +607 -0
- package/dist/modules/engine-activiti/index.d.ts +19 -0
- package/dist/modules/engine-activiti/index.js +160 -0
- package/dist/modules/engine-flowable/index.d.ts +19 -0
- package/dist/modules/engine-flowable/index.js +160 -0
- package/dist/modules/export-svg/index.d.ts +112 -0
- package/dist/modules/export-svg/index.js +2 -0
- package/dist/modules/export-svg/preview.js +327 -0
- package/dist/modules/export-svg/render.js +718 -0
- package/dist/modules/icons/index.d.ts +24 -0
- package/dist/modules/icons/index.js +264 -0
- package/dist/modules/palette/index.d.ts +74 -0
- package/dist/modules/palette/index.js +99 -0
- package/dist/modules/palette/panel.js +99 -0
- package/dist/modules/properties/index.d.ts +20 -0
- package/dist/modules/properties/index.js +19 -0
- package/dist/modules/properties-activiti/index.d.ts +3 -0
- package/dist/modules/properties-activiti/index.js +97 -0
- package/dist/modules/properties-bpmn/index.d.ts +3 -0
- package/dist/modules/properties-bpmn/index.js +518 -0
- package/dist/modules/properties-core/index.d.ts +124 -0
- package/dist/modules/properties-core/index.js +312 -0
- package/dist/modules/properties-flowable/index.d.ts +3 -0
- package/dist/modules/properties-flowable/index.js +114 -0
- package/dist/modules/properties-renderer/index.d.ts +25 -0
- package/dist/modules/properties-renderer/index.js +491 -0
- package/dist/modules/renderer-svg/index.d.ts +118 -0
- package/dist/modules/renderer-svg/index.js +1460 -0
- package/dist/modules/runtime/index.d.ts +169 -0
- package/dist/modules/runtime/index.js +535 -0
- package/dist/modules/theme/index.d.ts +95 -0
- package/dist/modules/theme/index.js +368 -0
- package/dist/modules/viewer/index.d.ts +265 -0
- package/dist/modules/viewer/index.js +1011 -0
- package/dist/modules/viewer/runtime-content.js +123 -0
- package/dist/modules/viewer/runtime-details-motion.js +228 -0
- package/dist/modules/viewer/runtime-trace.js +574 -0
- package/dist/modules/viewer/timeline.js +276 -0
- package/dist/selection-layout.js +1 -1
- package/dist/shell.js +210 -26
- package/dist/styles.css +116 -7
- package/llms-full.txt +3082 -0
- package/llms.txt +225 -0
- package/package.json +39 -16
|
@@ -0,0 +1,703 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyContainmentOperation,
|
|
3
|
+
createEmptyProcess,
|
|
4
|
+
createNode,
|
|
5
|
+
createEdge,
|
|
6
|
+
NODE_DEFINITIONS,
|
|
7
|
+
edgeWaypoints,
|
|
8
|
+
elementScopeId,
|
|
9
|
+
isEmbeddedSubProcess,
|
|
10
|
+
} from '../core/index.js';
|
|
11
|
+
import { flowableProfile } from '../engine-flowable/index.js';
|
|
12
|
+
import { activitiProfile } from '../engine-activiti/index.js';
|
|
13
|
+
|
|
14
|
+
const NS = {
|
|
15
|
+
bpmn: 'http://www.omg.org/spec/BPMN/20100524/MODEL',
|
|
16
|
+
bpmndi: 'http://www.omg.org/spec/BPMN/20100524/DI',
|
|
17
|
+
dc: 'http://www.omg.org/spec/DD/20100524/DC',
|
|
18
|
+
di: 'http://www.omg.org/spec/DD/20100524/DI',
|
|
19
|
+
xsi: 'http://www.w3.org/2001/XMLSchema-instance',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const EVENT_DEF_BY_LOCAL = {
|
|
23
|
+
messageEventDefinition: 'message',
|
|
24
|
+
timerEventDefinition: 'timer',
|
|
25
|
+
conditionalEventDefinition: 'conditional',
|
|
26
|
+
signalEventDefinition: 'signal',
|
|
27
|
+
errorEventDefinition: 'error',
|
|
28
|
+
escalationEventDefinition: 'escalation',
|
|
29
|
+
cancelEventDefinition: 'cancel',
|
|
30
|
+
compensateEventDefinition: 'compensate',
|
|
31
|
+
terminateEventDefinition: 'terminate',
|
|
32
|
+
linkEventDefinition: 'link',
|
|
33
|
+
};
|
|
34
|
+
const EVENT_LOCAL_BY_DEF = Object.fromEntries(Object.entries(EVENT_DEF_BY_LOCAL).map(([local, def]) => [def, local]));
|
|
35
|
+
|
|
36
|
+
const DIRECT_TYPE_BY_LOCAL = {
|
|
37
|
+
task: 'task',
|
|
38
|
+
userTask: 'userTask',
|
|
39
|
+
manualTask: 'manualTask',
|
|
40
|
+
serviceTask: 'serviceTask',
|
|
41
|
+
scriptTask: 'scriptTask',
|
|
42
|
+
businessRuleTask: 'businessRuleTask',
|
|
43
|
+
sendTask: 'sendTask',
|
|
44
|
+
receiveTask: 'receiveTask',
|
|
45
|
+
callActivity: 'callActivity',
|
|
46
|
+
exclusiveGateway: 'exclusiveGateway',
|
|
47
|
+
parallelGateway: 'parallelGateway',
|
|
48
|
+
inclusiveGateway: 'inclusiveGateway',
|
|
49
|
+
eventBasedGateway: 'eventBasedGateway',
|
|
50
|
+
complexGateway: 'complexGateway',
|
|
51
|
+
transaction: 'transaction',
|
|
52
|
+
adHocSubProcess: 'adHocSubProcess',
|
|
53
|
+
dataObjectReference: 'dataObjectReference',
|
|
54
|
+
dataStoreReference: 'dataStoreReference',
|
|
55
|
+
textAnnotation: 'textAnnotation',
|
|
56
|
+
group: 'group',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const EVENT_TYPE_INDEX = {};
|
|
60
|
+
for (const [type, def] of Object.entries(NODE_DEFINITIONS)) {
|
|
61
|
+
if (!def?.eventStage) continue;
|
|
62
|
+
const key = `${def.localName}|${def.eventDefinition || 'none'}`;
|
|
63
|
+
EVENT_TYPE_INDEX[key] = type;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const engineProfiles = { flowable: flowableProfile, activiti: activitiProfile };
|
|
67
|
+
|
|
68
|
+
export function detectEngineFromXml(xml) {
|
|
69
|
+
if (/xmlns:flowable\s*=/.test(xml) || /flowable:/.test(xml)) return 'flowable';
|
|
70
|
+
if (/xmlns:activiti\s*=/.test(xml) || /activiti:/.test(xml)) return 'activiti';
|
|
71
|
+
return 'flowable';
|
|
72
|
+
}
|
|
73
|
+
function getProfile(engine) { return engineProfiles[engine] || flowableProfile; }
|
|
74
|
+
|
|
75
|
+
function elementEventDefinition(el) {
|
|
76
|
+
const child = Array.from(el.children || []).find((item) => item.localName?.endsWith('EventDefinition'));
|
|
77
|
+
return child ? (EVENT_DEF_BY_LOCAL[child.localName] || null) : null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function inferType(el) {
|
|
81
|
+
if (['startEvent', 'endEvent', 'intermediateCatchEvent', 'intermediateThrowEvent', 'boundaryEvent'].includes(el.localName)) {
|
|
82
|
+
const eventDef = elementEventDefinition(el) || 'none';
|
|
83
|
+
return EVENT_TYPE_INDEX[`${el.localName}|${eventDef}`] || EVENT_TYPE_INDEX[`${el.localName}|none`] || 'generic';
|
|
84
|
+
}
|
|
85
|
+
if (el.localName === 'subProcess') return el.getAttribute('triggeredByEvent') === 'true' ? 'eventSubProcess' : 'subProcess';
|
|
86
|
+
return DIRECT_TYPE_BY_LOCAL[el.localName] || 'generic';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function readShapeMaps(doc) {
|
|
90
|
+
const shapeMap = new Map();
|
|
91
|
+
for (const shape of Array.from(doc.getElementsByTagNameNS(NS.bpmndi, 'BPMNShape'))) {
|
|
92
|
+
const elementId = shape.getAttribute('bpmnElement');
|
|
93
|
+
const bounds = Array.from(shape.children).find((el) => el.localName === 'Bounds');
|
|
94
|
+
if (!elementId || !bounds) continue;
|
|
95
|
+
shapeMap.set(elementId, {
|
|
96
|
+
x: Number(bounds.getAttribute('x') || 0),
|
|
97
|
+
y: Number(bounds.getAttribute('y') || 0),
|
|
98
|
+
width: Number(bounds.getAttribute('width') || 0),
|
|
99
|
+
height: Number(bounds.getAttribute('height') || 0),
|
|
100
|
+
isExpanded: shape.getAttribute('isExpanded') !== 'false',
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
const edgeDiMap = new Map();
|
|
104
|
+
for (const edgeDi of Array.from(doc.getElementsByTagNameNS(NS.bpmndi, 'BPMNEdge'))) {
|
|
105
|
+
const elementId = edgeDi.getAttribute('bpmnElement');
|
|
106
|
+
const points = Array.from(edgeDi.children)
|
|
107
|
+
.filter((el) => el.localName === 'waypoint')
|
|
108
|
+
.map((p) => ({ x: Number(p.getAttribute('x') || 0), y: Number(p.getAttribute('y') || 0) }));
|
|
109
|
+
if (elementId && points.length) edgeDiMap.set(elementId, points);
|
|
110
|
+
}
|
|
111
|
+
return { shapeMap, edgeDiMap };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function parseEventProperties(el, def) {
|
|
115
|
+
const props = {};
|
|
116
|
+
if (def.kind === 'boundary') {
|
|
117
|
+
props.attachedToRef = el.getAttribute('attachedToRef') || '';
|
|
118
|
+
props.cancelActivity = el.getAttribute('cancelActivity') !== 'false';
|
|
119
|
+
}
|
|
120
|
+
const eventDefinition = Array.from(el.children).find((child) => child.localName?.endsWith('EventDefinition'));
|
|
121
|
+
if (!eventDefinition) return props;
|
|
122
|
+
if (def.eventDefinition === 'timer') {
|
|
123
|
+
const timer = Array.from(eventDefinition.children).find((child) => ['timeDate', 'timeDuration', 'timeCycle'].includes(child.localName));
|
|
124
|
+
if (timer) {
|
|
125
|
+
props.timerType = ({ timeDate: 'date', timeDuration: 'duration', timeCycle: 'cycle' })[timer.localName] || 'duration';
|
|
126
|
+
props.timerValue = timer.textContent?.trim() || '';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (def.eventDefinition === 'conditional') {
|
|
130
|
+
const condition = Array.from(eventDefinition.children).find((child) => child.localName === 'condition');
|
|
131
|
+
props.condition = condition?.textContent?.trim() || '';
|
|
132
|
+
}
|
|
133
|
+
const refAttr = ({ message: 'messageRef', signal: 'signalRef', error: 'errorRef', escalation: 'escalationRef', link: 'name' })[def.eventDefinition];
|
|
134
|
+
if (refAttr) props.eventRef = eventDefinition.getAttribute(refAttr) || '';
|
|
135
|
+
if (def.eventDefinition === 'compensate') {
|
|
136
|
+
props.activityRef = eventDefinition.getAttribute('activityRef') || '';
|
|
137
|
+
props.waitForCompletion = eventDefinition.getAttribute('waitForCompletion') !== 'false';
|
|
138
|
+
}
|
|
139
|
+
return props;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function parseCommonNode(el, profile, shapeMap, fallbackIndex = 0) {
|
|
143
|
+
const type = inferType(el);
|
|
144
|
+
const def = NODE_DEFINITIONS[type] || NODE_DEFINITIONS.generic;
|
|
145
|
+
const id = el.getAttribute('id') || `Imported_${fallbackIndex + 1}`;
|
|
146
|
+
const bounds = shapeMap.get(id) || { x: 120 + fallbackIndex * 260, y: 220, width: def.width, height: def.height };
|
|
147
|
+
const properties = {
|
|
148
|
+
...profile.parseNode(el),
|
|
149
|
+
...parseEventProperties(el, def),
|
|
150
|
+
};
|
|
151
|
+
properties.documentation = Array.from(el.children || []).find((child) => child.localName === 'documentation')?.textContent || properties.documentation || '';
|
|
152
|
+
if (def.kind === 'gateway') properties.gatewayDirection = el.getAttribute('gatewayDirection') || properties.gatewayDirection || 'Unspecified';
|
|
153
|
+
if (type === 'eventBasedGateway') {
|
|
154
|
+
properties.instantiate = el.getAttribute('instantiate') === 'true';
|
|
155
|
+
properties.eventGatewayType = el.getAttribute('eventGatewayType') || 'Exclusive';
|
|
156
|
+
}
|
|
157
|
+
if (type === 'adHocSubProcess') {
|
|
158
|
+
properties.ordering = el.getAttribute('ordering') || 'Parallel';
|
|
159
|
+
properties.cancelRemainingInstances = el.getAttribute('cancelRemainingInstances') !== 'false';
|
|
160
|
+
properties.adHocCompletionCondition = Array.from(el.children || []).find((child) => child.localName === 'completionCondition')?.textContent?.trim() || '';
|
|
161
|
+
}
|
|
162
|
+
if (type === 'complexGateway') properties.activationCondition = Array.from(el.children || []).find((child) => child.localName === 'activationCondition')?.textContent?.trim() || '';
|
|
163
|
+
|
|
164
|
+
if (type === 'scriptTask') {
|
|
165
|
+
properties.scriptFormat = el.getAttribute('scriptFormat') || 'javascript';
|
|
166
|
+
properties.script = Array.from(el.children).find((child) => child.localName === 'script')?.textContent || '';
|
|
167
|
+
}
|
|
168
|
+
if (type === 'callActivity') properties.calledElement = el.getAttribute('calledElement') || '';
|
|
169
|
+
if (['sendTask', 'receiveTask'].includes(type)) {
|
|
170
|
+
properties.messageRef = el.getAttribute('messageRef') || '';
|
|
171
|
+
properties.operationRef = el.getAttribute('operationRef') || '';
|
|
172
|
+
if (type === 'receiveTask') properties.instantiate = el.getAttribute('instantiate') === 'true';
|
|
173
|
+
}
|
|
174
|
+
if ((def.eventStage === 'start' || (def.eventStage === 'intermediate' && def.eventRole === 'catch')) && ['message', 'signal'].includes(def.eventDefinition)) {
|
|
175
|
+
properties.parallelMultiple = el.getAttribute('parallelMultiple') === 'true';
|
|
176
|
+
}
|
|
177
|
+
if (type === 'textAnnotation') {
|
|
178
|
+
properties.text = Array.from(el.children).find((child) => child.localName === 'text')?.textContent || '';
|
|
179
|
+
properties.textFormat = el.getAttribute('textFormat') || 'text/plain';
|
|
180
|
+
}
|
|
181
|
+
if (type === 'dataObjectReference') properties.dataObjectRef = el.getAttribute('dataObjectRef') || '';
|
|
182
|
+
if (type === 'dataStoreReference') properties.dataStoreRef = el.getAttribute('dataStoreRef') || '';
|
|
183
|
+
if (type === 'eventSubProcess') properties.triggeredByEvent = true;
|
|
184
|
+
|
|
185
|
+
const loop = Array.from(el.children).find((child) => ['standardLoopCharacteristics', 'multiInstanceLoopCharacteristics'].includes(child.localName));
|
|
186
|
+
if (loop?.localName === 'standardLoopCharacteristics') {
|
|
187
|
+
properties.loopType = 'standard';
|
|
188
|
+
properties.testBefore = loop.getAttribute('testBefore') === 'true';
|
|
189
|
+
properties.loopMaximum = loop.getAttribute('loopMaximum') || '';
|
|
190
|
+
properties.loopCondition = Array.from(loop.children || []).find((child) => child.localName === 'loopCondition')?.textContent?.trim() || '';
|
|
191
|
+
}
|
|
192
|
+
if (loop?.localName === 'multiInstanceLoopCharacteristics') {
|
|
193
|
+
properties.loopType = loop.getAttribute('isSequential') === 'true' ? 'sequential' : 'parallel';
|
|
194
|
+
const getLoop = (name) => loop.getAttributeNS(profile.namespace, name) || loop.getAttribute(`${profile.prefix}:${name}`) || '';
|
|
195
|
+
properties.collection = getLoop('collection');
|
|
196
|
+
properties.elementVariable = getLoop('elementVariable');
|
|
197
|
+
properties.elementIndexVariable = getLoop('elementIndexVariable');
|
|
198
|
+
properties.loopCardinality = Array.from(loop.children || []).find((child) => child.localName === 'loopCardinality')?.textContent?.trim() || '';
|
|
199
|
+
properties.completionCondition = Array.from(loop.children || []).find((child) => child.localName === 'completionCondition')?.textContent?.trim() || '';
|
|
200
|
+
}
|
|
201
|
+
properties.isForCompensation = el.getAttribute('isForCompensation') === 'true';
|
|
202
|
+
|
|
203
|
+
const knownAttrs = new Set(['id', 'name', 'scriptFormat', 'calledElement', 'default', 'attachedToRef', 'cancelActivity', 'triggeredByEvent', 'isForCompensation', 'dataObjectRef', 'dataStoreRef', 'gatewayDirection', 'instantiate', 'eventGatewayType', 'ordering', 'cancelRemainingInstances', 'messageRef', 'operationRef', 'parallelMultiple', 'instantiate', 'textFormat']);
|
|
204
|
+
const unknownAttributes = [];
|
|
205
|
+
for (const attr of Array.from(el.attributes || [])) {
|
|
206
|
+
if (knownAttrs.has(attr.localName)) continue;
|
|
207
|
+
if (attr.prefix === profile.prefix || attr.namespaceURI === profile.namespace) continue;
|
|
208
|
+
unknownAttributes.push({ name: attr.name, value: attr.value });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
properties.extensionAttributes = { ...(properties.extensionAttributes || {}), ...Object.fromEntries(unknownAttributes.filter((attr) => attr.name.includes(':')).map((attr) => [attr.name, attr.value])) };
|
|
212
|
+
|
|
213
|
+
const extensionElements = [];
|
|
214
|
+
const ext = Array.from(el.children || []).find((child) => child.localName === 'extensionElements');
|
|
215
|
+
if (ext && typeof XMLSerializer !== 'undefined') {
|
|
216
|
+
const serializer = new XMLSerializer();
|
|
217
|
+
const handled = new Set(['executionListener', 'taskListener', 'field', 'failedJobRetryTimeCycle']);
|
|
218
|
+
for (const child of Array.from(ext.children)) {
|
|
219
|
+
const isEngineHandled = handled.has(child.localName) && (child.prefix === profile.prefix || child.namespaceURI === profile.namespace);
|
|
220
|
+
if (!isEngineHandled) extensionElements.push(serializer.serializeToString(child));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return createNode(type, bounds.x, bounds.y, {
|
|
225
|
+
id,
|
|
226
|
+
name: el.getAttribute('name') || def.label,
|
|
227
|
+
width: bounds.width || def.width,
|
|
228
|
+
height: bounds.height || def.height,
|
|
229
|
+
bpmnType: def.bpmnType,
|
|
230
|
+
originalLocalName: el.localName,
|
|
231
|
+
properties,
|
|
232
|
+
unknownAttributes,
|
|
233
|
+
extensionElements,
|
|
234
|
+
defaultFlowId: el.getAttribute('default') || '',
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function importBpmn(xml, engineHint) {
|
|
239
|
+
if (typeof DOMParser === 'undefined') throw new Error('importBpmn currently requires a browser DOMParser.');
|
|
240
|
+
const doc = new DOMParser().parseFromString(xml, 'application/xml');
|
|
241
|
+
const parserError = doc.querySelector('parsererror');
|
|
242
|
+
if (parserError) throw new Error(`BPMN XML 解析失败: ${parserError.textContent.trim()}`);
|
|
243
|
+
|
|
244
|
+
const definitions = doc.documentElement;
|
|
245
|
+
const engine = engineHint || detectEngineFromXml(xml);
|
|
246
|
+
const profile = getProfile(engine);
|
|
247
|
+
const process = Array.from(definitions.children).find((el) => el.localName === 'process');
|
|
248
|
+
if (!process) throw new Error('未找到 <process> 元素。');
|
|
249
|
+
|
|
250
|
+
const model = createEmptyProcess(engine);
|
|
251
|
+
model.id = process.getAttribute('id') || 'Process_1';
|
|
252
|
+
model.name = process.getAttribute('name') || model.id;
|
|
253
|
+
model.isExecutable = process.getAttribute('isExecutable') !== 'false';
|
|
254
|
+
model.targetNamespace = definitions.getAttribute('targetNamespace') || model.targetNamespace;
|
|
255
|
+
model.properties = { ...(model.properties || {}), ...(profile.parseProcess?.(process) || {}) };
|
|
256
|
+
model.properties.documentation = Array.from(process.children || []).find((child) => child.localName === 'documentation')?.textContent || model.properties.documentation || '';
|
|
257
|
+
model.properties.extensionAttributes = { ...(model.properties.extensionAttributes || {}) };
|
|
258
|
+
for (const attr of Array.from(process.attributes || [])) {
|
|
259
|
+
if (['id', 'name', 'isExecutable'].includes(attr.localName)) continue;
|
|
260
|
+
if (attr.prefix === profile.prefix || attr.namespaceURI === profile.namespace) continue;
|
|
261
|
+
if (attr.name.includes(':')) model.properties.extensionAttributes[attr.name] = attr.value;
|
|
262
|
+
}
|
|
263
|
+
model.extensionElements = [];
|
|
264
|
+
const processExt = Array.from(process.children || []).find((child) => child.localName === 'extensionElements');
|
|
265
|
+
if (processExt && typeof XMLSerializer !== 'undefined') {
|
|
266
|
+
const serializer = new XMLSerializer();
|
|
267
|
+
for (const child of Array.from(processExt.children || [])) {
|
|
268
|
+
const handled = child.localName === 'executionListener' && (child.prefix === profile.prefix || child.namespaceURI === profile.namespace);
|
|
269
|
+
if (!handled) model.extensionElements.push(serializer.serializeToString(child));
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
model.resources = { messages: [], signals: [], errors: [], escalations: [] };
|
|
273
|
+
for (const child of Array.from(definitions.children || [])) {
|
|
274
|
+
const map = { message: 'messages', signal: 'signals', error: 'errors', escalation: 'escalations' };
|
|
275
|
+
const bucket = map[child.localName];
|
|
276
|
+
if (!bucket) continue;
|
|
277
|
+
const item = { id: child.getAttribute('id') || '', name: child.getAttribute('name') || '' };
|
|
278
|
+
if (child.localName === 'error') item.errorCode = child.getAttribute('errorCode') || '';
|
|
279
|
+
if (child.localName === 'escalation') item.escalationCode = child.getAttribute('escalationCode') || '';
|
|
280
|
+
model.resources[bucket].push(item);
|
|
281
|
+
}
|
|
282
|
+
model.namespaceDeclarations = {};
|
|
283
|
+
for (const attr of Array.from(definitions.attributes)) {
|
|
284
|
+
if (attr.name.startsWith('xmlns:') && !['xmlns:bpmn', 'xmlns:bpmndi', 'xmlns:dc', 'xmlns:di', 'xmlns:xsi', `xmlns:${profile.prefix}`].includes(attr.name)) {
|
|
285
|
+
model.namespaceDeclarations[attr.name] = attr.value;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const { shapeMap, edgeDiMap } = readShapeMaps(doc);
|
|
290
|
+
const categoryValues = new Map(
|
|
291
|
+
Array.from(definitions.getElementsByTagNameNS(NS.bpmn, 'categoryValue'))
|
|
292
|
+
.map((element) => [element.getAttribute('id'), element.getAttribute('value') || '']),
|
|
293
|
+
);
|
|
294
|
+
const groupElements = new Map(
|
|
295
|
+
Array.from(process.getElementsByTagNameNS(NS.bpmn, 'group'))
|
|
296
|
+
.map((element) => [element.getAttribute('id'), element]),
|
|
297
|
+
);
|
|
298
|
+
const sequenceEls = [];
|
|
299
|
+
const associationEls = [];
|
|
300
|
+
const skipLocals = new Set(['extensionElements', 'documentation', 'laneSet', 'dataObject', 'incoming', 'outgoing', 'completionCondition', 'standardLoopCharacteristics', 'multiInstanceLoopCharacteristics', 'ioSpecification', 'property']);
|
|
301
|
+
|
|
302
|
+
const collectScope = (container, scopeId) => {
|
|
303
|
+
for (const el of Array.from(container.children || [])) {
|
|
304
|
+
if (el.localName === 'sequenceFlow') { sequenceEls.push({ el, scopeId }); continue; }
|
|
305
|
+
if (el.localName === 'association') { associationEls.push({ el, scopeId }); continue; }
|
|
306
|
+
if (skipLocals.has(el.localName)) continue;
|
|
307
|
+
const node = parseCommonNode(el, profile, shapeMap, model.nodes.length);
|
|
308
|
+
node.scopeId = scopeId;
|
|
309
|
+
if (isEmbeddedSubProcess(node)) node.isExpanded = false;
|
|
310
|
+
model.nodes.push(node);
|
|
311
|
+
if (isEmbeddedSubProcess(node)) collectScope(el, node.id);
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
collectScope(process, model.id);
|
|
315
|
+
|
|
316
|
+
for (const group of model.nodes.filter((node) => node.type === 'group')) {
|
|
317
|
+
const element = groupElements.get(group.id);
|
|
318
|
+
group.properties.categoryValue = categoryValues.get(element?.getAttribute('categoryValueRef')) || '';
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
for (const lane of Array.from(process.querySelectorAll(':scope > laneSet > lane'))) {
|
|
322
|
+
const node = parseCommonNode(lane, profile, shapeMap, model.nodes.length);
|
|
323
|
+
node.type = 'lane'; node.bpmnType = 'bpmn:Lane'; node.originalLocalName = 'lane';
|
|
324
|
+
node.scopeId = model.id;
|
|
325
|
+
node.properties.flowNodeRefs = Array.from(lane.children).filter((child) => child.localName === 'flowNodeRef').map((child) => child.textContent.trim());
|
|
326
|
+
model.nodes.push(node);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const collaboration = Array.from(definitions.children).find((el) => el.localName === 'collaboration');
|
|
330
|
+
if (collaboration) {
|
|
331
|
+
for (const participant of Array.from(collaboration.children).filter((el) => el.localName === 'participant')) {
|
|
332
|
+
const node = parseCommonNode(participant, profile, shapeMap, model.nodes.length);
|
|
333
|
+
node.type = 'participant'; node.bpmnType = 'bpmn:Participant'; node.originalLocalName = 'participant';
|
|
334
|
+
node.scopeId = model.id;
|
|
335
|
+
node.properties.processRef = participant.getAttribute('processRef') || '';
|
|
336
|
+
model.nodes.push(node);
|
|
337
|
+
}
|
|
338
|
+
for (const el of Array.from(collaboration.children).filter((item) => item.localName === 'messageFlow')) associationEls.push({ el, scopeId: model.id });
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
for (const { el, scopeId } of sequenceEls) {
|
|
342
|
+
const id = el.getAttribute('id') || `Flow_${model.edges.length + 1}`;
|
|
343
|
+
const condition = Array.from(el.children).find((child) => child.localName === 'conditionExpression');
|
|
344
|
+
const source = el.getAttribute('sourceRef') || '';
|
|
345
|
+
const sourceNode = model.nodes.find((n) => n.id === source);
|
|
346
|
+
const edgeProperties = profile.parseEdge?.(el) || {};
|
|
347
|
+
const edgeUnknownAttributes = [];
|
|
348
|
+
for (const attr of Array.from(el.attributes || [])) {
|
|
349
|
+
if (['id', 'name', 'sourceRef', 'targetRef'].includes(attr.localName)) continue;
|
|
350
|
+
if (attr.prefix === profile.prefix || attr.namespaceURI === profile.namespace) continue;
|
|
351
|
+
edgeUnknownAttributes.push({ name: attr.name, value: attr.value });
|
|
352
|
+
}
|
|
353
|
+
edgeProperties.extensionAttributes = { ...(edgeProperties.extensionAttributes || {}), ...Object.fromEntries(edgeUnknownAttributes.filter((attr) => attr.name.includes(':')).map((attr) => [attr.name, attr.value])) };
|
|
354
|
+
const edgeExtensionElements = [];
|
|
355
|
+
const edgeExt = Array.from(el.children || []).find((child) => child.localName === 'extensionElements');
|
|
356
|
+
if (edgeExt && typeof XMLSerializer !== 'undefined') {
|
|
357
|
+
const serializer = new XMLSerializer();
|
|
358
|
+
for (const child of Array.from(edgeExt.children || [])) {
|
|
359
|
+
const handled = child.localName === 'executionListener' && (child.prefix === profile.prefix || child.namespaceURI === profile.namespace);
|
|
360
|
+
if (!handled) edgeExtensionElements.push(serializer.serializeToString(child));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
model.edges.push(createEdge(source, el.getAttribute('targetRef') || '', {
|
|
364
|
+
id,
|
|
365
|
+
type: 'sequenceFlow',
|
|
366
|
+
name: el.getAttribute('name') || '',
|
|
367
|
+
condition: condition?.textContent?.trim() || '',
|
|
368
|
+
isDefault: sourceNode?.defaultFlowId === id,
|
|
369
|
+
waypoints: edgeDiMap.get(id) || null,
|
|
370
|
+
properties: { documentation: Array.from(el.children || []).find((child) => child.localName === 'documentation')?.textContent || '', executionListeners: [], extensionAttributes: {}, ...edgeProperties },
|
|
371
|
+
unknownAttributes: edgeUnknownAttributes,
|
|
372
|
+
extensionElements: edgeExtensionElements,
|
|
373
|
+
scopeId,
|
|
374
|
+
}));
|
|
375
|
+
}
|
|
376
|
+
for (const { el, scopeId } of associationEls) {
|
|
377
|
+
const edgeType = el.localName === 'messageFlow' ? 'messageFlow' : 'association';
|
|
378
|
+
const properties = {
|
|
379
|
+
documentation: Array.from(el.children || []).find((child) => child.localName === 'documentation')?.textContent || '',
|
|
380
|
+
executionListeners: [],
|
|
381
|
+
extensionAttributes: {},
|
|
382
|
+
messageRef: edgeType === 'messageFlow' ? (el.getAttribute('messageRef') || '') : '',
|
|
383
|
+
associationDirection: edgeType === 'association' ? (el.getAttribute('associationDirection') || 'None') : 'None',
|
|
384
|
+
};
|
|
385
|
+
const unknownAttributes = [];
|
|
386
|
+
for (const attr of Array.from(el.attributes || [])) {
|
|
387
|
+
if (['id', 'name', 'sourceRef', 'targetRef', 'messageRef', 'associationDirection'].includes(attr.localName)) continue;
|
|
388
|
+
unknownAttributes.push({ name: attr.name, value: attr.value });
|
|
389
|
+
if (attr.name.includes(':')) properties.extensionAttributes[attr.name] = attr.value;
|
|
390
|
+
}
|
|
391
|
+
const extensionElements = [];
|
|
392
|
+
const ext = Array.from(el.children || []).find((child) => child.localName === 'extensionElements');
|
|
393
|
+
if (ext && typeof XMLSerializer !== 'undefined') {
|
|
394
|
+
const serializer = new XMLSerializer();
|
|
395
|
+
for (const child of Array.from(ext.children || [])) extensionElements.push(serializer.serializeToString(child));
|
|
396
|
+
}
|
|
397
|
+
model.edges.push(createEdge(el.getAttribute('sourceRef') || '', el.getAttribute('targetRef') || '', {
|
|
398
|
+
id: el.getAttribute('id') || `Association_${model.edges.length + 1}`,
|
|
399
|
+
type: edgeType,
|
|
400
|
+
name: el.getAttribute('name') || '',
|
|
401
|
+
waypoints: edgeDiMap.get(el.getAttribute('id')) || null,
|
|
402
|
+
properties,
|
|
403
|
+
unknownAttributes,
|
|
404
|
+
extensionElements,
|
|
405
|
+
scopeId,
|
|
406
|
+
}));
|
|
407
|
+
}
|
|
408
|
+
applyContainmentOperation(model, { type: 'reconcile', inferOwners: true, normalize: true });
|
|
409
|
+
return model;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function escapeXml(value = '') {
|
|
413
|
+
return String(value).replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", ''');
|
|
414
|
+
}
|
|
415
|
+
function cdata(value = '') { return String(value).replaceAll(']]>', ']]]]><![CDATA[>'); }
|
|
416
|
+
function attrsToString(attrs) {
|
|
417
|
+
return Object.entries(attrs).filter(([, value]) => value !== undefined && value !== null && value !== '')
|
|
418
|
+
.map(([name, value]) => `${name}="${escapeXml(value)}"`).join(' ');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function eventDefinitionBody(node) {
|
|
422
|
+
const def = NODE_DEFINITIONS[node.type] || {};
|
|
423
|
+
const eventType = def.eventDefinition;
|
|
424
|
+
if (!eventType) return [];
|
|
425
|
+
const local = EVENT_LOCAL_BY_DEF[eventType];
|
|
426
|
+
if (!local) return [];
|
|
427
|
+
const p = node.properties || {};
|
|
428
|
+
const refAttr = ({ message: 'messageRef', signal: 'signalRef', error: 'errorRef', escalation: 'escalationRef', link: 'name' })[eventType];
|
|
429
|
+
let attrs = refAttr && p.eventRef ? ` ${refAttr}="${escapeXml(p.eventRef)}"` : '';
|
|
430
|
+
if (eventType === 'compensate') {
|
|
431
|
+
if (p.activityRef) attrs += ` activityRef="${escapeXml(p.activityRef)}"`;
|
|
432
|
+
if (p.waitForCompletion === false) attrs += ' waitForCompletion="false"';
|
|
433
|
+
}
|
|
434
|
+
if (eventType === 'timer') {
|
|
435
|
+
const tag = ({ date: 'timeDate', cycle: 'timeCycle', duration: 'timeDuration' })[p.timerType] || 'timeDuration';
|
|
436
|
+
return [` <bpmn:${local}${attrs}>`, ` <bpmn:${tag} xsi:type="bpmn:tFormalExpression"><![CDATA[${cdata(p.timerValue || 'PT1H')}]]></bpmn:${tag}>`, ` </bpmn:${local}>`];
|
|
437
|
+
}
|
|
438
|
+
if (eventType === 'conditional') {
|
|
439
|
+
return [` <bpmn:${local}${attrs}>`, ` <bpmn:condition xsi:type="bpmn:tFormalExpression"><![CDATA[${cdata(p.condition || '${true}')}]]></bpmn:condition>`, ` </bpmn:${local}>`];
|
|
440
|
+
}
|
|
441
|
+
return [` <bpmn:${local}${attrs} />`];
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function activityBody(node, profile) {
|
|
445
|
+
const p = node.properties || {};
|
|
446
|
+
const body = [];
|
|
447
|
+
if (p.documentation) body.push(` <bpmn:documentation><![CDATA[${cdata(p.documentation)}]]></bpmn:documentation>`);
|
|
448
|
+
const extensionElements = [...(node.extensionElements || []), ...(profile.nodeExtensionElements?.(node) || [])];
|
|
449
|
+
if (extensionElements.length) {
|
|
450
|
+
body.push(' <bpmn:extensionElements>');
|
|
451
|
+
for (const raw of extensionElements) body.push(` ${raw}`);
|
|
452
|
+
body.push(' </bpmn:extensionElements>');
|
|
453
|
+
}
|
|
454
|
+
if (node.type === 'scriptTask' && p.script) body.push(` <bpmn:script><![CDATA[${cdata(p.script)}]]></bpmn:script>`);
|
|
455
|
+
if (node.type === 'textAnnotation') body.push(` <bpmn:text><![CDATA[${cdata(p.text || node.name || '')}]]></bpmn:text>`);
|
|
456
|
+
if (p.loopType === 'standard') {
|
|
457
|
+
const loopAttrs = attrsToString({ ...(p.testBefore ? { testBefore: 'true' } : {}), ...(p.loopMaximum !== '' && p.loopMaximum != null ? { loopMaximum: p.loopMaximum } : {}) });
|
|
458
|
+
if (p.loopCondition) {
|
|
459
|
+
body.push(` <bpmn:standardLoopCharacteristics${loopAttrs ? ` ${loopAttrs}` : ''}>`);
|
|
460
|
+
body.push(` <bpmn:loopCondition xsi:type="bpmn:tFormalExpression"><![CDATA[${cdata(p.loopCondition)}]]></bpmn:loopCondition>`);
|
|
461
|
+
body.push(' </bpmn:standardLoopCharacteristics>');
|
|
462
|
+
} else body.push(` <bpmn:standardLoopCharacteristics${loopAttrs ? ` ${loopAttrs}` : ''} />`);
|
|
463
|
+
}
|
|
464
|
+
if (p.loopType === 'parallel' || p.loopType === 'sequential') {
|
|
465
|
+
const loopAttrs = {
|
|
466
|
+
isSequential: p.loopType === 'sequential' ? 'true' : 'false',
|
|
467
|
+
...(p.collection ? { [`${profile.prefix}:collection`]: p.collection } : {}),
|
|
468
|
+
...(p.elementVariable ? { [`${profile.prefix}:elementVariable`]: p.elementVariable } : {}),
|
|
469
|
+
...(p.elementIndexVariable ? { [`${profile.prefix}:elementIndexVariable`]: p.elementIndexVariable } : {}),
|
|
470
|
+
};
|
|
471
|
+
const hasBody = !!p.loopCardinality || !!p.completionCondition;
|
|
472
|
+
if (!hasBody) body.push(` <bpmn:multiInstanceLoopCharacteristics ${attrsToString(loopAttrs)} />`);
|
|
473
|
+
else {
|
|
474
|
+
body.push(` <bpmn:multiInstanceLoopCharacteristics ${attrsToString(loopAttrs)}>`);
|
|
475
|
+
if (p.loopCardinality) body.push(` <bpmn:loopCardinality xsi:type="bpmn:tFormalExpression"><![CDATA[${cdata(p.loopCardinality)}]]></bpmn:loopCardinality>`);
|
|
476
|
+
if (p.completionCondition) body.push(` <bpmn:completionCondition xsi:type="bpmn:tFormalExpression"><![CDATA[${cdata(p.completionCondition)}]]></bpmn:completionCondition>`);
|
|
477
|
+
body.push(' </bpmn:multiInstanceLoopCharacteristics>');
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (node.type === 'adHocSubProcess' && p.adHocCompletionCondition) body.push(` <bpmn:completionCondition xsi:type="bpmn:tFormalExpression"><![CDATA[${cdata(p.adHocCompletionCondition)}]]></bpmn:completionCondition>`);
|
|
481
|
+
if (node.type === 'complexGateway' && p.activationCondition) body.push(` <bpmn:activationCondition xsi:type="bpmn:tFormalExpression"><![CDATA[${cdata(p.activationCondition)}]]></bpmn:activationCondition>`);
|
|
482
|
+
body.push(...(profile.nodeChildElements?.(node) || []).map((raw) => ` ${raw}`));
|
|
483
|
+
body.push(...eventDefinitionBody(node));
|
|
484
|
+
return body;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function nodeTag(node) { return NODE_DEFINITIONS[node.type]?.localName || node.originalLocalName || 'task'; }
|
|
488
|
+
|
|
489
|
+
function nodeAttributes(node, profile, model, defaultBySource) {
|
|
490
|
+
const p = node.properties || {};
|
|
491
|
+
const common = node.type === 'textAnnotation'
|
|
492
|
+
? { id: node.id }
|
|
493
|
+
: { id: node.id, name: node.name };
|
|
494
|
+
if (defaultBySource.has(node.id)) common.default = defaultBySource.get(node.id);
|
|
495
|
+
if (node.type === 'scriptTask' && p.scriptFormat) common.scriptFormat = p.scriptFormat;
|
|
496
|
+
if (node.type === 'callActivity' && p.calledElement) common.calledElement = p.calledElement;
|
|
497
|
+
if (['sendTask', 'receiveTask'].includes(node.type)) {
|
|
498
|
+
if (p.messageRef) common.messageRef = p.messageRef;
|
|
499
|
+
if (p.operationRef) common.operationRef = p.operationRef;
|
|
500
|
+
if (node.type === 'receiveTask' && p.instantiate) common.instantiate = 'true';
|
|
501
|
+
}
|
|
502
|
+
const eventDef = NODE_DEFINITIONS[node.type] || {};
|
|
503
|
+
if ((eventDef.eventStage === 'start' || (eventDef.eventStage === 'intermediate' && eventDef.eventRole === 'catch')) && ['message', 'signal'].includes(eventDef.eventDefinition) && p.parallelMultiple) common.parallelMultiple = 'true';
|
|
504
|
+
if (node.type === 'textAnnotation' && p.textFormat && p.textFormat !== 'text/plain') common.textFormat = p.textFormat;
|
|
505
|
+
if ((NODE_DEFINITIONS[node.type]?.kind || '') === 'gateway' && p.gatewayDirection && p.gatewayDirection !== 'Unspecified') common.gatewayDirection = p.gatewayDirection;
|
|
506
|
+
if (node.type === 'eventBasedGateway') {
|
|
507
|
+
if (p.instantiate) common.instantiate = 'true';
|
|
508
|
+
if (p.eventGatewayType && p.eventGatewayType !== 'Exclusive') common.eventGatewayType = p.eventGatewayType;
|
|
509
|
+
}
|
|
510
|
+
if (node.type === 'adHocSubProcess') {
|
|
511
|
+
if (p.ordering) common.ordering = p.ordering;
|
|
512
|
+
if (p.cancelRemainingInstances === false) common.cancelRemainingInstances = 'false';
|
|
513
|
+
}
|
|
514
|
+
if (NODE_DEFINITIONS[node.type]?.kind === 'boundary') {
|
|
515
|
+
if (p.attachedToRef) common.attachedToRef = p.attachedToRef;
|
|
516
|
+
if (p.cancelActivity === false) common.cancelActivity = 'false';
|
|
517
|
+
}
|
|
518
|
+
if (node.type === 'eventSubProcess') common.triggeredByEvent = 'true';
|
|
519
|
+
if (p.isForCompensation) common.isForCompensation = 'true';
|
|
520
|
+
if (node.type === 'dataObjectReference') common.dataObjectRef = p.dataObjectRef || `${node.id}_Object`;
|
|
521
|
+
if (node.type === 'dataStoreReference') common.dataStoreRef = p.dataStoreRef || `${node.id}_Store`;
|
|
522
|
+
const unknownAttrs = Object.fromEntries((node.unknownAttributes || []).map((a) => [a.name, a.value]));
|
|
523
|
+
return attrsToString({ ...unknownAttrs, ...common, ...profile.nodeAttributes(node) });
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function nodesInsideLane(model, lane) {
|
|
527
|
+
const explicit = lane.properties?.flowNodeRefs;
|
|
528
|
+
if (Array.isArray(explicit)) return explicit;
|
|
529
|
+
return model.nodes.filter((node) => {
|
|
530
|
+
if (node.id === lane.id || ['lane', 'participant', 'group'].includes(node.type)) return false;
|
|
531
|
+
const cx = node.x + node.width / 2;
|
|
532
|
+
const cy = node.y + node.height / 2;
|
|
533
|
+
return cx >= lane.x && cx <= lane.x + lane.width && cy >= lane.y && cy <= lane.y + lane.height;
|
|
534
|
+
}).map((node) => node.id);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export function exportBpmn(model, engineOverride) {
|
|
538
|
+
const engine = engineOverride || model.engine || 'flowable';
|
|
539
|
+
const profile = getProfile(engine);
|
|
540
|
+
const participants = model.nodes.filter((node) => node.type === 'participant');
|
|
541
|
+
const participantOrder = new Map(participants.map((participant, index) => [participant.id, index]));
|
|
542
|
+
const lanes = model.nodes.filter((node) => node.type === 'lane').sort((a, b) => {
|
|
543
|
+
const aOwner = participantOrder.has(a.containerId) ? participantOrder.get(a.containerId) : Number.MAX_SAFE_INTEGER;
|
|
544
|
+
const bOwner = participantOrder.has(b.containerId) ? participantOrder.get(b.containerId) : Number.MAX_SAFE_INTEGER;
|
|
545
|
+
return aOwner - bOwner || a.y - b.y || a.x - b.x || a.id.localeCompare(b.id);
|
|
546
|
+
});
|
|
547
|
+
const groups = model.nodes.filter((node) => node.type === 'group');
|
|
548
|
+
const dataObjects = model.nodes.filter((node) => node.type === 'dataObjectReference');
|
|
549
|
+
const dataStores = model.nodes.filter((node) => node.type === 'dataStoreReference');
|
|
550
|
+
const processNodes = model.nodes.filter((node) => !['participant', 'lane'].includes(node.type));
|
|
551
|
+
const sequenceFlows = model.edges.filter((edge) => (edge.type || 'sequenceFlow') === 'sequenceFlow');
|
|
552
|
+
const associations = model.edges.filter((edge) => edge.type === 'association');
|
|
553
|
+
const messageFlows = model.edges.filter((edge) => edge.type === 'messageFlow');
|
|
554
|
+
const hasCollaboration = participants.length > 0 || messageFlows.length > 0;
|
|
555
|
+
const planeElement = hasCollaboration ? `Collaboration_${model.id}` : model.id;
|
|
556
|
+
|
|
557
|
+
const extraNs = Object.entries(model.namespaceDeclarations || {}).map(([name, value]) => `${name}="${escapeXml(value)}"`).join('\n ');
|
|
558
|
+
const lines = [
|
|
559
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
560
|
+
`<bpmn:definitions xmlns:bpmn="${NS.bpmn}"`,
|
|
561
|
+
` xmlns:bpmndi="${NS.bpmndi}"`,
|
|
562
|
+
` xmlns:dc="${NS.dc}"`,
|
|
563
|
+
` xmlns:di="${NS.di}"`,
|
|
564
|
+
` xmlns:xsi="${NS.xsi}"`,
|
|
565
|
+
` xmlns:${profile.prefix}="${profile.namespace}"`,
|
|
566
|
+
...(extraNs ? [` ${extraNs}`] : []),
|
|
567
|
+
` id="Definitions_${escapeXml(model.id)}"`,
|
|
568
|
+
` targetNamespace="${escapeXml(model.targetNamespace || 'urn:bpmn-nova:process')}">`,
|
|
569
|
+
];
|
|
570
|
+
|
|
571
|
+
for (const message of model.resources?.messages || []) lines.push(` <bpmn:message ${attrsToString({ id: message.id, name: message.name })} />`);
|
|
572
|
+
for (const signal of model.resources?.signals || []) lines.push(` <bpmn:signal ${attrsToString({ id: signal.id, name: signal.name })} />`);
|
|
573
|
+
for (const error of model.resources?.errors || []) lines.push(` <bpmn:error ${attrsToString({ id: error.id, name: error.name, errorCode: error.errorCode })} />`);
|
|
574
|
+
for (const escalation of model.resources?.escalations || []) lines.push(` <bpmn:escalation ${attrsToString({ id: escalation.id, name: escalation.name, escalationCode: escalation.escalationCode })} />`);
|
|
575
|
+
|
|
576
|
+
for (const node of dataStores) lines.push(` <bpmn:dataStore id="${escapeXml(node.properties?.dataStoreRef || `${node.id}_Store`)}" name="${escapeXml(node.name)}" />`);
|
|
577
|
+
for (const group of groups) {
|
|
578
|
+
lines.push(` <bpmn:category id="Category_${escapeXml(group.id)}">`);
|
|
579
|
+
lines.push(` <bpmn:categoryValue id="CategoryValue_${escapeXml(group.id)}" value="${escapeXml(group.properties?.categoryValue || group.name || '分组')}" />`);
|
|
580
|
+
lines.push(' </bpmn:category>');
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const processAttrs = attrsToString({ id: model.id, name: model.name || model.id, isExecutable: model.isExecutable !== false ? 'true' : 'false', ...(profile.processAttributes?.(model) || {}) });
|
|
584
|
+
lines.push(` <bpmn:process ${processAttrs}>`);
|
|
585
|
+
if (model.properties?.documentation) lines.push(` <bpmn:documentation><![CDATA[${cdata(model.properties.documentation)}]]></bpmn:documentation>`);
|
|
586
|
+
const processExtensions = [...(model.extensionElements || []), ...(profile.processExtensionElements?.(model) || [])];
|
|
587
|
+
if (processExtensions.length) {
|
|
588
|
+
lines.push(' <bpmn:extensionElements>');
|
|
589
|
+
for (const raw of processExtensions) lines.push(` ${raw}`);
|
|
590
|
+
lines.push(' </bpmn:extensionElements>');
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (lanes.length) {
|
|
594
|
+
lines.push(` <bpmn:laneSet id="LaneSet_${escapeXml(model.id)}">`);
|
|
595
|
+
for (const lane of lanes) {
|
|
596
|
+
lines.push(` <bpmn:lane id="${escapeXml(lane.id)}" name="${escapeXml(lane.name)}">`);
|
|
597
|
+
for (const ref of nodesInsideLane(model, lane)) lines.push(` <bpmn:flowNodeRef>${escapeXml(ref)}</bpmn:flowNodeRef>`);
|
|
598
|
+
lines.push(' </bpmn:lane>');
|
|
599
|
+
}
|
|
600
|
+
lines.push(' </bpmn:laneSet>');
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
for (const node of dataObjects) lines.push(` <bpmn:dataObject id="${escapeXml(node.properties?.dataObjectRef || `${node.id}_Object`)}" name="${escapeXml(node.name)}" />`);
|
|
604
|
+
|
|
605
|
+
const defaultBySource = new Map(sequenceFlows.filter((edge) => edge.isDefault).map((edge) => [edge.source, edge.id]));
|
|
606
|
+
const serializeScope = (scopeId, indent = ' ') => {
|
|
607
|
+
const output = [];
|
|
608
|
+
const bodyIndent = `${indent} `;
|
|
609
|
+
const reindentBody = (body) => body.map((line) => line.replace(/^ /, bodyIndent));
|
|
610
|
+
for (const node of processNodes.filter((item) => elementScopeId(model, item) === scopeId)) {
|
|
611
|
+
const tag = nodeTag(node);
|
|
612
|
+
const attrs = nodeAttributes(node, profile, model, defaultBySource);
|
|
613
|
+
if (node.type === 'group') {
|
|
614
|
+
output.push(`${indent}<bpmn:group ${attrsToString({ id: node.id, categoryValueRef: `CategoryValue_${node.id}` })} />`);
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
const body = reindentBody(activityBody(node, profile));
|
|
618
|
+
const nested = isEmbeddedSubProcess(node) ? serializeScope(node.id, bodyIndent) : [];
|
|
619
|
+
if (body.length || nested.length) {
|
|
620
|
+
output.push(`${indent}<bpmn:${tag} ${attrs}>`, ...body, ...nested, `${indent}</bpmn:${tag}>`);
|
|
621
|
+
} else output.push(`${indent}<bpmn:${tag} ${attrs} />`);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
for (const edge of sequenceFlows.filter((item) => elementScopeId(model, item) === scopeId)) {
|
|
625
|
+
const unknownAttrs = Object.fromEntries((edge.unknownAttributes || []).map((a) => [a.name, a.value]));
|
|
626
|
+
const attrs = attrsToString({ ...unknownAttrs, ...(edge.properties?.extensionAttributes || {}), id: edge.id, name: edge.name, sourceRef: edge.source, targetRef: edge.target });
|
|
627
|
+
const edgeExtensions = [...(edge.extensionElements || []), ...(profile.edgeExtensionElements?.(edge) || [])];
|
|
628
|
+
if (edge.condition || edgeExtensions.length || edge.properties?.documentation) {
|
|
629
|
+
output.push(`${indent}<bpmn:sequenceFlow ${attrs}>`);
|
|
630
|
+
if (edge.properties?.documentation) output.push(`${bodyIndent}<bpmn:documentation><![CDATA[${cdata(edge.properties.documentation)}]]></bpmn:documentation>`);
|
|
631
|
+
if (edge.condition) output.push(`${bodyIndent}<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression"><![CDATA[${cdata(edge.condition)}]]></bpmn:conditionExpression>`);
|
|
632
|
+
if (edgeExtensions.length) {
|
|
633
|
+
output.push(`${bodyIndent}<bpmn:extensionElements>`);
|
|
634
|
+
for (const raw of edgeExtensions) output.push(`${bodyIndent} ${raw}`);
|
|
635
|
+
output.push(`${bodyIndent}</bpmn:extensionElements>`);
|
|
636
|
+
}
|
|
637
|
+
output.push(`${indent}</bpmn:sequenceFlow>`);
|
|
638
|
+
} else output.push(`${indent}<bpmn:sequenceFlow ${attrs} />`);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
for (const edge of associations.filter((item) => elementScopeId(model, item) === scopeId)) {
|
|
642
|
+
const unknownAttrs = Object.fromEntries((edge.unknownAttributes || []).map((item) => [item.name, item.value]));
|
|
643
|
+
const attrs = attrsToString({ ...unknownAttrs, ...(edge.properties?.extensionAttributes || {}), id: edge.id, sourceRef: edge.source, targetRef: edge.target, associationDirection: edge.properties?.associationDirection && edge.properties.associationDirection !== 'None' ? edge.properties.associationDirection : '' });
|
|
644
|
+
const body = [];
|
|
645
|
+
if (edge.properties?.documentation) body.push(`${bodyIndent}<bpmn:documentation><![CDATA[${cdata(edge.properties.documentation)}]]></bpmn:documentation>`);
|
|
646
|
+
if ((edge.extensionElements || []).length) {
|
|
647
|
+
body.push(`${bodyIndent}<bpmn:extensionElements>`);
|
|
648
|
+
for (const raw of edge.extensionElements || []) body.push(`${bodyIndent} ${raw}`);
|
|
649
|
+
body.push(`${bodyIndent}</bpmn:extensionElements>`);
|
|
650
|
+
}
|
|
651
|
+
if (body.length) output.push(`${indent}<bpmn:association ${attrs}>`, ...body, `${indent}</bpmn:association>`);
|
|
652
|
+
else output.push(`${indent}<bpmn:association ${attrs} />`);
|
|
653
|
+
}
|
|
654
|
+
return output;
|
|
655
|
+
};
|
|
656
|
+
lines.push(...serializeScope(model.id));
|
|
657
|
+
lines.push(' </bpmn:process>');
|
|
658
|
+
|
|
659
|
+
if (hasCollaboration) {
|
|
660
|
+
lines.push(` <bpmn:collaboration id="Collaboration_${escapeXml(model.id)}">`);
|
|
661
|
+
if (participants.length) {
|
|
662
|
+
for (const participant of participants) lines.push(` <bpmn:participant ${attrsToString({ id: participant.id, name: participant.name, processRef: participant.properties?.processRef || model.id })} />`);
|
|
663
|
+
} else {
|
|
664
|
+
lines.push(` <bpmn:participant id="Participant_${escapeXml(model.id)}" name="${escapeXml(model.name)}" processRef="${escapeXml(model.id)}" />`);
|
|
665
|
+
}
|
|
666
|
+
for (const edge of messageFlows) {
|
|
667
|
+
const unknownAttrs = Object.fromEntries((edge.unknownAttributes || []).map((item) => [item.name, item.value]));
|
|
668
|
+
const attrs = attrsToString({ ...unknownAttrs, ...(edge.properties?.extensionAttributes || {}), id: edge.id, name: edge.name, sourceRef: edge.source, targetRef: edge.target, messageRef: edge.properties?.messageRef });
|
|
669
|
+
const body = [];
|
|
670
|
+
if (edge.properties?.documentation) body.push(` <bpmn:documentation><![CDATA[${cdata(edge.properties.documentation)}]]></bpmn:documentation>`);
|
|
671
|
+
if ((edge.extensionElements || []).length) {
|
|
672
|
+
body.push(' <bpmn:extensionElements>');
|
|
673
|
+
for (const raw of edge.extensionElements || []) body.push(` ${raw}`);
|
|
674
|
+
body.push(' </bpmn:extensionElements>');
|
|
675
|
+
}
|
|
676
|
+
if (body.length) {
|
|
677
|
+
lines.push(` <bpmn:messageFlow ${attrs}>`);
|
|
678
|
+
lines.push(...body);
|
|
679
|
+
lines.push(' </bpmn:messageFlow>');
|
|
680
|
+
} else lines.push(` <bpmn:messageFlow ${attrs} />`);
|
|
681
|
+
}
|
|
682
|
+
lines.push(' </bpmn:collaboration>');
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
lines.push(` <bpmndi:BPMNDiagram id="BPMNDiagram_${escapeXml(model.id)}">`);
|
|
686
|
+
lines.push(` <bpmndi:BPMNPlane id="BPMNPlane_${escapeXml(model.id)}" bpmnElement="${escapeXml(planeElement)}">`);
|
|
687
|
+
for (const node of model.nodes) {
|
|
688
|
+
const def = NODE_DEFINITIONS[node.type] || NODE_DEFINITIONS.generic;
|
|
689
|
+
const extra = ['container'].includes(def.kind) ? ' isExpanded="false"' : '';
|
|
690
|
+
lines.push(` <bpmndi:BPMNShape id="${escapeXml(node.id)}_di" bpmnElement="${escapeXml(node.id)}"${extra}>`);
|
|
691
|
+
lines.push(` <dc:Bounds x="${Math.round(node.x)}" y="${Math.round(node.y)}" width="${Math.round(node.width)}" height="${Math.round(node.height)}" />`);
|
|
692
|
+
lines.push(' </bpmndi:BPMNShape>');
|
|
693
|
+
}
|
|
694
|
+
for (const edge of model.edges) {
|
|
695
|
+
lines.push(` <bpmndi:BPMNEdge id="${escapeXml(edge.id)}_di" bpmnElement="${escapeXml(edge.id)}">`);
|
|
696
|
+
for (const point of edgeWaypoints(model, edge)) lines.push(` <di:waypoint x="${Math.round(point.x)}" y="${Math.round(point.y)}" />`);
|
|
697
|
+
lines.push(' </bpmndi:BPMNEdge>');
|
|
698
|
+
}
|
|
699
|
+
lines.push(' </bpmndi:BPMNPlane>');
|
|
700
|
+
lines.push(' </bpmndi:BPMNDiagram>');
|
|
701
|
+
lines.push('</bpmn:definitions>');
|
|
702
|
+
return lines.join('\n');
|
|
703
|
+
}
|