@waron97/prbot 3.2.0 → 3.3.0
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 +26 -18
- package/agrippa-pb.md +172 -74
- package/package.json +1 -1
- package/src/agrippa/commands/clone.js +7 -1
- package/src/agrippa/commands/cloneLrp.js +114 -0
- package/src/agrippa/commands/diff.js +23 -10
- package/src/agrippa/commands/initPhase.js +44 -34
- package/src/agrippa/commands/pb.js +61 -16
- package/src/agrippa/commands/pull.js +93 -17
- package/src/agrippa/commands/pullLrp.js +55 -0
- package/src/agrippa/commands/push.js +88 -19
- package/src/agrippa/commands/pushLrp.js +56 -0
- package/src/agrippa/index.js +32 -17
- package/src/agrippa/lib/lrpApi.js +153 -0
- package/src/agrippa/lib/pbEdit.js +103 -6
- package/src/agrippa/lib/pbLayout.js +23 -2
- package/src/agrippa/lib/pbModel.js +324 -90
- package/src/agrippa/lib/pbPreview.js +4 -2
- package/src/agrippa/lib/pbProject.js +34 -5
- package/src/agrippa/lib/pbScriptTemplate.js +29 -0
- package/src/commands/autopr.js +1 -1
|
@@ -84,6 +84,22 @@ const NODE_TAGS = new Set([
|
|
|
84
84
|
'subProcess',
|
|
85
85
|
'transaction',
|
|
86
86
|
'boundaryEvent',
|
|
87
|
+
// LRP-only (also legal on PBs, just unused there so far):
|
|
88
|
+
'intermediateCatchEvent',
|
|
89
|
+
'intermediateThrowEvent',
|
|
90
|
+
'callActivity',
|
|
91
|
+
'parallelGateway',
|
|
92
|
+
'eventBasedGateway',
|
|
93
|
+
]);
|
|
94
|
+
|
|
95
|
+
// Flow-node types that can carry <xEventDefinition/> children (see
|
|
96
|
+
// parseEventDefinitions/buildEventDefinitions below).
|
|
97
|
+
const EVENT_HOST_TAGS = new Set([
|
|
98
|
+
'startEvent',
|
|
99
|
+
'endEvent',
|
|
100
|
+
'boundaryEvent',
|
|
101
|
+
'intermediateCatchEvent',
|
|
102
|
+
'intermediateThrowEvent',
|
|
87
103
|
]);
|
|
88
104
|
|
|
89
105
|
const PROMOTED_ATTRS = {
|
|
@@ -95,8 +111,14 @@ const PROMOTED_ATTRS = {
|
|
|
95
111
|
boundaryEvent: ['@_id', '@_name', '@_attachedToRef'],
|
|
96
112
|
startEvent: ['@_id', '@_name'],
|
|
97
113
|
endEvent: ['@_id', '@_name'],
|
|
114
|
+
intermediateCatchEvent: ['@_id', '@_name'],
|
|
115
|
+
intermediateThrowEvent: ['@_id', '@_name'],
|
|
116
|
+
callActivity: ['@_id', '@_name', '@_calledElement', '@_activiti:class'],
|
|
117
|
+
parallelGateway: ['@_id', '@_name'],
|
|
118
|
+
eventBasedGateway: ['@_id', '@_name'],
|
|
98
119
|
process_root: ['@_id', '@_name', '@_isExecutable'],
|
|
99
120
|
association: ['@_id', '@_sourceRef', '@_targetRef'],
|
|
121
|
+
conditionExpression: ['@_xsi:type'],
|
|
100
122
|
};
|
|
101
123
|
|
|
102
124
|
// Collect the non-promoted attributes into a plain map (prefix stripped),
|
|
@@ -111,19 +133,28 @@ function extraAttrs(attrs, type) {
|
|
|
111
133
|
return out;
|
|
112
134
|
}
|
|
113
135
|
|
|
136
|
+
// Returns undefined if the node has no <extensionElements> at all (nothing to
|
|
137
|
+
// rebuild); an array (possibly empty) if it does — some real BPMNs carry a
|
|
138
|
+
// bare `<extensionElements/>` with zero activiti:field children, and that
|
|
139
|
+
// empty wrapper must still round-trip (see buildFlowChildren's serviceTask case).
|
|
114
140
|
function parseServiceFields(node) {
|
|
115
141
|
const ext = node.serviceTask?.find((k) => tagOf(k) === 'extensionElements');
|
|
116
|
-
if (!ext) return
|
|
142
|
+
if (!ext) return undefined;
|
|
117
143
|
const fields = [];
|
|
118
144
|
for (const f of ext.extensionElements) {
|
|
119
145
|
if (tagOf(f) !== 'activiti:field') continue;
|
|
120
146
|
const name = attrsOf(f)['@_name'];
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
147
|
+
// Normally exactly one of activiti:string/activiti:expression, but the
|
|
148
|
+
// real corpus has fields carrying both (likely a copy-paste artifact
|
|
149
|
+
// upstream) — preserve every child, in order, rather than picking one.
|
|
150
|
+
const parts = f['activiti:field']
|
|
151
|
+
.filter((k) => tagOf(k) === 'activiti:string' || tagOf(k) === 'activiti:expression')
|
|
152
|
+
.map((k) => {
|
|
153
|
+
const kind = tagOf(k) === 'activiti:string' ? 'string' : 'expression';
|
|
154
|
+
return { kind, value: cdataOf(k, tagOf(k)) ?? '' };
|
|
155
|
+
});
|
|
156
|
+
if (parts.length) {
|
|
157
|
+
fields.push({ name, parts });
|
|
127
158
|
} else {
|
|
128
159
|
fields.push({ name }); // empty <activiti:field/>
|
|
129
160
|
}
|
|
@@ -156,10 +187,83 @@ function parseMultiInstance(node, tag) {
|
|
|
156
187
|
return out;
|
|
157
188
|
}
|
|
158
189
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
190
|
+
// Restore a prefix-stripped extra-attrs map back to `@_`-prefixed XML attrs.
|
|
191
|
+
function restoreAttr(extra) {
|
|
192
|
+
const out = {};
|
|
193
|
+
for (const [k, v] of Object.entries(extra || {})) out[`@_${k}`] = v;
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Event-definition child tags legal on startEvent/endEvent/boundaryEvent/
|
|
198
|
+
// intermediateCatchEvent/intermediateThrowEvent. Each carries only reference
|
|
199
|
+
// attrs (messageRef/errorRef/signalRef) except timerEventDefinition, which
|
|
200
|
+
// nests one of timeDuration/timeDate/timeCycle (text + xsi:type attr).
|
|
201
|
+
const EVENT_DEF_TAGS = new Set([
|
|
202
|
+
'messageEventDefinition',
|
|
203
|
+
'errorEventDefinition',
|
|
204
|
+
'timerEventDefinition',
|
|
205
|
+
'terminateEventDefinition',
|
|
206
|
+
'signalEventDefinition',
|
|
207
|
+
]);
|
|
208
|
+
const TIMER_CHILD_TAGS = new Set(['timeDuration', 'timeDate', 'timeCycle']);
|
|
209
|
+
|
|
210
|
+
function parseTimerChildren(defNode, tag) {
|
|
211
|
+
const kids = defNode[tag];
|
|
212
|
+
if (!Array.isArray(kids)) return [];
|
|
213
|
+
return kids
|
|
214
|
+
.filter((k) => TIMER_CHILD_TAGS.has(tagOf(k)))
|
|
215
|
+
.map((k) => ({ tag: tagOf(k), value: textOf(k, tagOf(k)) ?? '', attrs: allAttrs(k) }));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Parse every event-definition child of a node (order preserved; almost always
|
|
219
|
+
// exactly one, but modeled as an array — never observed >1 in the corpus).
|
|
220
|
+
function parseEventDefinitions(child, tag) {
|
|
221
|
+
const kids = child[tag];
|
|
222
|
+
if (!Array.isArray(kids)) return [];
|
|
223
|
+
const defs = [];
|
|
224
|
+
for (const k of kids) {
|
|
225
|
+
const dtag = tagOf(k);
|
|
226
|
+
if (!EVENT_DEF_TAGS.has(dtag)) continue;
|
|
227
|
+
const d = { type: dtag, attrs: allAttrs(k) };
|
|
228
|
+
if (dtag === 'timerEventDefinition') {
|
|
229
|
+
const timer = parseTimerChildren(k, dtag);
|
|
230
|
+
if (timer.length) d.timer = timer;
|
|
231
|
+
}
|
|
232
|
+
defs.push(d);
|
|
233
|
+
}
|
|
234
|
+
return defs;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function buildEventDefinitions(defs) {
|
|
238
|
+
return (defs || []).map((d) => {
|
|
239
|
+
const kids = (d.timer || []).map((t) =>
|
|
240
|
+
el(t.tag, restoreAttr(t.attrs), t.value != null ? [textChild(t.value)] : [])
|
|
241
|
+
);
|
|
242
|
+
return el(d.type, restoreAttr(d.attrs), kids);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// callActivity data mappings live in <extensionElements><activiti:in/out .../>
|
|
247
|
+
// (self-closing, source/target attrs only — same shape both ways).
|
|
248
|
+
function parseCallActivityIO(child) {
|
|
249
|
+
const ext = child.callActivity?.find((k) => tagOf(k) === 'extensionElements');
|
|
250
|
+
if (!ext) return [];
|
|
251
|
+
const io = [];
|
|
252
|
+
for (const f of ext.extensionElements) {
|
|
253
|
+
const t = tagOf(f);
|
|
254
|
+
if (t === 'activiti:in' || t === 'activiti:out') {
|
|
255
|
+
io.push({ dir: t === 'activiti:in' ? 'in' : 'out', ...allAttrs(f) });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return io;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function buildCallActivityIO(io) {
|
|
262
|
+
if (!io || !io.length) return [];
|
|
263
|
+
const ioEls = io.map((d) =>
|
|
264
|
+
el(`activiti:${d.dir}`, { '@_source': d.source, '@_target': d.target }, [])
|
|
265
|
+
);
|
|
266
|
+
return [el('extensionElements', {}, ioEls)];
|
|
163
267
|
}
|
|
164
268
|
|
|
165
269
|
// Parse the children of a <process> or <subProcess> element array into model.
|
|
@@ -173,26 +277,39 @@ function parseFlowChildren(children, parentId, model) {
|
|
|
173
277
|
if (parentId === null) model.process.documentation = textOf(child, tag);
|
|
174
278
|
continue;
|
|
175
279
|
}
|
|
176
|
-
if (tag === 'error') {
|
|
177
|
-
model.errors.push({ id: attrs['@_id'], name: attrs['@_name'] });
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
280
|
if (tag === 'textAnnotation') {
|
|
181
281
|
const txtEl = child.textAnnotation.find((k) => tagOf(k) === 'text');
|
|
182
|
-
|
|
282
|
+
const a = {
|
|
183
283
|
id: attrs['@_id'],
|
|
184
284
|
attrs: extraAttrs(attrs, 'textAnnotation'),
|
|
185
285
|
text: txtEl ? textOf(txtEl, 'text') : '',
|
|
186
|
-
}
|
|
286
|
+
};
|
|
287
|
+
if (parentId) a.parent = parentId;
|
|
288
|
+
model.annotations.push(a);
|
|
187
289
|
continue;
|
|
188
290
|
}
|
|
189
291
|
if (tag === 'association') {
|
|
190
|
-
|
|
292
|
+
const a = {
|
|
191
293
|
id: attrs['@_id'],
|
|
192
294
|
sourceRef: attrs['@_sourceRef'],
|
|
193
295
|
targetRef: attrs['@_targetRef'],
|
|
194
296
|
attrs: extraAttrs({ ...attrs }, 'association'),
|
|
195
|
-
}
|
|
297
|
+
};
|
|
298
|
+
if (parentId) a.parent = parentId;
|
|
299
|
+
model.associations.push(a);
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (tag === 'group') {
|
|
303
|
+
// Visual grouping box, references a <definitions>-root
|
|
304
|
+
// categoryValue by id. Has its own bpmndi:BPMNShape (geometry) but
|
|
305
|
+
// no flow semantics of its own — modeled like an annotation.
|
|
306
|
+
const g = {
|
|
307
|
+
id: attrs['@_id'],
|
|
308
|
+
categoryValueRef: attrs['@_categoryValueRef'],
|
|
309
|
+
attrs: extraAttrs({ ...attrs }, 'group'),
|
|
310
|
+
};
|
|
311
|
+
if (parentId) g.parent = parentId;
|
|
312
|
+
model.groups.push(g);
|
|
196
313
|
continue;
|
|
197
314
|
}
|
|
198
315
|
if (tag === 'sequenceFlow') {
|
|
@@ -206,10 +323,23 @@ function parseFlowChildren(children, parentId, model) {
|
|
|
206
323
|
if (attrs['@_name'] !== undefined) edge.name = attrs['@_name'];
|
|
207
324
|
if (parentId) edge.parent = parentId;
|
|
208
325
|
if (cond) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
326
|
+
// A self-closing/childless <conditionExpression/> (xsi:type
|
|
327
|
+
// only, no text) is real in the corpus — don't synthesize an
|
|
328
|
+
// empty CDATA body for it on rebuild (see conditionType below).
|
|
329
|
+
const condKids = cond.conditionExpression;
|
|
330
|
+
if (Array.isArray(condKids) && condKids.length) {
|
|
331
|
+
edge.condition =
|
|
332
|
+
cdataOf(cond, 'conditionExpression') ??
|
|
333
|
+
textOf(cond, 'conditionExpression') ??
|
|
334
|
+
'';
|
|
335
|
+
}
|
|
336
|
+
const condAttrs = attrsOf(cond);
|
|
337
|
+
const xsi = condAttrs['@_xsi:type'];
|
|
212
338
|
if (xsi) edge.conditionType = xsi;
|
|
339
|
+
// Rare non-standard attrs (e.g. a stray `language="..."` seen
|
|
340
|
+
// in the corpus) — preserve verbatim rather than drop.
|
|
341
|
+
const extra = extraAttrs(condAttrs, 'conditionExpression');
|
|
342
|
+
if (Object.keys(extra).length) edge.conditionAttrs = extra;
|
|
213
343
|
}
|
|
214
344
|
if (doc) edge.documentation = textOf(doc, 'documentation');
|
|
215
345
|
model.edges.push(edge);
|
|
@@ -237,15 +367,23 @@ function parseFlowChildren(children, parentId, model) {
|
|
|
237
367
|
} else if (tag === 'boundaryEvent') {
|
|
238
368
|
if (attrs['@_attachedToRef'] !== undefined)
|
|
239
369
|
n.attachedToRef = attrs['@_attachedToRef'];
|
|
240
|
-
|
|
241
|
-
if (
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
370
|
+
} else if (tag === 'callActivity') {
|
|
371
|
+
if (attrs['@_calledElement'] !== undefined)
|
|
372
|
+
n.calledElement = attrs['@_calledElement'];
|
|
373
|
+
if (attrs['@_activiti:class'] !== undefined) n.class = attrs['@_activiti:class'];
|
|
374
|
+
const io = parseCallActivityIO(child);
|
|
375
|
+
if (io.length) n.dataIO = io;
|
|
245
376
|
} else if (CONTAINER_TAGS.has(tag)) {
|
|
246
377
|
const mi = parseMultiInstance(child, tag);
|
|
247
378
|
if (mi) n.multiInstance = mi;
|
|
248
379
|
}
|
|
380
|
+
// Event-definition children apply across several event-host types
|
|
381
|
+
// (start/end/boundary/intermediate catch+throw) regardless of the
|
|
382
|
+
// per-type branch above.
|
|
383
|
+
if (EVENT_HOST_TAGS.has(tag)) {
|
|
384
|
+
const defs = parseEventDefinitions(child, tag);
|
|
385
|
+
if (defs.length) n.eventDefinitions = defs;
|
|
386
|
+
}
|
|
249
387
|
model.nodes.push(n);
|
|
250
388
|
|
|
251
389
|
if (CONTAINER_TAGS.has(tag)) {
|
|
@@ -278,12 +416,30 @@ function parseProcess(builtPage) {
|
|
|
278
416
|
if (Object.keys(extra).length) p.attrs = extra;
|
|
279
417
|
return p;
|
|
280
418
|
})(),
|
|
419
|
+
// Declared at <definitions> root, as siblings of <process> — NOT nested
|
|
420
|
+
// inside it (verified against the real corpus). messageRef/errorRef/
|
|
421
|
+
// signalRef on event definitions resolve against these.
|
|
422
|
+
messages: [],
|
|
281
423
|
errors: [],
|
|
424
|
+
signals: [],
|
|
425
|
+
// Any other <definitions>-level child we don't model explicitly
|
|
426
|
+
// (e.g. <category>/<categoryValue> — rare) — kept as raw preserveOrder
|
|
427
|
+
// nodes and re-emitted verbatim, so nothing at this level is ever lost.
|
|
428
|
+
extraDefs: [],
|
|
282
429
|
nodes: [],
|
|
283
430
|
edges: [],
|
|
284
431
|
annotations: [],
|
|
285
432
|
associations: [],
|
|
433
|
+
groups: [],
|
|
286
434
|
};
|
|
435
|
+
for (const c of defNode.definitions) {
|
|
436
|
+
const t = tagOf(c);
|
|
437
|
+
if (t === 'process' || t === 'bpmndi:BPMNDiagram') continue;
|
|
438
|
+
if (t === 'message') model.messages.push({ attrs: allAttrs(c) });
|
|
439
|
+
else if (t === 'error') model.errors.push({ attrs: allAttrs(c) });
|
|
440
|
+
else if (t === 'signal') model.signals.push({ attrs: allAttrs(c) });
|
|
441
|
+
else model.extraDefs.push(c);
|
|
442
|
+
}
|
|
287
443
|
parseFlowChildren(procNode.process, null, model);
|
|
288
444
|
|
|
289
445
|
return { ns, model, diagram: parseDiagram(builtPage) };
|
|
@@ -299,6 +455,20 @@ function num(v) {
|
|
|
299
455
|
return Number.isNaN(n) ? v : n;
|
|
300
456
|
}
|
|
301
457
|
|
|
458
|
+
// The DD/DI package (waypoint's namespace) is bound to different prefixes
|
|
459
|
+
// across the corpus — usually `omgdi:` but some exporters use `di:` for the
|
|
460
|
+
// identical namespace URI. Resolve the real prefix from the <definitions>
|
|
461
|
+
// root's xmlns declarations instead of hardcoding one, so both the `<process>`
|
|
462
|
+
// diagram bpmnElement and its waypoint tag stay readable and re-emittable
|
|
463
|
+
// under whichever prefix the source actually declared.
|
|
464
|
+
const DI_NS_URI = 'http://www.omg.org/spec/DD/20100524/DI';
|
|
465
|
+
function diWaypointTag(ns) {
|
|
466
|
+
for (const [k, v] of Object.entries(ns || {})) {
|
|
467
|
+
if (v === DI_NS_URI) return `${k.replace(/^@_xmlns:/, '')}:waypoint`;
|
|
468
|
+
}
|
|
469
|
+
return 'omgdi:waypoint';
|
|
470
|
+
}
|
|
471
|
+
|
|
302
472
|
function parseLabel(labelNode) {
|
|
303
473
|
const b = labelNode['bpmndi:BPMNLabel'].find((k) => tagOf(k) === 'omgdc:Bounds');
|
|
304
474
|
return { attrs: allAttrs(labelNode), bounds: b ? mapBounds(allAttrs(b)) : null };
|
|
@@ -312,6 +482,8 @@ function mapBounds(a) {
|
|
|
312
482
|
function parseDiagram(builtPage) {
|
|
313
483
|
const tree = parser.parse(builtPage);
|
|
314
484
|
const defNode = tree.find((n) => tagOf(n) === 'definitions');
|
|
485
|
+
const ns = attrsOf(defNode);
|
|
486
|
+
const wpTag = diWaypointTag(ns);
|
|
315
487
|
const dia = defNode.definitions.find((n) => tagOf(n) === 'bpmndi:BPMNDiagram');
|
|
316
488
|
if (!dia) return null;
|
|
317
489
|
const plane = dia['bpmndi:BPMNDiagram'].find((n) => tagOf(n) === 'bpmndi:BPMNPlane');
|
|
@@ -330,7 +502,7 @@ function parseDiagram(builtPage) {
|
|
|
330
502
|
});
|
|
331
503
|
} else if (tg === 'bpmndi:BPMNEdge') {
|
|
332
504
|
const wps = c['bpmndi:BPMNEdge']
|
|
333
|
-
.filter((x) => tagOf(x) ===
|
|
505
|
+
.filter((x) => tagOf(x) === wpTag)
|
|
334
506
|
.map((w) => mapBounds(allAttrs(w)));
|
|
335
507
|
const label = c['bpmndi:BPMNEdge'].find((x) => tagOf(x) === 'bpmndi:BPMNLabel');
|
|
336
508
|
edges.push({
|
|
@@ -353,11 +525,13 @@ function boundsAttrs(b) {
|
|
|
353
525
|
}
|
|
354
526
|
|
|
355
527
|
// Rebuild <bpmndi> purely from structure.yaml geometry (model graph + geo maps).
|
|
356
|
-
// DI element ids are derived (`<id>_di`); the manifest is never consulted.
|
|
357
|
-
// are omitted (renderers auto-place them);
|
|
528
|
+
// DI element ids are derived (`<id>_di`); the manifest is never consulted. Node
|
|
529
|
+
// labels are omitted (renderers auto-place them); edge labels use ELK-computed
|
|
530
|
+
// bounds from geo.labelPos when present — only positions, sizes, waypoints and
|
|
358
531
|
// subprocess expand-state are authoritative — see compareDiagram.
|
|
359
|
-
function buildDiagram(model, geo) {
|
|
532
|
+
function buildDiagram(model, geo, ns) {
|
|
360
533
|
if (!geo) return null;
|
|
534
|
+
const wpTag = diWaypointTag(ns);
|
|
361
535
|
const shapeEls = [];
|
|
362
536
|
const pushShape = (id) => {
|
|
363
537
|
const b = geo.bounds[id];
|
|
@@ -368,15 +542,19 @@ function buildDiagram(model, geo) {
|
|
|
368
542
|
};
|
|
369
543
|
for (const n of model.nodes) pushShape(n.id);
|
|
370
544
|
for (const a of model.annotations || []) pushShape(a.id);
|
|
545
|
+
for (const g of model.groups || []) pushShape(g.id);
|
|
371
546
|
|
|
372
547
|
const edgeEls = [];
|
|
373
548
|
const pushEdge = (id) => {
|
|
374
549
|
const wps = geo.waypoints[id];
|
|
375
550
|
if (!wps) return;
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
)
|
|
379
|
-
|
|
551
|
+
const children = [];
|
|
552
|
+
const lp = geo.labelPos?.[id];
|
|
553
|
+
for (const [x, y] of wps)
|
|
554
|
+
children.push(el(wpTag, { '@_x': String(x), '@_y': String(y) }, []));
|
|
555
|
+
if (lp)
|
|
556
|
+
children.push(el('bpmndi:BPMNLabel', {}, [el('omgdc:Bounds', boundsAttrs(lp), [])]));
|
|
557
|
+
edgeEls.push(el('bpmndi:BPMNEdge', { '@_id': `${id}_di`, '@_bpmnElement': id }, children));
|
|
380
558
|
};
|
|
381
559
|
for (const e of model.edges) pushEdge(e.id);
|
|
382
560
|
for (const a of model.associations || []) pushEdge(a.id);
|
|
@@ -399,18 +577,8 @@ function buildFlowChildren(model, parentId) {
|
|
|
399
577
|
if (parentId === null && model.process.documentation != null) {
|
|
400
578
|
children.push(el('documentation', {}, [textChild(model.process.documentation)]));
|
|
401
579
|
}
|
|
402
|
-
//
|
|
403
|
-
|
|
404
|
-
for (const e of model.errors) {
|
|
405
|
-
children.push(el('error', { '@_id': e.id, '@_name': e.name }, []));
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
const restoreAttr = (extra) => {
|
|
410
|
-
const out = {};
|
|
411
|
-
for (const [k, v] of Object.entries(extra || {})) out[`@_${k}`] = v;
|
|
412
|
-
return out;
|
|
413
|
-
};
|
|
580
|
+
// message/error/signal declarations are <definitions>-root siblings of
|
|
581
|
+
// <process>, not process children — built separately in buildProcess().
|
|
414
582
|
|
|
415
583
|
const incoming = (id) => model.edges.filter((e) => e.target === id).map((e) => e.id);
|
|
416
584
|
const outgoing = (id) => model.edges.filter((e) => e.source === id).map((e) => e.id);
|
|
@@ -443,16 +611,15 @@ function buildFlowChildren(model, parentId) {
|
|
|
443
611
|
if (n.class !== undefined) attrs['@_activiti:class'] = n.class;
|
|
444
612
|
Object.assign(attrs, restoreAttr(n.attrs));
|
|
445
613
|
const kids = [];
|
|
446
|
-
if (n.fields
|
|
614
|
+
if (n.fields !== undefined) {
|
|
447
615
|
const fieldEls = n.fields.map((f) => {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
} else if (Object.prototype.hasOwnProperty.call(f, 'expression')) {
|
|
452
|
-
inner = [el('activiti:expression', {}, [cdataChild(f.expression)])];
|
|
453
|
-
}
|
|
616
|
+
const inner = (f.parts || []).map((p) =>
|
|
617
|
+
el(`activiti:${p.kind}`, {}, [cdataChild(p.value)])
|
|
618
|
+
);
|
|
454
619
|
return el('activiti:field', { '@_name': f.name }, inner);
|
|
455
620
|
});
|
|
621
|
+
// Emit the wrapper even with zero fields — some sources carry a
|
|
622
|
+
// bare <extensionElements/> (see parseServiceFields).
|
|
456
623
|
kids.push(el('extensionElements', {}, fieldEls));
|
|
457
624
|
}
|
|
458
625
|
kids.push(...baseKids(n));
|
|
@@ -466,22 +633,35 @@ function buildFlowChildren(model, parentId) {
|
|
|
466
633
|
children.push(el('exclusiveGateway', attrs, baseKids(n)));
|
|
467
634
|
} else if (n.type === 'startEvent') {
|
|
468
635
|
Object.assign(attrs, restoreAttr(n.attrs));
|
|
469
|
-
|
|
636
|
+
const kids = baseKids(n);
|
|
637
|
+
kids.push(...buildEventDefinitions(n.eventDefinitions));
|
|
638
|
+
children.push(el('startEvent', attrs, kids));
|
|
470
639
|
} else if (n.type === 'endEvent') {
|
|
471
640
|
Object.assign(attrs, restoreAttr(n.attrs));
|
|
472
641
|
const kids = baseKids(n);
|
|
473
|
-
|
|
474
|
-
kids.push(el('errorEventDefinition', restoreAttr(n.errorEventDefinition), []));
|
|
475
|
-
}
|
|
642
|
+
kids.push(...buildEventDefinitions(n.eventDefinitions));
|
|
476
643
|
children.push(el('endEvent', attrs, kids));
|
|
477
644
|
} else if (n.type === 'boundaryEvent') {
|
|
478
645
|
if (n.attachedToRef !== undefined) attrs['@_attachedToRef'] = n.attachedToRef;
|
|
479
646
|
Object.assign(attrs, restoreAttr(n.attrs));
|
|
480
647
|
const kids = baseKids(n);
|
|
481
|
-
|
|
482
|
-
kids.push(el('errorEventDefinition', restoreAttr(n.errorEventDefinition), []));
|
|
483
|
-
}
|
|
648
|
+
kids.push(...buildEventDefinitions(n.eventDefinitions));
|
|
484
649
|
children.push(el('boundaryEvent', attrs, kids));
|
|
650
|
+
} else if (n.type === 'intermediateCatchEvent' || n.type === 'intermediateThrowEvent') {
|
|
651
|
+
Object.assign(attrs, restoreAttr(n.attrs));
|
|
652
|
+
const kids = baseKids(n);
|
|
653
|
+
kids.push(...buildEventDefinitions(n.eventDefinitions));
|
|
654
|
+
children.push(el(n.type, attrs, kids));
|
|
655
|
+
} else if (n.type === 'callActivity') {
|
|
656
|
+
if (n.calledElement !== undefined) attrs['@_calledElement'] = n.calledElement;
|
|
657
|
+
if (n.class !== undefined) attrs['@_activiti:class'] = n.class;
|
|
658
|
+
Object.assign(attrs, restoreAttr(n.attrs));
|
|
659
|
+
const kids = buildCallActivityIO(n.dataIO);
|
|
660
|
+
kids.push(...baseKids(n));
|
|
661
|
+
children.push(el('callActivity', attrs, kids));
|
|
662
|
+
} else if (n.type === 'parallelGateway' || n.type === 'eventBasedGateway') {
|
|
663
|
+
Object.assign(attrs, restoreAttr(n.attrs));
|
|
664
|
+
children.push(el(n.type, attrs, baseKids(n)));
|
|
485
665
|
} else if (CONTAINER_TAGS.has(n.type)) {
|
|
486
666
|
Object.assign(attrs, restoreAttr(n.attrs));
|
|
487
667
|
const kids = baseKids(n);
|
|
@@ -522,45 +702,67 @@ function buildFlowChildren(model, parentId) {
|
|
|
522
702
|
const kids = [];
|
|
523
703
|
if (e.documentation != null)
|
|
524
704
|
kids.push(el('documentation', {}, [textChild(e.documentation)]));
|
|
525
|
-
if (e.condition != null) {
|
|
526
|
-
const cAttrs =
|
|
527
|
-
|
|
705
|
+
if (e.condition != null || e.conditionType !== undefined || e.conditionAttrs) {
|
|
706
|
+
const cAttrs = {
|
|
707
|
+
...(e.conditionType ? { '@_xsi:type': e.conditionType } : {}),
|
|
708
|
+
...restoreAttr(e.conditionAttrs),
|
|
709
|
+
};
|
|
710
|
+
const condKids = e.condition != null ? [cdataChild(e.condition)] : [];
|
|
711
|
+
kids.push(el('conditionExpression', cAttrs, condKids));
|
|
528
712
|
}
|
|
529
713
|
children.push(el('sequenceFlow', attrs, kids));
|
|
530
714
|
}
|
|
531
715
|
|
|
532
|
-
// annotations + associations (
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
716
|
+
// annotations + associations + groups scoped to this parent (nested inside
|
|
717
|
+
// a subProcess/transaction in the original just as often as at root).
|
|
718
|
+
for (const a of model.annotations.filter((x) => (x.parent ?? null) === parentId)) {
|
|
719
|
+
children.push(
|
|
720
|
+
el('textAnnotation', { '@_id': a.id, ...restoreAttr(a.attrs) }, [
|
|
721
|
+
el('text', {}, [textChild(a.text ?? '')]),
|
|
722
|
+
])
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
for (const a of model.associations.filter((x) => (x.parent ?? null) === parentId)) {
|
|
726
|
+
children.push(
|
|
727
|
+
el(
|
|
728
|
+
'association',
|
|
729
|
+
{
|
|
730
|
+
'@_id': a.id,
|
|
731
|
+
'@_sourceRef': a.sourceRef,
|
|
732
|
+
'@_targetRef': a.targetRef,
|
|
733
|
+
...restoreAttr(a.attrs),
|
|
734
|
+
},
|
|
735
|
+
[]
|
|
736
|
+
)
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
for (const g of (model.groups || []).filter((x) => (x.parent ?? null) === parentId)) {
|
|
740
|
+
children.push(
|
|
741
|
+
el(
|
|
742
|
+
'group',
|
|
743
|
+
{ '@_id': g.id, '@_categoryValueRef': g.categoryValueRef, ...restoreAttr(g.attrs) },
|
|
744
|
+
[]
|
|
745
|
+
)
|
|
746
|
+
);
|
|
555
747
|
}
|
|
556
748
|
|
|
557
749
|
return children;
|
|
558
750
|
}
|
|
559
751
|
|
|
752
|
+
// Build one <definitions>-root declaration (<message>/<error>/<signal>), attrs
|
|
753
|
+
// passed through verbatim.
|
|
754
|
+
function buildDefDecl(tag, attrs) {
|
|
755
|
+
return el(tag, restoreAttr(attrs), []);
|
|
756
|
+
}
|
|
757
|
+
|
|
560
758
|
// Build the full built_page string from { ns, model, geo }. The <bpmndi> block
|
|
561
759
|
// is regenerated solely from `geo` (structure.yaml geometry) — never a manifest.
|
|
760
|
+
// message/error/signal declarations and any unmodeled <definitions>-level
|
|
761
|
+
// children (model.extraDefs, kept as raw preserveOrder nodes) are re-emitted as
|
|
762
|
+
// siblings of <process>, matching the real corpus layout (process, then decls,
|
|
763
|
+
// then bpmndi).
|
|
562
764
|
function buildProcess({ ns, model, geo }) {
|
|
563
|
-
const diagramXml = buildDiagram(model, geo);
|
|
765
|
+
const diagramXml = buildDiagram(model, geo, ns);
|
|
564
766
|
const procAttrs = { '@_id': model.process.id };
|
|
565
767
|
if (model.process.name !== undefined) procAttrs['@_name'] = model.process.name;
|
|
566
768
|
if (model.process.isExecutable !== undefined)
|
|
@@ -570,6 +772,14 @@ function buildProcess({ ns, model, geo }) {
|
|
|
570
772
|
const procNode = el('process', procAttrs, buildFlowChildren(model, null));
|
|
571
773
|
const procXml = builder.build([procNode]).trim();
|
|
572
774
|
|
|
775
|
+
const declEls = [
|
|
776
|
+
...(model.messages || []).map((m) => buildDefDecl('message', m.attrs)),
|
|
777
|
+
...(model.errors || []).map((e) => buildDefDecl('error', e.attrs)),
|
|
778
|
+
...(model.signals || []).map((s) => buildDefDecl('signal', s.attrs)),
|
|
779
|
+
];
|
|
780
|
+
const declXml = declEls.length ? builder.build(declEls).trim() : '';
|
|
781
|
+
const extraXml = (model.extraDefs || []).length ? builder.build(model.extraDefs).trim() : '';
|
|
782
|
+
|
|
573
783
|
const defAttrs = Object.entries(ns)
|
|
574
784
|
.map(([k, v]) => `${k.replace(/^@_/, '')}="${v}"`)
|
|
575
785
|
.join(' ');
|
|
@@ -577,6 +787,8 @@ function buildProcess({ ns, model, geo }) {
|
|
|
577
787
|
// Whitespace between elements is insignificant in XML. The <process> is
|
|
578
788
|
// compact (format:false) to keep CDATA exact; the diagram is regenerated.
|
|
579
789
|
const parts = ['<?xml version="1.0" encoding="UTF-8"?>', `<definitions ${defAttrs}>`, procXml];
|
|
790
|
+
if (declXml) parts.push(declXml);
|
|
791
|
+
if (extraXml) parts.push(extraXml);
|
|
580
792
|
if (diagramXml) parts.push(diagramXml);
|
|
581
793
|
parts.push('</definitions>');
|
|
582
794
|
return parts.join('\n');
|
|
@@ -603,7 +815,16 @@ function canon(node) {
|
|
|
603
815
|
const attrs = {};
|
|
604
816
|
for (const [k, v] of Object.entries(attrsOf(node))) attrs[k] = v;
|
|
605
817
|
const rawKids = Array.isArray(node[tag]) ? node[tag] : [];
|
|
606
|
-
|
|
818
|
+
// <incoming>/<outgoing> are a redundant echo of sequenceFlow sourceRef/
|
|
819
|
+
// targetRef (already compared via the sequenceFlow nodes themselves) —
|
|
820
|
+
// never consulted by the execution engine, and inconsistently omitted by
|
|
821
|
+
// some exporters/hand-edits in the real corpus. We always regenerate a
|
|
822
|
+
// complete, correct set from the edge list on rebuild, so comparing their
|
|
823
|
+
// raw presence/count here would flag a behaviorally meaningless "loss".
|
|
824
|
+
const kids = rawKids
|
|
825
|
+
.filter((k) => tagOf(k) !== 'incoming' && tagOf(k) !== 'outgoing')
|
|
826
|
+
.map(canon)
|
|
827
|
+
.filter((x) => x !== null);
|
|
607
828
|
kids.sort((a, b) => keyOf(a).localeCompare(keyOf(b)));
|
|
608
829
|
return { tag, attrs, kids };
|
|
609
830
|
}
|
|
@@ -618,11 +839,24 @@ function keyOf(c) {
|
|
|
618
839
|
return `${c.tag}|${id}|${childKey}`;
|
|
619
840
|
}
|
|
620
841
|
|
|
842
|
+
// Canonicalize both the <process> subtree AND every other <definitions>-level
|
|
843
|
+
// declaration (message/error/signal/category/...) except <process> itself and
|
|
844
|
+
// <bpmndi:BPMNDiagram> (compared separately, by geometry, in compareDiagram).
|
|
845
|
+
// Declarations reference each other by id (messageRef/errorRef/signalRef), so
|
|
846
|
+
// they must be part of the semantic 0-loss gate, not just the flow graph.
|
|
621
847
|
function normalizeProcessTree(builtPage) {
|
|
622
848
|
const tree = parser.parse(builtPage);
|
|
623
849
|
const defNode = tree.find((n) => tagOf(n) === 'definitions');
|
|
624
850
|
const procNode = defNode.definitions.find((n) => tagOf(n) === 'process');
|
|
625
|
-
|
|
851
|
+
const decls = defNode.definitions
|
|
852
|
+
.filter((n) => {
|
|
853
|
+
const t = tagOf(n);
|
|
854
|
+
return t !== 'process' && t !== 'bpmndi:BPMNDiagram';
|
|
855
|
+
})
|
|
856
|
+
.map(canon)
|
|
857
|
+
.filter((x) => x !== null)
|
|
858
|
+
.sort((a, b) => keyOf(a).localeCompare(keyOf(b)));
|
|
859
|
+
return { process: canon(procNode), decls };
|
|
626
860
|
}
|
|
627
861
|
|
|
628
862
|
// Deep structural comparison; returns first diff path or null if equal.
|
|
@@ -91,9 +91,11 @@ function toSvg(structure) {
|
|
|
91
91
|
`<polyline points="${pts}" fill="none" stroke="#444" stroke-width="1.5" marker-end="url(#arrow)"/>`
|
|
92
92
|
);
|
|
93
93
|
if (e.name) {
|
|
94
|
-
const
|
|
94
|
+
const lp = e.labelPos;
|
|
95
|
+
const lx = lp ? lp.x + lp.width / 2 : e.waypoints[Math.floor(e.waypoints.length / 2)][0];
|
|
96
|
+
const ly = lp ? lp.y + lp.height / 2 : e.waypoints[Math.floor(e.waypoints.length / 2)][1] - 4;
|
|
95
97
|
out.push(
|
|
96
|
-
`<text x="${
|
|
98
|
+
`<text x="${lx + ox}" y="${ly + oy}" text-anchor="middle" fill="#666" font-size="9">${esc(e.name)}</text>`
|
|
97
99
|
);
|
|
98
100
|
}
|
|
99
101
|
}
|