@waron97/prbot 3.2.1 → 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 +141 -68
- 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/initPhase.js +44 -34
- package/src/agrippa/commands/pb.js +46 -13
- package/src/agrippa/commands/pull.js +79 -3
- package/src/agrippa/commands/pullLrp.js +55 -0
- package/src/agrippa/commands/push.js +81 -11
- package/src/agrippa/commands/pushLrp.js +56 -0
- package/src/agrippa/index.js +28 -19
- package/src/agrippa/lib/lrpApi.js +153 -0
- package/src/agrippa/lib/pbEdit.js +31 -10
- package/src/agrippa/lib/pbModel.js +317 -86
- package/src/agrippa/lib/pbProject.js +22 -4
- 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({
|
|
@@ -357,8 +529,9 @@ function boundsAttrs(b) {
|
|
|
357
529
|
// labels are omitted (renderers auto-place them); edge labels use ELK-computed
|
|
358
530
|
// bounds from geo.labelPos when present — only positions, sizes, waypoints and
|
|
359
531
|
// subprocess expand-state are authoritative — see compareDiagram.
|
|
360
|
-
function buildDiagram(model, geo) {
|
|
532
|
+
function buildDiagram(model, geo, ns) {
|
|
361
533
|
if (!geo) return null;
|
|
534
|
+
const wpTag = diWaypointTag(ns);
|
|
362
535
|
const shapeEls = [];
|
|
363
536
|
const pushShape = (id) => {
|
|
364
537
|
const b = geo.bounds[id];
|
|
@@ -369,6 +542,7 @@ function buildDiagram(model, geo) {
|
|
|
369
542
|
};
|
|
370
543
|
for (const n of model.nodes) pushShape(n.id);
|
|
371
544
|
for (const a of model.annotations || []) pushShape(a.id);
|
|
545
|
+
for (const g of model.groups || []) pushShape(g.id);
|
|
372
546
|
|
|
373
547
|
const edgeEls = [];
|
|
374
548
|
const pushEdge = (id) => {
|
|
@@ -377,8 +551,9 @@ function buildDiagram(model, geo) {
|
|
|
377
551
|
const children = [];
|
|
378
552
|
const lp = geo.labelPos?.[id];
|
|
379
553
|
for (const [x, y] of wps)
|
|
380
|
-
children.push(el(
|
|
381
|
-
if (lp)
|
|
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), [])]));
|
|
382
557
|
edgeEls.push(el('bpmndi:BPMNEdge', { '@_id': `${id}_di`, '@_bpmnElement': id }, children));
|
|
383
558
|
};
|
|
384
559
|
for (const e of model.edges) pushEdge(e.id);
|
|
@@ -402,18 +577,8 @@ function buildFlowChildren(model, parentId) {
|
|
|
402
577
|
if (parentId === null && model.process.documentation != null) {
|
|
403
578
|
children.push(el('documentation', {}, [textChild(model.process.documentation)]));
|
|
404
579
|
}
|
|
405
|
-
//
|
|
406
|
-
|
|
407
|
-
for (const e of model.errors) {
|
|
408
|
-
children.push(el('error', { '@_id': e.id, '@_name': e.name }, []));
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
const restoreAttr = (extra) => {
|
|
413
|
-
const out = {};
|
|
414
|
-
for (const [k, v] of Object.entries(extra || {})) out[`@_${k}`] = v;
|
|
415
|
-
return out;
|
|
416
|
-
};
|
|
580
|
+
// message/error/signal declarations are <definitions>-root siblings of
|
|
581
|
+
// <process>, not process children — built separately in buildProcess().
|
|
417
582
|
|
|
418
583
|
const incoming = (id) => model.edges.filter((e) => e.target === id).map((e) => e.id);
|
|
419
584
|
const outgoing = (id) => model.edges.filter((e) => e.source === id).map((e) => e.id);
|
|
@@ -446,16 +611,15 @@ function buildFlowChildren(model, parentId) {
|
|
|
446
611
|
if (n.class !== undefined) attrs['@_activiti:class'] = n.class;
|
|
447
612
|
Object.assign(attrs, restoreAttr(n.attrs));
|
|
448
613
|
const kids = [];
|
|
449
|
-
if (n.fields
|
|
614
|
+
if (n.fields !== undefined) {
|
|
450
615
|
const fieldEls = n.fields.map((f) => {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
} else if (Object.prototype.hasOwnProperty.call(f, 'expression')) {
|
|
455
|
-
inner = [el('activiti:expression', {}, [cdataChild(f.expression)])];
|
|
456
|
-
}
|
|
616
|
+
const inner = (f.parts || []).map((p) =>
|
|
617
|
+
el(`activiti:${p.kind}`, {}, [cdataChild(p.value)])
|
|
618
|
+
);
|
|
457
619
|
return el('activiti:field', { '@_name': f.name }, inner);
|
|
458
620
|
});
|
|
621
|
+
// Emit the wrapper even with zero fields — some sources carry a
|
|
622
|
+
// bare <extensionElements/> (see parseServiceFields).
|
|
459
623
|
kids.push(el('extensionElements', {}, fieldEls));
|
|
460
624
|
}
|
|
461
625
|
kids.push(...baseKids(n));
|
|
@@ -469,22 +633,35 @@ function buildFlowChildren(model, parentId) {
|
|
|
469
633
|
children.push(el('exclusiveGateway', attrs, baseKids(n)));
|
|
470
634
|
} else if (n.type === 'startEvent') {
|
|
471
635
|
Object.assign(attrs, restoreAttr(n.attrs));
|
|
472
|
-
|
|
636
|
+
const kids = baseKids(n);
|
|
637
|
+
kids.push(...buildEventDefinitions(n.eventDefinitions));
|
|
638
|
+
children.push(el('startEvent', attrs, kids));
|
|
473
639
|
} else if (n.type === 'endEvent') {
|
|
474
640
|
Object.assign(attrs, restoreAttr(n.attrs));
|
|
475
641
|
const kids = baseKids(n);
|
|
476
|
-
|
|
477
|
-
kids.push(el('errorEventDefinition', restoreAttr(n.errorEventDefinition), []));
|
|
478
|
-
}
|
|
642
|
+
kids.push(...buildEventDefinitions(n.eventDefinitions));
|
|
479
643
|
children.push(el('endEvent', attrs, kids));
|
|
480
644
|
} else if (n.type === 'boundaryEvent') {
|
|
481
645
|
if (n.attachedToRef !== undefined) attrs['@_attachedToRef'] = n.attachedToRef;
|
|
482
646
|
Object.assign(attrs, restoreAttr(n.attrs));
|
|
483
647
|
const kids = baseKids(n);
|
|
484
|
-
|
|
485
|
-
kids.push(el('errorEventDefinition', restoreAttr(n.errorEventDefinition), []));
|
|
486
|
-
}
|
|
648
|
+
kids.push(...buildEventDefinitions(n.eventDefinitions));
|
|
487
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)));
|
|
488
665
|
} else if (CONTAINER_TAGS.has(n.type)) {
|
|
489
666
|
Object.assign(attrs, restoreAttr(n.attrs));
|
|
490
667
|
const kids = baseKids(n);
|
|
@@ -525,45 +702,67 @@ function buildFlowChildren(model, parentId) {
|
|
|
525
702
|
const kids = [];
|
|
526
703
|
if (e.documentation != null)
|
|
527
704
|
kids.push(el('documentation', {}, [textChild(e.documentation)]));
|
|
528
|
-
if (e.condition != null) {
|
|
529
|
-
const cAttrs =
|
|
530
|
-
|
|
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));
|
|
531
712
|
}
|
|
532
713
|
children.push(el('sequenceFlow', attrs, kids));
|
|
533
714
|
}
|
|
534
715
|
|
|
535
|
-
// annotations + associations (
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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
|
+
);
|
|
558
747
|
}
|
|
559
748
|
|
|
560
749
|
return children;
|
|
561
750
|
}
|
|
562
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
|
+
|
|
563
758
|
// Build the full built_page string from { ns, model, geo }. The <bpmndi> block
|
|
564
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).
|
|
565
764
|
function buildProcess({ ns, model, geo }) {
|
|
566
|
-
const diagramXml = buildDiagram(model, geo);
|
|
765
|
+
const diagramXml = buildDiagram(model, geo, ns);
|
|
567
766
|
const procAttrs = { '@_id': model.process.id };
|
|
568
767
|
if (model.process.name !== undefined) procAttrs['@_name'] = model.process.name;
|
|
569
768
|
if (model.process.isExecutable !== undefined)
|
|
@@ -573,6 +772,14 @@ function buildProcess({ ns, model, geo }) {
|
|
|
573
772
|
const procNode = el('process', procAttrs, buildFlowChildren(model, null));
|
|
574
773
|
const procXml = builder.build([procNode]).trim();
|
|
575
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
|
+
|
|
576
783
|
const defAttrs = Object.entries(ns)
|
|
577
784
|
.map(([k, v]) => `${k.replace(/^@_/, '')}="${v}"`)
|
|
578
785
|
.join(' ');
|
|
@@ -580,6 +787,8 @@ function buildProcess({ ns, model, geo }) {
|
|
|
580
787
|
// Whitespace between elements is insignificant in XML. The <process> is
|
|
581
788
|
// compact (format:false) to keep CDATA exact; the diagram is regenerated.
|
|
582
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);
|
|
583
792
|
if (diagramXml) parts.push(diagramXml);
|
|
584
793
|
parts.push('</definitions>');
|
|
585
794
|
return parts.join('\n');
|
|
@@ -606,7 +815,16 @@ function canon(node) {
|
|
|
606
815
|
const attrs = {};
|
|
607
816
|
for (const [k, v] of Object.entries(attrsOf(node))) attrs[k] = v;
|
|
608
817
|
const rawKids = Array.isArray(node[tag]) ? node[tag] : [];
|
|
609
|
-
|
|
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);
|
|
610
828
|
kids.sort((a, b) => keyOf(a).localeCompare(keyOf(b)));
|
|
611
829
|
return { tag, attrs, kids };
|
|
612
830
|
}
|
|
@@ -621,11 +839,24 @@ function keyOf(c) {
|
|
|
621
839
|
return `${c.tag}|${id}|${childKey}`;
|
|
622
840
|
}
|
|
623
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.
|
|
624
847
|
function normalizeProcessTree(builtPage) {
|
|
625
848
|
const tree = parser.parse(builtPage);
|
|
626
849
|
const defNode = tree.find((n) => tagOf(n) === 'definitions');
|
|
627
850
|
const procNode = defNode.definitions.find((n) => tagOf(n) === 'process');
|
|
628
|
-
|
|
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 };
|
|
629
860
|
}
|
|
630
861
|
|
|
631
862
|
// Deep structural comparison; returns first diff path or null if equal.
|