@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.
Files changed (63) hide show
  1. package/README.md +245 -20
  2. package/dist/canvas.js +7 -4
  3. package/dist/context-menu.js +2 -2
  4. package/dist/controller.js +84 -6
  5. package/dist/index.d.ts +138 -38
  6. package/dist/index.js +12 -11
  7. package/dist/interactions.js +1 -1
  8. package/dist/modules/bpmn-model/index.d.ts +11 -0
  9. package/dist/modules/bpmn-model/index.js +703 -0
  10. package/dist/modules/core/containment.js +590 -0
  11. package/dist/modules/core/gateway.js +72 -0
  12. package/dist/modules/core/history.js +32 -0
  13. package/dist/modules/core/index.d.ts +268 -0
  14. package/dist/modules/core/index.js +7 -0
  15. package/dist/modules/core/layout.js +644 -0
  16. package/dist/modules/core/model.js +287 -0
  17. package/dist/modules/core/runtime-transition-route.js +360 -0
  18. package/dist/modules/core/scope.js +99 -0
  19. package/dist/modules/designer/index.d.ts +79 -0
  20. package/dist/modules/designer/index.js +607 -0
  21. package/dist/modules/engine-activiti/index.d.ts +19 -0
  22. package/dist/modules/engine-activiti/index.js +160 -0
  23. package/dist/modules/engine-flowable/index.d.ts +19 -0
  24. package/dist/modules/engine-flowable/index.js +160 -0
  25. package/dist/modules/export-svg/index.d.ts +112 -0
  26. package/dist/modules/export-svg/index.js +2 -0
  27. package/dist/modules/export-svg/preview.js +327 -0
  28. package/dist/modules/export-svg/render.js +718 -0
  29. package/dist/modules/icons/index.d.ts +24 -0
  30. package/dist/modules/icons/index.js +264 -0
  31. package/dist/modules/palette/index.d.ts +74 -0
  32. package/dist/modules/palette/index.js +99 -0
  33. package/dist/modules/palette/panel.js +99 -0
  34. package/dist/modules/properties/index.d.ts +20 -0
  35. package/dist/modules/properties/index.js +19 -0
  36. package/dist/modules/properties-activiti/index.d.ts +3 -0
  37. package/dist/modules/properties-activiti/index.js +97 -0
  38. package/dist/modules/properties-bpmn/index.d.ts +3 -0
  39. package/dist/modules/properties-bpmn/index.js +518 -0
  40. package/dist/modules/properties-core/index.d.ts +124 -0
  41. package/dist/modules/properties-core/index.js +312 -0
  42. package/dist/modules/properties-flowable/index.d.ts +3 -0
  43. package/dist/modules/properties-flowable/index.js +114 -0
  44. package/dist/modules/properties-renderer/index.d.ts +25 -0
  45. package/dist/modules/properties-renderer/index.js +491 -0
  46. package/dist/modules/renderer-svg/index.d.ts +118 -0
  47. package/dist/modules/renderer-svg/index.js +1460 -0
  48. package/dist/modules/runtime/index.d.ts +169 -0
  49. package/dist/modules/runtime/index.js +535 -0
  50. package/dist/modules/theme/index.d.ts +95 -0
  51. package/dist/modules/theme/index.js +368 -0
  52. package/dist/modules/viewer/index.d.ts +265 -0
  53. package/dist/modules/viewer/index.js +1011 -0
  54. package/dist/modules/viewer/runtime-content.js +123 -0
  55. package/dist/modules/viewer/runtime-details-motion.js +228 -0
  56. package/dist/modules/viewer/runtime-trace.js +574 -0
  57. package/dist/modules/viewer/timeline.js +276 -0
  58. package/dist/selection-layout.js +1 -1
  59. package/dist/shell.js +210 -26
  60. package/dist/styles.css +116 -7
  61. package/llms-full.txt +3082 -0
  62. package/llms.txt +225 -0
  63. 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('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&apos;');
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
+ }