@bpmnkit/core 0.1.2 → 0.2.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.
Files changed (42) hide show
  1. package/README.md +30 -1
  2. package/dist/bpmn/bpmn-builder.d.ts +209 -3
  3. package/dist/bpmn/bpmn-builder.js +456 -16
  4. package/dist/bpmn/bpmn-model.d.ts +110 -0
  5. package/dist/bpmn/bpmn-parser.js +1413 -528
  6. package/dist/bpmn/bpmn-serializer.js +101 -19
  7. package/dist/bpmn/compact.d.ts +17 -2
  8. package/dist/bpmn/compact.js +3 -3
  9. package/dist/bpmn/full-operations.d.ts +89 -0
  10. package/dist/bpmn/full-operations.js +478 -0
  11. package/dist/bpmn/index.d.ts +19 -0
  12. package/dist/bpmn/index.js +21 -0
  13. package/dist/bpmn/optimize/feel.js +2 -2
  14. package/dist/bpmn/optimize/patterns.js +23 -16
  15. package/dist/bpmn/optimize/tasks.js +30 -7
  16. package/dist/bpmn/optimize/utils.js +2 -4
  17. package/dist/bpmn/optimize/variable-flow.js +58 -67
  18. package/dist/bpmn/semantic-hash.d.ts +93 -0
  19. package/dist/bpmn/semantic-hash.js +155 -0
  20. package/dist/bpmn/sha256.d.ts +17 -0
  21. package/dist/bpmn/sha256.js +95 -0
  22. package/dist/bpmn/zeebe-extensions.d.ts +56 -0
  23. package/dist/bpmn/zeebe-extensions.js +79 -0
  24. package/dist/bpmn/zeebe-placement.d.ts +12 -0
  25. package/dist/bpmn/zeebe-placement.js +140 -0
  26. package/dist/errors.d.ts +40 -1
  27. package/dist/errors.js +41 -0
  28. package/dist/index.d.ts +10 -4
  29. package/dist/index.js +7 -3
  30. package/dist/layout/semantic/graph.d.ts +9 -1
  31. package/dist/layout/semantic/graph.js +42 -17
  32. package/dist/layout/semantic/route.js +102 -42
  33. package/dist/node/index.d.ts +10 -0
  34. package/dist/node/index.js +9 -0
  35. package/dist/node/write.d.ts +81 -0
  36. package/dist/node/write.js +167 -0
  37. package/dist/types/id-generator.js +11 -3
  38. package/dist/xml/index.d.ts +3 -1
  39. package/dist/xml/index.js +2 -1
  40. package/dist/xml/xml-parser.d.ts +32 -0
  41. package/dist/xml/xml-parser.js +394 -143
  42. package/package.json +8 -1
@@ -1,40 +1,34 @@
1
1
  import { ParseError } from "../errors.js";
2
- import { parseXml } from "../xml/xml-parser.js";
3
- // ---------------------------------------------------------------------------
4
- // Helpers
5
- // ---------------------------------------------------------------------------
2
+ import { Visit, scanXml } from "../xml/xml-parser.js";
6
3
  function localName(name) {
7
4
  const idx = name.indexOf(":");
8
5
  return idx >= 0 ? name.slice(idx + 1) : name;
9
6
  }
10
- function findChildren(element, tagLocalName) {
11
- return element.children.filter((c) => localName(c.name) === tagLocalName);
12
- }
13
- function findChild(element, tagLocalName) {
14
- return element.children.find((c) => localName(c.name) === tagLocalName);
7
+ /** True when `qname` is `local` or `<prefix>:<local>` — without slicing a new string. */
8
+ function hasLocalName(qname, local) {
9
+ const idx = qname.indexOf(":");
10
+ if (idx < 0)
11
+ return qname === local;
12
+ return qname.length - idx - 1 === local.length && qname.endsWith(local);
15
13
  }
16
- function attr(element, name) {
17
- if (element.attributes[name] !== undefined)
18
- return element.attributes[name];
19
- for (const [key, value] of Object.entries(element.attributes)) {
20
- if (localName(key) === name)
21
- return value;
14
+ function attr(attributes, name) {
15
+ const direct = attributes[name];
16
+ if (direct !== undefined)
17
+ return direct;
18
+ // Fall back to a namespace-qualified spelling of the same attribute.
19
+ for (const key in attributes) {
20
+ if (key.length > name.length && hasLocalName(key, name))
21
+ return attributes[key];
22
22
  }
23
23
  return undefined;
24
24
  }
25
- function requiredAttr(element, name) {
26
- const value = attr(element, name);
25
+ function requiredAttr(attributes, name, elementName) {
26
+ const value = attr(attributes, name);
27
27
  if (value === undefined) {
28
- throw new ParseError(`Missing required attribute "${name}" on <${element.name}>`);
28
+ throw new ParseError(`Missing required attribute "${name}" on <${elementName}>`);
29
29
  }
30
30
  return value;
31
31
  }
32
- /** Collect text from child <bpmn:incoming> / <bpmn:outgoing> elements. */
33
- function collectFlowRefs(element, tag) {
34
- return findChildren(element, tag)
35
- .map((c) => c.text?.trim())
36
- .filter((t) => !!t);
37
- }
38
32
  /** Known attribute names on flow nodes — everything else goes to unknownAttributes. */
39
33
  const KNOWN_ATTRS = new Set([
40
34
  "id",
@@ -59,22 +53,23 @@ const KNOWN_ATTRS = new Set([
59
53
  "exporter",
60
54
  "exporterVersion",
61
55
  "processRef",
56
+ "messageRef",
62
57
  "dataObjectRef",
63
58
  "dataStoreRef",
64
59
  "isCollection",
65
60
  "categoryValueRef",
66
61
  ]);
67
62
  /** Extract unknown (namespace-qualified) attributes from an element. */
68
- function unknownAttrs(element) {
63
+ function unknownAttrs(attributes) {
69
64
  const result = {};
70
- for (const [key, value] of Object.entries(element.attributes)) {
71
- if (key.startsWith("xmlns:") || key === "xmlns")
72
- continue;
65
+ for (const key in attributes) {
73
66
  if (KNOWN_ATTRS.has(key))
74
67
  continue;
75
- if (KNOWN_ATTRS.has(localName(key)))
68
+ if (key.startsWith("xmlns:") || key === "xmlns")
69
+ continue;
70
+ if (key.includes(":") && KNOWN_ATTRS.has(localName(key)))
76
71
  continue;
77
- result[key] = value;
72
+ result[key] = attributes[key];
78
73
  }
79
74
  return result;
80
75
  }
@@ -129,114 +124,298 @@ const KNOWN_PROCESS_CHILDREN = new Set([
129
124
  "extensionElements",
130
125
  ]);
131
126
  // ---------------------------------------------------------------------------
132
- // Extension elements
127
+ // Streaming model builder
128
+ //
129
+ // The document is scanned once. Each known element gets a small frame that
130
+ // keeps only what the model needs and builds the model object at the end tag,
131
+ // in the same field order the former tree walk produced. Unknown children are
132
+ // skipped by the scanner without any allocation; only the content of
133
+ // <extensionElements> is still materialised as XmlElement trees, because the
134
+ // model keeps it verbatim.
133
135
  // ---------------------------------------------------------------------------
134
- function parseExtensionElements(element) {
135
- const ext = findChild(element, "extensionElements");
136
- return ext ? ext.children : [];
136
+ /** One element's handler while the scanner is inside it. */
137
+ class Frame {
138
+ /** Whether this frame reads character data; the scanner skips it otherwise. */
139
+ wantsText = false;
140
+ /** A child start tag: the frame for it, or null to skip its whole subtree. */
141
+ child(_local, _name, _attrs) {
142
+ return null;
143
+ }
144
+ /** Character data directly inside this element. */
145
+ text(_text) { }
137
146
  }
138
- // ---------------------------------------------------------------------------
139
- // Event definitions
140
- // ---------------------------------------------------------------------------
141
- function parseEventDefinitions(element) {
142
- const defs = [];
143
- for (const child of element.children) {
144
- const ln = localName(child.name);
145
- if (ln === "timerEventDefinition") {
146
- const durationEl = findChild(child, "timeDuration");
147
- const dateEl = findChild(child, "timeDate");
148
- const cycleEl = findChild(child, "timeCycle");
149
- defs.push({
150
- type: "timer",
151
- id: attr(child, "id"),
152
- timeDuration: durationEl?.text?.trim(),
153
- timeDurationAttributes: durationEl
154
- ? Object.keys(durationEl.attributes).length > 0
155
- ? { ...durationEl.attributes }
156
- : undefined
157
- : undefined,
158
- timeDate: dateEl?.text?.trim(),
159
- timeDateAttributes: dateEl
160
- ? Object.keys(dateEl.attributes).length > 0
161
- ? { ...dateEl.attributes }
162
- : undefined
163
- : undefined,
164
- timeCycle: cycleEl?.text?.trim(),
165
- timeCycleAttributes: cycleEl
166
- ? Object.keys(cycleEl.attributes).length > 0
167
- ? { ...cycleEl.attributes }
168
- : undefined
169
- : undefined,
170
- });
171
- }
172
- else if (ln === "errorEventDefinition") {
173
- defs.push({
174
- type: "error",
175
- id: attr(child, "id"),
176
- errorRef: attr(child, "errorRef"),
177
- });
178
- }
179
- else if (ln === "escalationEventDefinition") {
180
- defs.push({
181
- type: "escalation",
182
- id: attr(child, "id"),
183
- escalationRef: attr(child, "escalationRef"),
184
- });
185
- }
186
- else if (ln === "messageEventDefinition") {
187
- defs.push({
188
- type: "message",
189
- id: attr(child, "id"),
190
- messageRef: attr(child, "messageRef"),
191
- });
192
- }
193
- else if (ln === "signalEventDefinition") {
194
- defs.push({
195
- type: "signal",
196
- id: attr(child, "id"),
197
- signalRef: attr(child, "signalRef"),
198
- });
199
- }
200
- else if (ln === "conditionalEventDefinition") {
201
- const condEl = findChild(child, "condition");
202
- defs.push({
203
- type: "conditional",
204
- id: attr(child, "id"),
205
- condition: condEl?.text?.trim(),
206
- });
207
- }
208
- else if (ln === "linkEventDefinition") {
209
- defs.push({
210
- type: "link",
211
- id: attr(child, "id"),
212
- name: attr(child, "name"),
213
- });
214
- }
215
- else if (ln === "cancelEventDefinition") {
216
- defs.push({ type: "cancel", id: attr(child, "id") });
147
+ // Which text-bearing child a TextFrame reports back to its owner.
148
+ const SLOT_INCOMING = 0;
149
+ const SLOT_OUTGOING = 1;
150
+ const SLOT_DOCUMENTATION = 2;
151
+ const SLOT_FLOW_NODE_REF = 3;
152
+ const SLOT_TEXT = 4;
153
+ const SLOT_CONDITION_EXPRESSION = 5;
154
+ const SLOT_COMPLETION_CONDITION = 6;
155
+ const SLOT_TIME_DURATION = 7;
156
+ const SLOT_TIME_DATE = 8;
157
+ const SLOT_TIME_CYCLE = 9;
158
+ const SLOT_CONDITION = 10;
159
+ const SLOT_LOOP_CARDINALITY = 13;
160
+ /** Collects the direct character data of a leaf element; its children are skipped. */
161
+ class TextFrame extends Frame {
162
+ owner;
163
+ slot;
164
+ attrs;
165
+ wantsText = true;
166
+ value;
167
+ constructor(owner, slot, attrs) {
168
+ super();
169
+ this.owner = owner;
170
+ this.slot = slot;
171
+ this.attrs = attrs;
172
+ }
173
+ text(text) {
174
+ this.value = this.value === undefined ? text : this.value + text;
175
+ }
176
+ finish() {
177
+ this.owner.setText(this.slot, this.value, this.attrs);
178
+ }
179
+ }
180
+ /** Materialises the children of an <extensionElements> as XmlElement trees. */
181
+ class TreeFrame extends Frame {
182
+ target;
183
+ owner;
184
+ wantsText = true;
185
+ stack = [];
186
+ /**
187
+ * @param target - Where top-level children are appended.
188
+ * @param owner - Element receiving character data directly inside it, when
189
+ * the frame stands for an element rather than a bare child list.
190
+ */
191
+ constructor(target, owner) {
192
+ super();
193
+ this.target = target;
194
+ this.owner = owner;
195
+ }
196
+ child(_local, name, attrs) {
197
+ const el = { name, attributes: attrs, children: [] };
198
+ const parent = this.stack[this.stack.length - 1];
199
+ if (parent)
200
+ parent.children.push(el);
201
+ else
202
+ this.target.push(el);
203
+ this.stack.push(el);
204
+ return this;
205
+ }
206
+ text(text) {
207
+ const el = this.stack[this.stack.length - 1] ?? this.owner;
208
+ if (el)
209
+ el.text = el.text === undefined ? text : el.text + text;
210
+ }
211
+ finish() {
212
+ dropLayoutWhitespace(this.stack.pop() ?? this.owner);
213
+ }
214
+ }
215
+ /**
216
+ * Clears an element's text when it is only the indentation between its child
217
+ * elements. Without this, re-serialising a nested extension or captured subtree
218
+ * re-emits that indentation as content and it grows on every round trip.
219
+ */
220
+ function dropLayoutWhitespace(element) {
221
+ if (element === undefined)
222
+ return;
223
+ if (element.children.length > 0 && element.text !== undefined && element.text.trim() === "") {
224
+ element.text = undefined;
225
+ }
226
+ }
227
+ /**
228
+ * Shared handling of the two children every BPMN base element may have.
229
+ *
230
+ * Subclasses call {@link baseChild} first and fall through to their own cases,
231
+ * and read {@link baseFields} when building their model object. Frames that
232
+ * also collect text children override {@link baseText}.
233
+ */
234
+ class BaseElementFrame extends Frame {
235
+ documentation;
236
+ documentationSeen = false;
237
+ extensions = null;
238
+ /** A base child's frame, or `undefined` when `local` is not one. */
239
+ baseChild(local, attrs) {
240
+ switch (local) {
241
+ case "documentation":
242
+ if (this.documentationSeen)
243
+ return null;
244
+ this.documentationSeen = true;
245
+ return new TextFrame(this, SLOT_DOCUMENTATION, attrs);
246
+ case "extensionElements":
247
+ if (this.extensions !== null)
248
+ return null;
249
+ this.extensions = [];
250
+ return new TreeFrame(this.extensions);
251
+ default:
252
+ return undefined;
217
253
  }
218
- else if (ln === "terminateEventDefinition") {
219
- defs.push({ type: "terminate", id: attr(child, "id") });
254
+ }
255
+ /** Only defined keys, so model objects keep the shape they had before. */
256
+ baseFields() {
257
+ const fields = {};
258
+ if (this.documentation !== undefined)
259
+ fields.documentation = this.documentation;
260
+ if (this.extensions !== null && this.extensions.length > 0) {
261
+ fields.extensionElements = this.extensions;
220
262
  }
221
- else if (ln === "compensateEventDefinition") {
222
- defs.push({
223
- type: "compensate",
224
- id: attr(child, "id"),
225
- activityRef: attr(child, "activityRef"),
226
- });
263
+ return fields;
264
+ }
265
+ setText(slot, text, attrs) {
266
+ if (slot === SLOT_DOCUMENTATION) {
267
+ this.documentation = text;
268
+ return;
227
269
  }
270
+ this.baseText(slot, text, attrs);
271
+ }
272
+ /** Text children other than `documentation`. */
273
+ baseText(_slot, _text, _attrs) { }
274
+ }
275
+ /**
276
+ * Captures an unrecognised child subtree verbatim so a round trip does not
277
+ * discard it.
278
+ *
279
+ * Only ever called from a frame's `default:` branch — the path that means "this
280
+ * element is not part of the model". Children a frame recognises but chooses
281
+ * not to store (a second `documentation`, a loop on a type that cannot loop)
282
+ * keep returning `null`, so nothing is captured twice.
283
+ */
284
+ function captureUnknown(target, name, attrs) {
285
+ const element = { name, attributes: attrs, children: [] };
286
+ target.push(element);
287
+ return new TreeFrame(element.children, element);
288
+ }
289
+ /**
290
+ * A frame for an element whose only children are `documentation` and
291
+ * `extensionElements`; everything else it needs comes from its attributes.
292
+ */
293
+ class BaseOnlyFrame extends BaseElementFrame {
294
+ attrs;
295
+ build;
296
+ target;
297
+ constructor(attrs, build, target) {
298
+ super();
299
+ this.attrs = attrs;
300
+ this.build = build;
301
+ this.target = target;
302
+ }
303
+ child(local, _name, attrs) {
304
+ return this.baseChild(local, attrs) ?? null;
305
+ }
306
+ finish() {
307
+ this.target.push(this.build(this.attrs, this.baseFields()));
228
308
  }
229
- return defs;
230
309
  }
231
310
  // ---------------------------------------------------------------------------
232
- // Multi-instance loop
311
+ // Attribute-only elements
233
312
  // ---------------------------------------------------------------------------
234
- function parseLoopCharacteristics(element) {
235
- const loopEl = findChild(element, "multiInstanceLoopCharacteristics");
236
- if (!loopEl)
237
- return undefined;
238
- const isSequential = loopEl.attributes.isSequential === "true" ? true : undefined;
239
- return { isSequential, extensionElements: parseExtensionElements(loopEl) };
313
+ function parseAssociation(name, attrs, base = {}) {
314
+ return {
315
+ id: requiredAttr(attrs, "id", name),
316
+ sourceRef: requiredAttr(attrs, "sourceRef", name),
317
+ targetRef: requiredAttr(attrs, "targetRef", name),
318
+ associationDirection: attr(attrs, "associationDirection"),
319
+ ...base,
320
+ unknownAttributes: unknownAttrs(attrs),
321
+ };
322
+ }
323
+ function parseGroup(name, attrs, base = {}) {
324
+ return {
325
+ id: requiredAttr(attrs, "id", name),
326
+ categoryValueRef: attr(attrs, "categoryValueRef"),
327
+ ...base,
328
+ unknownAttributes: unknownAttrs(attrs),
329
+ };
330
+ }
331
+ function parseParticipant(name, attrs, base = {}) {
332
+ return {
333
+ id: requiredAttr(attrs, "id", name),
334
+ name: attr(attrs, "name"),
335
+ processRef: attr(attrs, "processRef"),
336
+ ...base,
337
+ unknownAttributes: unknownAttrs(attrs),
338
+ };
339
+ }
340
+ function parseMessageFlow(name, attrs, base = {}) {
341
+ return {
342
+ id: requiredAttr(attrs, "id", name),
343
+ name: attr(attrs, "name"),
344
+ sourceRef: requiredAttr(attrs, "sourceRef", name),
345
+ targetRef: requiredAttr(attrs, "targetRef", name),
346
+ messageRef: attr(attrs, "messageRef"),
347
+ ...base,
348
+ unknownAttributes: unknownAttrs(attrs),
349
+ };
350
+ }
351
+ function parseError(name, attrs, base = {}) {
352
+ return {
353
+ id: requiredAttr(attrs, "id", name),
354
+ name: attr(attrs, "name"),
355
+ errorCode: attr(attrs, "errorCode"),
356
+ ...base,
357
+ unknownAttributes: unknownAttrs(attrs),
358
+ };
359
+ }
360
+ function parseEscalation(name, attrs, base = {}) {
361
+ return {
362
+ id: requiredAttr(attrs, "id", name),
363
+ name: attr(attrs, "name"),
364
+ escalationCode: attr(attrs, "escalationCode"),
365
+ ...base,
366
+ unknownAttributes: unknownAttrs(attrs),
367
+ };
368
+ }
369
+ function parseMessage(name, attrs, base = {}) {
370
+ return {
371
+ id: requiredAttr(attrs, "id", name),
372
+ name: attr(attrs, "name"),
373
+ ...base,
374
+ unknownAttributes: unknownAttrs(attrs),
375
+ };
376
+ }
377
+ function parseSignal(name, attrs, base = {}) {
378
+ return {
379
+ id: requiredAttr(attrs, "id", name),
380
+ name: attr(attrs, "name"),
381
+ ...base,
382
+ unknownAttributes: unknownAttrs(attrs),
383
+ };
384
+ }
385
+ function parseBounds(attrs) {
386
+ return {
387
+ x: Number(attr(attrs, "x") ?? "0"),
388
+ y: Number(attr(attrs, "y") ?? "0"),
389
+ width: Number(attr(attrs, "width") ?? "0"),
390
+ height: Number(attr(attrs, "height") ?? "0"),
391
+ };
392
+ }
393
+ function newContents() {
394
+ return { flowElements: [], sequenceFlows: [], textAnnotations: [], associations: [], groups: [] };
395
+ }
396
+ /**
397
+ * A frame for a child of a flow-element container.
398
+ *
399
+ * Returns `null` when the child is recognised but needs no frame, and
400
+ * `undefined` when it is not part of the model — the caller keeps the latter
401
+ * verbatim, so the two cases must stay distinct.
402
+ */
403
+ function contentsChild(contents, local, name, attrs) {
404
+ if (FLOW_ELEMENT_TYPES.has(local)) {
405
+ return new FlowNodeFrame(local, name, attrs, contents);
406
+ }
407
+ switch (local) {
408
+ case "sequenceFlow":
409
+ return new SequenceFlowFrame(name, attrs, contents);
410
+ case "textAnnotation":
411
+ return new TextAnnotationFrame(name, attrs, contents.textAnnotations);
412
+ case "association":
413
+ return new BaseOnlyFrame(attrs, (a, base) => parseAssociation(name, a, base), contents.associations);
414
+ case "group":
415
+ return new BaseOnlyFrame(attrs, (a, base) => parseGroup(name, a, base), contents.groups);
416
+ default:
417
+ return undefined;
418
+ }
240
419
  }
241
420
  // ---------------------------------------------------------------------------
242
421
  // Flow elements
@@ -269,452 +448,1158 @@ const FLOW_ELEMENT_TYPES = new Set([
269
448
  "dataObjectReference",
270
449
  "dataStoreReference",
271
450
  ]);
272
- function parseFlowElement(element) {
273
- const ln = localName(element.name);
274
- if (!FLOW_ELEMENT_TYPES.has(ln))
275
- return undefined;
276
- const base = {
277
- id: requiredAttr(element, "id"),
278
- name: attr(element, "name"),
279
- incoming: collectFlowRefs(element, "incoming"),
280
- outgoing: collectFlowRefs(element, "outgoing"),
281
- documentation: findChild(element, "documentation")?.text,
282
- extensionElements: parseExtensionElements(element),
283
- unknownAttributes: unknownAttrs(element),
284
- };
285
- switch (ln) {
286
- case "startEvent": {
287
- const isInterruptingAttr = attr(element, "isInterrupting");
288
- return {
289
- ...base,
290
- type: "startEvent",
291
- eventDefinitions: parseEventDefinitions(element),
292
- ...(isInterruptingAttr === "false" ? { isInterrupting: false } : {}),
293
- };
451
+ const EVENT_TYPES = new Set([
452
+ "startEvent",
453
+ "endEvent",
454
+ "intermediateCatchEvent",
455
+ "intermediateThrowEvent",
456
+ "boundaryEvent",
457
+ ]);
458
+ /** Types whose <multiInstanceLoopCharacteristics> child is read. */
459
+ const LOOP_TYPES = new Set([
460
+ "task",
461
+ "serviceTask",
462
+ "scriptTask",
463
+ "userTask",
464
+ "businessRuleTask",
465
+ "manualTask",
466
+ "callActivity",
467
+ "sendTask",
468
+ "receiveTask",
469
+ "adHocSubProcess",
470
+ "subProcess",
471
+ "transaction",
472
+ ]);
473
+ const CONTAINER_TYPES = new Set([
474
+ "adHocSubProcess",
475
+ "subProcess",
476
+ "eventSubProcess",
477
+ "transaction",
478
+ ]);
479
+ const EVENT_DEFINITION_TYPES = new Set([
480
+ "timerEventDefinition",
481
+ "errorEventDefinition",
482
+ "escalationEventDefinition",
483
+ "messageEventDefinition",
484
+ "signalEventDefinition",
485
+ "conditionalEventDefinition",
486
+ "linkEventDefinition",
487
+ "cancelEventDefinition",
488
+ "terminateEventDefinition",
489
+ "compensateEventDefinition",
490
+ ]);
491
+ class FlowNodeFrame extends Frame {
492
+ type;
493
+ name;
494
+ attrs;
495
+ target;
496
+ id;
497
+ incoming = [];
498
+ outgoing = [];
499
+ documentation;
500
+ documentationSeen = false;
501
+ extensionElements = null;
502
+ eventDefinitions;
503
+ loopCharacteristics;
504
+ loopSeen = false;
505
+ completionCondition;
506
+ completionSeen = false;
507
+ unknownChildren = [];
508
+ properties = [];
509
+ dataInputAssociations = [];
510
+ dataOutputAssociations = [];
511
+ contents;
512
+ constructor(type, name, attrs, target) {
513
+ super();
514
+ this.type = type;
515
+ this.name = name;
516
+ this.attrs = attrs;
517
+ this.target = target;
518
+ this.id = requiredAttr(attrs, "id", name);
519
+ this.eventDefinitions = EVENT_TYPES.has(type) ? [] : null;
520
+ this.contents = CONTAINER_TYPES.has(type) ? newContents() : null;
521
+ }
522
+ child(local, name, attrs) {
523
+ switch (local) {
524
+ case "incoming":
525
+ return new TextFrame(this, SLOT_INCOMING, attrs);
526
+ case "outgoing":
527
+ return new TextFrame(this, SLOT_OUTGOING, attrs);
528
+ case "documentation":
529
+ if (this.documentationSeen)
530
+ return null;
531
+ this.documentationSeen = true;
532
+ return new TextFrame(this, SLOT_DOCUMENTATION, attrs);
533
+ case "extensionElements":
534
+ if (this.extensionElements !== null)
535
+ return null;
536
+ this.extensionElements = [];
537
+ return new TreeFrame(this.extensionElements);
538
+ case "multiInstanceLoopCharacteristics":
539
+ if (!LOOP_TYPES.has(this.type) || this.loopSeen)
540
+ return null;
541
+ this.loopSeen = true;
542
+ return new LoopFrame(attrs, this);
543
+ case "completionCondition":
544
+ if (this.type !== "adHocSubProcess" || this.completionSeen)
545
+ return null;
546
+ this.completionSeen = true;
547
+ return new TextFrame(this, SLOT_COMPLETION_CONDITION, attrs);
548
+ case "property":
549
+ this.properties.push({
550
+ id: attr(attrs, "id"),
551
+ name: attr(attrs, "name"),
552
+ itemSubjectRef: attr(attrs, "itemSubjectRef"),
553
+ unknownAttributes: unknownAttrs(attrs),
554
+ });
555
+ return null;
556
+ case "dataInputAssociation":
557
+ return new DataAssociationFrame(attrs, this.dataInputAssociations);
558
+ case "dataOutputAssociation":
559
+ return new DataAssociationFrame(attrs, this.dataOutputAssociations);
560
+ default:
561
+ break;
294
562
  }
295
- case "endEvent":
296
- case "intermediateCatchEvent":
297
- case "intermediateThrowEvent":
298
- return { ...base, type: ln, eventDefinitions: parseEventDefinitions(element) };
299
- case "boundaryEvent":
300
- return {
301
- ...base,
302
- type: "boundaryEvent",
303
- attachedToRef: requiredAttr(element, "attachedToRef"),
304
- cancelActivity: attr(element, "cancelActivity") !== undefined
305
- ? attr(element, "cancelActivity") === "true"
306
- : undefined,
307
- eventDefinitions: parseEventDefinitions(element),
308
- };
309
- case "task":
310
- case "serviceTask":
311
- case "scriptTask":
312
- case "userTask":
313
- case "businessRuleTask":
314
- case "manualTask":
315
- case "callActivity":
316
- return {
317
- ...base,
318
- type: ln,
319
- loopCharacteristics: parseLoopCharacteristics(element),
320
- isForCompensation: attr(element, "isForCompensation") === "true" ? true : undefined,
321
- };
322
- case "sendTask":
323
- case "receiveTask":
324
- return {
325
- ...base,
326
- type: ln,
327
- messageRef: attr(element, "messageRef"),
328
- loopCharacteristics: parseLoopCharacteristics(element),
329
- isForCompensation: attr(element, "isForCompensation") === "true" ? true : undefined,
330
- };
331
- case "adHocSubProcess": {
332
- const completionEl = findChild(element, "completionCondition");
333
- return {
334
- ...base,
335
- type: "adHocSubProcess",
336
- loopCharacteristics: parseLoopCharacteristics(element),
337
- completionCondition: completionEl
338
- ? { text: completionEl.text ?? "", attributes: { ...completionEl.attributes } }
339
- : undefined,
340
- cancelRemainingInstances: attr(element, "cancelRemainingInstances") !== undefined
341
- ? attr(element, "cancelRemainingInstances") === "true"
342
- : undefined,
343
- ...parseProcessContents(element),
344
- };
563
+ if (this.eventDefinitions !== null && EVENT_DEFINITION_TYPES.has(local)) {
564
+ return eventDefinitionFrame(local, attrs, this.eventDefinitions);
345
565
  }
346
- case "subProcess":
347
- return {
348
- ...base,
349
- type: "subProcess",
350
- triggeredByEvent: attr(element, "triggeredByEvent") !== undefined
351
- ? attr(element, "triggeredByEvent") === "true"
352
- : undefined,
353
- loopCharacteristics: parseLoopCharacteristics(element),
354
- ...parseProcessContents(element),
355
- };
356
- case "eventSubProcess":
357
- return {
358
- ...base,
359
- type: "eventSubProcess",
360
- ...parseProcessContents(element),
361
- };
362
- case "transaction":
363
- return {
364
- ...base,
365
- type: "transaction",
366
- loopCharacteristics: parseLoopCharacteristics(element),
367
- ...parseProcessContents(element),
368
- };
369
- case "exclusiveGateway":
370
- return { ...base, type: "exclusiveGateway", default: attr(element, "default") };
371
- case "parallelGateway":
372
- return { ...base, type: "parallelGateway" };
373
- case "inclusiveGateway":
374
- return { ...base, type: "inclusiveGateway", default: attr(element, "default") };
375
- case "eventBasedGateway":
376
- return { ...base, type: "eventBasedGateway" };
377
- case "complexGateway":
378
- return { ...base, type: "complexGateway", default: attr(element, "default") };
379
- case "dataObject":
380
- return {
381
- ...base,
382
- type: "dataObject",
383
- ...(attr(element, "isCollection") === "true" ? { isCollection: true } : {}),
384
- };
385
- case "dataObjectReference":
386
- return {
387
- ...base,
388
- type: "dataObjectReference",
389
- dataObjectRef: attr(element, "dataObjectRef"),
390
- ...(attr(element, "isCollection") === "true" ? { isCollection: true } : {}),
391
- };
392
- case "dataStoreReference":
393
- return {
394
- ...base,
395
- type: "dataStoreReference",
396
- dataStoreRef: attr(element, "dataStoreRef"),
397
- };
398
- default:
399
- return undefined;
566
+ if (this.contents !== null) {
567
+ const child = contentsChild(this.contents, local, name, attrs);
568
+ if (child !== undefined)
569
+ return child;
570
+ }
571
+ return captureUnknown(this.unknownChildren, name, attrs);
400
572
  }
401
- }
402
- // ---------------------------------------------------------------------------
403
- // Sequence flows
404
- // ---------------------------------------------------------------------------
405
- function parseSequenceFlow(element) {
406
- const condEl = findChild(element, "conditionExpression");
407
- let conditionExpression;
408
- if (condEl) {
409
- conditionExpression = {
410
- text: condEl.text ?? "",
411
- attributes: { ...condEl.attributes },
573
+ setText(slot, text, attrs) {
574
+ switch (slot) {
575
+ case SLOT_INCOMING: {
576
+ const ref = text?.trim();
577
+ if (ref)
578
+ this.incoming.push(ref);
579
+ break;
580
+ }
581
+ case SLOT_OUTGOING: {
582
+ const ref = text?.trim();
583
+ if (ref)
584
+ this.outgoing.push(ref);
585
+ break;
586
+ }
587
+ case SLOT_DOCUMENTATION:
588
+ this.documentation = text;
589
+ break;
590
+ case SLOT_COMPLETION_CONDITION:
591
+ this.completionCondition = { text: text ?? "", attributes: { ...attrs } };
592
+ break;
593
+ default:
594
+ break;
595
+ }
596
+ }
597
+ setLoopCharacteristics(loop) {
598
+ this.loopCharacteristics = loop;
599
+ }
600
+ finish() {
601
+ const attrs = this.attrs;
602
+ const base = {
603
+ id: this.id,
604
+ ...(this.properties.length > 0 ? { properties: this.properties } : {}),
605
+ ...(this.dataInputAssociations.length > 0
606
+ ? { dataInputAssociations: this.dataInputAssociations }
607
+ : {}),
608
+ ...(this.dataOutputAssociations.length > 0
609
+ ? { dataOutputAssociations: this.dataOutputAssociations }
610
+ : {}),
611
+ ...(this.unknownChildren.length > 0 ? { unknownChildren: this.unknownChildren } : {}),
612
+ name: attr(attrs, "name"),
613
+ incoming: this.incoming,
614
+ outgoing: this.outgoing,
615
+ documentation: this.documentation,
616
+ extensionElements: this.extensionElements ?? [],
617
+ unknownAttributes: unknownAttrs(attrs),
412
618
  };
619
+ const eventDefinitions = this.eventDefinitions ?? [];
620
+ const contents = this.contents ?? newContents();
621
+ const type = this.type;
622
+ let el;
623
+ switch (type) {
624
+ case "startEvent": {
625
+ const isInterruptingAttr = attr(attrs, "isInterrupting");
626
+ el = {
627
+ ...base,
628
+ type: "startEvent",
629
+ eventDefinitions,
630
+ ...(isInterruptingAttr === "false" ? { isInterrupting: false } : {}),
631
+ };
632
+ break;
633
+ }
634
+ case "endEvent":
635
+ case "intermediateCatchEvent":
636
+ case "intermediateThrowEvent":
637
+ el = { ...base, type, eventDefinitions };
638
+ break;
639
+ case "boundaryEvent":
640
+ el = {
641
+ ...base,
642
+ type: "boundaryEvent",
643
+ attachedToRef: requiredAttr(attrs, "attachedToRef", this.name),
644
+ cancelActivity: attr(attrs, "cancelActivity") !== undefined
645
+ ? attr(attrs, "cancelActivity") === "true"
646
+ : undefined,
647
+ eventDefinitions,
648
+ };
649
+ break;
650
+ case "task":
651
+ case "serviceTask":
652
+ case "scriptTask":
653
+ case "userTask":
654
+ case "businessRuleTask":
655
+ case "manualTask":
656
+ case "callActivity":
657
+ el = {
658
+ ...base,
659
+ type,
660
+ loopCharacteristics: this.loopCharacteristics,
661
+ isForCompensation: attr(attrs, "isForCompensation") === "true" ? true : undefined,
662
+ };
663
+ break;
664
+ case "sendTask":
665
+ case "receiveTask":
666
+ el = {
667
+ ...base,
668
+ type,
669
+ messageRef: attr(attrs, "messageRef"),
670
+ loopCharacteristics: this.loopCharacteristics,
671
+ isForCompensation: attr(attrs, "isForCompensation") === "true" ? true : undefined,
672
+ };
673
+ break;
674
+ case "adHocSubProcess":
675
+ el = {
676
+ ...base,
677
+ type: "adHocSubProcess",
678
+ loopCharacteristics: this.loopCharacteristics,
679
+ completionCondition: this.completionCondition,
680
+ cancelRemainingInstances: attr(attrs, "cancelRemainingInstances") !== undefined
681
+ ? attr(attrs, "cancelRemainingInstances") === "true"
682
+ : undefined,
683
+ ...contents,
684
+ };
685
+ break;
686
+ case "subProcess":
687
+ el = {
688
+ ...base,
689
+ type: "subProcess",
690
+ triggeredByEvent: attr(attrs, "triggeredByEvent") !== undefined
691
+ ? attr(attrs, "triggeredByEvent") === "true"
692
+ : undefined,
693
+ loopCharacteristics: this.loopCharacteristics,
694
+ ...contents,
695
+ };
696
+ break;
697
+ case "eventSubProcess":
698
+ el = { ...base, type: "eventSubProcess", ...contents };
699
+ break;
700
+ case "transaction":
701
+ el = {
702
+ ...base,
703
+ type: "transaction",
704
+ loopCharacteristics: this.loopCharacteristics,
705
+ ...contents,
706
+ };
707
+ break;
708
+ case "exclusiveGateway":
709
+ el = { ...base, type: "exclusiveGateway", default: attr(attrs, "default") };
710
+ break;
711
+ case "parallelGateway":
712
+ el = { ...base, type: "parallelGateway" };
713
+ break;
714
+ case "inclusiveGateway":
715
+ el = { ...base, type: "inclusiveGateway", default: attr(attrs, "default") };
716
+ break;
717
+ case "eventBasedGateway":
718
+ el = { ...base, type: "eventBasedGateway" };
719
+ break;
720
+ case "complexGateway":
721
+ el = { ...base, type: "complexGateway", default: attr(attrs, "default") };
722
+ break;
723
+ case "dataObject":
724
+ el = {
725
+ ...base,
726
+ type: "dataObject",
727
+ ...(attr(attrs, "isCollection") === "true" ? { isCollection: true } : {}),
728
+ };
729
+ break;
730
+ case "dataObjectReference":
731
+ el = {
732
+ ...base,
733
+ type: "dataObjectReference",
734
+ dataObjectRef: attr(attrs, "dataObjectRef"),
735
+ ...(attr(attrs, "isCollection") === "true" ? { isCollection: true } : {}),
736
+ };
737
+ break;
738
+ case "dataStoreReference":
739
+ el = {
740
+ ...base,
741
+ type: "dataStoreReference",
742
+ dataStoreRef: attr(attrs, "dataStoreRef"),
743
+ };
744
+ break;
745
+ default:
746
+ return;
747
+ }
748
+ this.target.flowElements.push(el);
413
749
  }
414
- return {
415
- id: requiredAttr(element, "id"),
416
- name: attr(element, "name"),
417
- sourceRef: requiredAttr(element, "sourceRef"),
418
- targetRef: requiredAttr(element, "targetRef"),
419
- conditionExpression,
420
- extensionElements: parseExtensionElements(element),
421
- unknownAttributes: unknownAttrs(element),
422
- };
423
750
  }
424
751
  // ---------------------------------------------------------------------------
425
- // Annotations
752
+ // Event definitions
426
753
  // ---------------------------------------------------------------------------
427
- function parseTextAnnotation(element) {
428
- const textEl = findChild(element, "text");
429
- return {
430
- id: requiredAttr(element, "id"),
431
- text: textEl?.text,
432
- unknownAttributes: unknownAttrs(element),
433
- };
754
+ function eventDefinitionFrame(local, attrs, target) {
755
+ switch (local) {
756
+ case "timerEventDefinition":
757
+ return new TimerDefinitionFrame(attrs, target);
758
+ case "conditionalEventDefinition":
759
+ return new ConditionalDefinitionFrame(attrs, target);
760
+ case "errorEventDefinition":
761
+ target.push({ type: "error", id: attr(attrs, "id"), errorRef: attr(attrs, "errorRef") });
762
+ return null;
763
+ case "escalationEventDefinition":
764
+ target.push({
765
+ type: "escalation",
766
+ id: attr(attrs, "id"),
767
+ escalationRef: attr(attrs, "escalationRef"),
768
+ });
769
+ return null;
770
+ case "messageEventDefinition":
771
+ target.push({ type: "message", id: attr(attrs, "id"), messageRef: attr(attrs, "messageRef") });
772
+ return null;
773
+ case "signalEventDefinition":
774
+ target.push({ type: "signal", id: attr(attrs, "id"), signalRef: attr(attrs, "signalRef") });
775
+ return null;
776
+ case "linkEventDefinition":
777
+ target.push({ type: "link", id: attr(attrs, "id"), name: attr(attrs, "name") });
778
+ return null;
779
+ case "cancelEventDefinition":
780
+ target.push({ type: "cancel", id: attr(attrs, "id") });
781
+ return null;
782
+ case "terminateEventDefinition":
783
+ target.push({ type: "terminate", id: attr(attrs, "id") });
784
+ return null;
785
+ case "compensateEventDefinition":
786
+ target.push({
787
+ type: "compensate",
788
+ id: attr(attrs, "id"),
789
+ activityRef: attr(attrs, "activityRef"),
790
+ });
791
+ return null;
792
+ default:
793
+ return null;
794
+ }
434
795
  }
435
- function parseAssociation(element) {
436
- return {
437
- id: requiredAttr(element, "id"),
438
- sourceRef: requiredAttr(element, "sourceRef"),
439
- targetRef: requiredAttr(element, "targetRef"),
440
- associationDirection: attr(element, "associationDirection"),
441
- unknownAttributes: unknownAttrs(element),
442
- };
796
+ class TimerDefinitionFrame extends Frame {
797
+ attrs;
798
+ target;
799
+ duration;
800
+ date;
801
+ cycle;
802
+ constructor(attrs, target) {
803
+ super();
804
+ this.attrs = attrs;
805
+ this.target = target;
806
+ }
807
+ child(local, _name, attrs) {
808
+ switch (local) {
809
+ case "timeDuration":
810
+ if (this.duration)
811
+ return null;
812
+ this.duration = { text: undefined, attrs };
813
+ return new TextFrame(this, SLOT_TIME_DURATION, attrs);
814
+ case "timeDate":
815
+ if (this.date)
816
+ return null;
817
+ this.date = { text: undefined, attrs };
818
+ return new TextFrame(this, SLOT_TIME_DATE, attrs);
819
+ case "timeCycle":
820
+ if (this.cycle)
821
+ return null;
822
+ this.cycle = { text: undefined, attrs };
823
+ return new TextFrame(this, SLOT_TIME_CYCLE, attrs);
824
+ default:
825
+ return null;
826
+ }
827
+ }
828
+ setText(slot, text) {
829
+ if (slot === SLOT_TIME_DURATION && this.duration)
830
+ this.duration.text = text;
831
+ else if (slot === SLOT_TIME_DATE && this.date)
832
+ this.date.text = text;
833
+ else if (slot === SLOT_TIME_CYCLE && this.cycle)
834
+ this.cycle.text = text;
835
+ }
836
+ finish() {
837
+ this.target.push({
838
+ type: "timer",
839
+ id: attr(this.attrs, "id"),
840
+ timeDuration: this.duration?.text?.trim(),
841
+ timeDurationAttributes: partAttributes(this.duration),
842
+ timeDate: this.date?.text?.trim(),
843
+ timeDateAttributes: partAttributes(this.date),
844
+ timeCycle: this.cycle?.text?.trim(),
845
+ timeCycleAttributes: partAttributes(this.cycle),
846
+ });
847
+ }
443
848
  }
444
- function parseGroup(element) {
445
- return {
446
- id: requiredAttr(element, "id"),
447
- categoryValueRef: attr(element, "categoryValueRef"),
448
- unknownAttributes: unknownAttrs(element),
449
- };
849
+ function partAttributes(part) {
850
+ if (!part)
851
+ return undefined;
852
+ return Object.keys(part.attrs).length > 0 ? { ...part.attrs } : undefined;
853
+ }
854
+ class ConditionalDefinitionFrame extends Frame {
855
+ attrs;
856
+ target;
857
+ conditionSeen = false;
858
+ condition;
859
+ constructor(attrs, target) {
860
+ super();
861
+ this.attrs = attrs;
862
+ this.target = target;
863
+ }
864
+ child(local, _name, attrs) {
865
+ if (local !== "condition" || this.conditionSeen)
866
+ return null;
867
+ this.conditionSeen = true;
868
+ return new TextFrame(this, SLOT_CONDITION, attrs);
869
+ }
870
+ setText(_slot, text) {
871
+ this.condition = text;
872
+ }
873
+ finish() {
874
+ this.target.push({
875
+ type: "conditional",
876
+ id: attr(this.attrs, "id"),
877
+ condition: this.condition?.trim(),
878
+ });
879
+ }
450
880
  }
451
881
  // ---------------------------------------------------------------------------
452
- // Process contents (shared between process and adHocSubProcess)
882
+ // Multi-instance loop
453
883
  // ---------------------------------------------------------------------------
454
- function parseProcessContents(element) {
455
- const flowElements = [];
456
- const sequenceFlows = [];
457
- const textAnnotations = [];
458
- const associations = [];
459
- const groups = [];
460
- for (const child of element.children) {
461
- const ln = localName(child.name);
462
- if (FLOW_ELEMENT_TYPES.has(ln)) {
463
- const fe = parseFlowElement(child);
464
- if (fe)
465
- flowElements.push(fe);
466
- }
467
- else if (ln === "sequenceFlow") {
468
- sequenceFlows.push(parseSequenceFlow(child));
469
- }
470
- else if (ln === "textAnnotation") {
471
- textAnnotations.push(parseTextAnnotation(child));
472
- }
473
- else if (ln === "association") {
474
- associations.push(parseAssociation(child));
475
- }
476
- else if (ln === "group") {
477
- groups.push(parseGroup(child));
884
+ class LoopFrame extends Frame {
885
+ attrs;
886
+ owner;
887
+ unknownChildren = [];
888
+ extensionElements = null;
889
+ loopCardinality;
890
+ cardinalitySeen = false;
891
+ completionCondition;
892
+ completionSeen = false;
893
+ constructor(attrs, owner) {
894
+ super();
895
+ this.attrs = attrs;
896
+ this.owner = owner;
897
+ }
898
+ child(local, _name, attrs) {
899
+ switch (local) {
900
+ case "extensionElements":
901
+ if (this.extensionElements !== null)
902
+ return null;
903
+ this.extensionElements = [];
904
+ return new TreeFrame(this.extensionElements);
905
+ case "loopCardinality":
906
+ if (this.cardinalitySeen)
907
+ return null;
908
+ this.cardinalitySeen = true;
909
+ return new TextFrame(this, SLOT_LOOP_CARDINALITY, attrs);
910
+ case "completionCondition":
911
+ if (this.completionSeen)
912
+ return null;
913
+ this.completionSeen = true;
914
+ return new TextFrame(this, SLOT_COMPLETION_CONDITION, attrs);
915
+ default:
916
+ return captureUnknown(this.unknownChildren, _name, attrs);
478
917
  }
479
918
  }
480
- return { flowElements, sequenceFlows, textAnnotations, associations, groups };
919
+ setText(slot, text, attrs) {
920
+ const expression = { text: text ?? "", attributes: { ...attrs } };
921
+ if (slot === SLOT_LOOP_CARDINALITY)
922
+ this.loopCardinality = expression;
923
+ else if (slot === SLOT_COMPLETION_CONDITION)
924
+ this.completionCondition = expression;
925
+ }
926
+ finish() {
927
+ this.owner.setLoopCharacteristics({
928
+ isSequential: this.attrs.isSequential === "true" ? true : undefined,
929
+ loopCardinality: this.loopCardinality,
930
+ completionCondition: this.completionCondition,
931
+ extensionElements: this.extensionElements ?? [],
932
+ ...(this.unknownChildren.length > 0 ? { unknownChildren: this.unknownChildren } : {}),
933
+ });
934
+ }
481
935
  }
482
936
  // ---------------------------------------------------------------------------
483
- // Lanes
937
+ // Sequence flows and annotations
484
938
  // ---------------------------------------------------------------------------
485
- function parseLane(element) {
486
- const flowNodeRefs = findChildren(element, "flowNodeRef")
487
- .map((c) => c.text?.trim())
488
- .filter((t) => !!t);
489
- const childLaneSetEl = findChild(element, "childLaneSet");
490
- return {
491
- id: requiredAttr(element, "id"),
492
- name: attr(element, "name"),
493
- flowNodeRefs,
494
- childLaneSet: childLaneSetEl ? parseLaneSet(childLaneSetEl) : undefined,
495
- unknownAttributes: unknownAttrs(element),
496
- };
939
+ class SequenceFlowFrame extends Frame {
940
+ attrs;
941
+ target;
942
+ id;
943
+ sourceRef;
944
+ targetRef;
945
+ conditionExpression;
946
+ conditionSeen = false;
947
+ extensionElements = null;
948
+ constructor(name, attrs, target) {
949
+ super();
950
+ this.attrs = attrs;
951
+ this.target = target;
952
+ this.id = requiredAttr(attrs, "id", name);
953
+ this.sourceRef = requiredAttr(attrs, "sourceRef", name);
954
+ this.targetRef = requiredAttr(attrs, "targetRef", name);
955
+ }
956
+ child(local, _name, attrs) {
957
+ switch (local) {
958
+ case "conditionExpression":
959
+ if (this.conditionSeen)
960
+ return null;
961
+ this.conditionSeen = true;
962
+ return new TextFrame(this, SLOT_CONDITION_EXPRESSION, attrs);
963
+ case "extensionElements":
964
+ if (this.extensionElements !== null)
965
+ return null;
966
+ this.extensionElements = [];
967
+ return new TreeFrame(this.extensionElements);
968
+ default:
969
+ return null;
970
+ }
971
+ }
972
+ setText(_slot, text, attrs) {
973
+ this.conditionExpression = { text: text ?? "", attributes: { ...attrs } };
974
+ }
975
+ finish() {
976
+ this.target.sequenceFlows.push({
977
+ id: this.id,
978
+ name: attr(this.attrs, "name"),
979
+ sourceRef: this.sourceRef,
980
+ targetRef: this.targetRef,
981
+ conditionExpression: this.conditionExpression,
982
+ extensionElements: this.extensionElements ?? [],
983
+ unknownAttributes: unknownAttrs(this.attrs),
984
+ });
985
+ }
497
986
  }
498
- function parseLaneSet(element) {
499
- return {
500
- id: attr(element, "id"),
501
- lanes: findChildren(element, "lane").map(parseLane),
502
- };
987
+ class TextAnnotationFrame extends BaseElementFrame {
988
+ name;
989
+ attrs;
990
+ target;
991
+ textSeen = false;
992
+ value;
993
+ constructor(name, attrs, target) {
994
+ super();
995
+ this.name = name;
996
+ this.attrs = attrs;
997
+ this.target = target;
998
+ }
999
+ child(local, _name, attrs) {
1000
+ const base = this.baseChild(local, attrs);
1001
+ if (base !== undefined)
1002
+ return base;
1003
+ if (local !== "text" || this.textSeen)
1004
+ return null;
1005
+ this.textSeen = true;
1006
+ return new TextFrame(this, SLOT_TEXT, attrs);
1007
+ }
1008
+ baseText(_slot, text) {
1009
+ this.value = text;
1010
+ }
1011
+ finish() {
1012
+ this.target.push({
1013
+ id: requiredAttr(this.attrs, "id", this.name),
1014
+ text: this.value,
1015
+ ...this.baseFields(),
1016
+ unknownAttributes: unknownAttrs(this.attrs),
1017
+ });
1018
+ }
503
1019
  }
504
1020
  // ---------------------------------------------------------------------------
505
- // Process
1021
+ // Categories
506
1022
  // ---------------------------------------------------------------------------
507
- function parseProcess(element) {
508
- const laneSetEl = findChild(element, "laneSet");
509
- return {
510
- id: requiredAttr(element, "id"),
511
- name: attr(element, "name"),
512
- isExecutable: attr(element, "isExecutable") === "true" ? true : undefined,
513
- extensionElements: parseExtensionElements(element),
514
- unknownAttributes: unknownAttrs(element),
515
- laneSet: laneSetEl ? parseLaneSet(laneSetEl) : undefined,
516
- ...parseProcessContents(element),
517
- };
1023
+ /** A root `bpmn:category`; its values supply the labels groups point at. */
1024
+ class CategoryFrame extends BaseElementFrame {
1025
+ attrs;
1026
+ target;
1027
+ categoryValues = [];
1028
+ constructor(attrs, target) {
1029
+ super();
1030
+ this.attrs = attrs;
1031
+ this.target = target;
1032
+ }
1033
+ child(local, _name, attrs) {
1034
+ const base = this.baseChild(local, attrs);
1035
+ if (base !== undefined)
1036
+ return base;
1037
+ if (local === "categoryValue") {
1038
+ const id = attr(attrs, "id");
1039
+ if (id !== undefined) {
1040
+ this.categoryValues.push({
1041
+ id,
1042
+ value: attr(attrs, "value"),
1043
+ unknownAttributes: unknownAttrs(attrs),
1044
+ });
1045
+ }
1046
+ }
1047
+ return null;
1048
+ }
1049
+ finish() {
1050
+ this.target.push({
1051
+ id: attr(this.attrs, "id"),
1052
+ name: attr(this.attrs, "name"),
1053
+ categoryValues: this.categoryValues,
1054
+ unknownAttributes: unknownAttrs(this.attrs),
1055
+ });
1056
+ }
518
1057
  }
519
1058
  // ---------------------------------------------------------------------------
520
- // Collaboration
1059
+ // Data associations
521
1060
  // ---------------------------------------------------------------------------
522
- function parseParticipant(element) {
523
- return {
524
- id: requiredAttr(element, "id"),
525
- name: attr(element, "name"),
526
- processRef: attr(element, "processRef"),
527
- unknownAttributes: unknownAttrs(element),
528
- };
1061
+ const SLOT_SOURCE_REF = 11;
1062
+ const SLOT_TARGET_REF = 12;
1063
+ /** A `dataInputAssociation` or `dataOutputAssociation` and its ref children. */
1064
+ class DataAssociationFrame extends BaseElementFrame {
1065
+ attrs;
1066
+ target;
1067
+ sourceRefs = [];
1068
+ unknownChildren = [];
1069
+ targetRef;
1070
+ constructor(attrs, target) {
1071
+ super();
1072
+ this.attrs = attrs;
1073
+ this.target = target;
1074
+ }
1075
+ child(local, name, attrs) {
1076
+ const base = this.baseChild(local, attrs);
1077
+ if (base !== undefined)
1078
+ return base;
1079
+ if (local === "sourceRef")
1080
+ return new TextFrame(this, SLOT_SOURCE_REF, attrs);
1081
+ if (local === "targetRef")
1082
+ return new TextFrame(this, SLOT_TARGET_REF, attrs);
1083
+ return captureUnknown(this.unknownChildren, name, attrs);
1084
+ }
1085
+ baseText(slot, text) {
1086
+ const ref = text?.trim();
1087
+ if (!ref)
1088
+ return;
1089
+ if (slot === SLOT_SOURCE_REF)
1090
+ this.sourceRefs.push(ref);
1091
+ else if (slot === SLOT_TARGET_REF)
1092
+ this.targetRef = ref;
1093
+ }
1094
+ finish() {
1095
+ this.target.push({
1096
+ id: attr(this.attrs, "id"),
1097
+ sourceRefs: this.sourceRefs,
1098
+ targetRef: this.targetRef,
1099
+ unknownAttributes: unknownAttrs(this.attrs),
1100
+ ...(this.unknownChildren.length > 0 ? { unknownChildren: this.unknownChildren } : {}),
1101
+ });
1102
+ }
529
1103
  }
530
- function parseMessageFlow(element) {
531
- return {
532
- id: requiredAttr(element, "id"),
533
- name: attr(element, "name"),
534
- sourceRef: requiredAttr(element, "sourceRef"),
535
- targetRef: requiredAttr(element, "targetRef"),
536
- unknownAttributes: unknownAttrs(element),
537
- };
1104
+ class LaneSetFrame extends Frame {
1105
+ attrs;
1106
+ owner;
1107
+ lanes = [];
1108
+ constructor(attrs, owner) {
1109
+ super();
1110
+ this.attrs = attrs;
1111
+ this.owner = owner;
1112
+ }
1113
+ child(local, name, attrs) {
1114
+ return local === "lane" ? new LaneFrame(name, attrs, this.lanes) : null;
1115
+ }
1116
+ finish() {
1117
+ this.owner.setLaneSet({
1118
+ id: attr(this.attrs, "id"),
1119
+ name: attr(this.attrs, "name"),
1120
+ lanes: this.lanes,
1121
+ unknownAttributes: unknownAttrs(this.attrs),
1122
+ });
1123
+ }
538
1124
  }
539
- function parseCollaboration(element) {
540
- return {
541
- id: requiredAttr(element, "id"),
542
- participants: findChildren(element, "participant").map(parseParticipant),
543
- messageFlows: findChildren(element, "messageFlow").map(parseMessageFlow),
544
- textAnnotations: findChildren(element, "textAnnotation").map(parseTextAnnotation),
545
- associations: findChildren(element, "association").map(parseAssociation),
546
- groups: findChildren(element, "group").map(parseGroup),
547
- extensionElements: parseExtensionElements(element),
548
- unknownAttributes: unknownAttrs(element),
549
- };
1125
+ class LaneFrame extends BaseElementFrame {
1126
+ name;
1127
+ attrs;
1128
+ target;
1129
+ flowNodeRefs = [];
1130
+ childLaneSet;
1131
+ childLaneSetSeen = false;
1132
+ constructor(name, attrs, target) {
1133
+ super();
1134
+ this.name = name;
1135
+ this.attrs = attrs;
1136
+ this.target = target;
1137
+ }
1138
+ child(local, _name, attrs) {
1139
+ const base = this.baseChild(local, attrs);
1140
+ if (base !== undefined)
1141
+ return base;
1142
+ switch (local) {
1143
+ case "flowNodeRef":
1144
+ return new TextFrame(this, SLOT_FLOW_NODE_REF, attrs);
1145
+ case "childLaneSet":
1146
+ if (this.childLaneSetSeen)
1147
+ return null;
1148
+ this.childLaneSetSeen = true;
1149
+ return new LaneSetFrame(attrs, this);
1150
+ default:
1151
+ return null;
1152
+ }
1153
+ }
1154
+ baseText(_slot, text) {
1155
+ const ref = text?.trim();
1156
+ if (ref)
1157
+ this.flowNodeRefs.push(ref);
1158
+ }
1159
+ setLaneSet(laneSet) {
1160
+ this.childLaneSet = laneSet;
1161
+ }
1162
+ finish() {
1163
+ this.target.push({
1164
+ id: requiredAttr(this.attrs, "id", this.name),
1165
+ name: attr(this.attrs, "name"),
1166
+ flowNodeRefs: this.flowNodeRefs,
1167
+ childLaneSet: this.childLaneSet,
1168
+ ...this.baseFields(),
1169
+ unknownAttributes: unknownAttrs(this.attrs),
1170
+ });
1171
+ }
550
1172
  }
551
1173
  // ---------------------------------------------------------------------------
552
- // Root elements
1174
+ // Process
553
1175
  // ---------------------------------------------------------------------------
554
- function parseError(element) {
555
- return {
556
- id: requiredAttr(element, "id"),
557
- name: attr(element, "name"),
558
- errorCode: attr(element, "errorCode"),
559
- };
560
- }
561
- function parseEscalation(element) {
562
- return {
563
- id: requiredAttr(element, "id"),
564
- name: attr(element, "name"),
565
- escalationCode: attr(element, "escalationCode"),
566
- };
567
- }
568
- function parseMessage(element) {
569
- return {
570
- id: requiredAttr(element, "id"),
571
- name: attr(element, "name"),
572
- unknownAttributes: unknownAttrs(element),
573
- };
574
- }
575
- function parseSignal(element) {
576
- return {
577
- id: requiredAttr(element, "id"),
578
- name: attr(element, "name"),
579
- };
1176
+ class ProcessFrame extends Frame {
1177
+ attrs;
1178
+ target;
1179
+ id;
1180
+ unknownChildren = [];
1181
+ documentation;
1182
+ documentationSeen = false;
1183
+ extensionElements = null;
1184
+ laneSet;
1185
+ laneSetSeen = false;
1186
+ contents = newContents();
1187
+ constructor(name, attrs, target) {
1188
+ super();
1189
+ this.attrs = attrs;
1190
+ this.target = target;
1191
+ this.id = requiredAttr(attrs, "id", name);
1192
+ }
1193
+ child(local, name, attrs) {
1194
+ switch (local) {
1195
+ case "documentation":
1196
+ if (this.documentationSeen)
1197
+ return null;
1198
+ this.documentationSeen = true;
1199
+ return new TextFrame(this, SLOT_DOCUMENTATION, attrs);
1200
+ case "extensionElements":
1201
+ if (this.extensionElements !== null)
1202
+ return null;
1203
+ this.extensionElements = [];
1204
+ return new TreeFrame(this.extensionElements);
1205
+ case "laneSet":
1206
+ if (this.laneSetSeen)
1207
+ return null;
1208
+ this.laneSetSeen = true;
1209
+ return new LaneSetFrame(attrs, this);
1210
+ default: {
1211
+ const child = contentsChild(this.contents, local, name, attrs);
1212
+ return child === undefined ? captureUnknown(this.unknownChildren, name, attrs) : child;
1213
+ }
1214
+ }
1215
+ }
1216
+ setText(slot, text) {
1217
+ if (slot === SLOT_DOCUMENTATION)
1218
+ this.documentation = text;
1219
+ }
1220
+ setLaneSet(laneSet) {
1221
+ this.laneSet = laneSet;
1222
+ }
1223
+ finish() {
1224
+ const attrs = this.attrs;
1225
+ this.target.push({
1226
+ id: this.id,
1227
+ name: attr(attrs, "name"),
1228
+ isExecutable: attr(attrs, "isExecutable") === "true" ? true : undefined,
1229
+ ...(this.documentation !== undefined ? { documentation: this.documentation } : {}),
1230
+ extensionElements: this.extensionElements ?? [],
1231
+ unknownAttributes: unknownAttrs(attrs),
1232
+ laneSet: this.laneSet,
1233
+ ...(this.unknownChildren.length > 0 ? { unknownChildren: this.unknownChildren } : {}),
1234
+ ...this.contents,
1235
+ });
1236
+ }
580
1237
  }
581
1238
  // ---------------------------------------------------------------------------
582
- // Diagram interchange
1239
+ // Collaboration
583
1240
  // ---------------------------------------------------------------------------
584
- function parseBounds(element) {
585
- return {
586
- x: Number(attr(element, "x") ?? "0"),
587
- y: Number(attr(element, "y") ?? "0"),
588
- width: Number(attr(element, "width") ?? "0"),
589
- height: Number(attr(element, "height") ?? "0"),
590
- };
1241
+ class CollaborationFrame extends Frame {
1242
+ attrs;
1243
+ target;
1244
+ id;
1245
+ participants = [];
1246
+ messageFlows = [];
1247
+ textAnnotations = [];
1248
+ associations = [];
1249
+ groups = [];
1250
+ unknownChildren = [];
1251
+ extensionElements = null;
1252
+ constructor(name, attrs, target) {
1253
+ super();
1254
+ this.attrs = attrs;
1255
+ this.target = target;
1256
+ this.id = requiredAttr(attrs, "id", name);
1257
+ }
1258
+ child(local, name, attrs) {
1259
+ switch (local) {
1260
+ case "participant":
1261
+ return new BaseOnlyFrame(attrs, (a, base) => parseParticipant(name, a, base), this.participants);
1262
+ case "messageFlow":
1263
+ return new BaseOnlyFrame(attrs, (a, base) => parseMessageFlow(name, a, base), this.messageFlows);
1264
+ case "textAnnotation":
1265
+ return new TextAnnotationFrame(name, attrs, this.textAnnotations);
1266
+ case "association":
1267
+ return new BaseOnlyFrame(attrs, (a, base) => parseAssociation(name, a, base), this.associations);
1268
+ case "group":
1269
+ return new BaseOnlyFrame(attrs, (a, base) => parseGroup(name, a, base), this.groups);
1270
+ case "extensionElements":
1271
+ if (this.extensionElements !== null)
1272
+ return null;
1273
+ this.extensionElements = [];
1274
+ return new TreeFrame(this.extensionElements);
1275
+ default:
1276
+ return captureUnknown(this.unknownChildren, name, attrs);
1277
+ }
1278
+ }
1279
+ finish() {
1280
+ this.target.push({
1281
+ id: this.id,
1282
+ participants: this.participants,
1283
+ messageFlows: this.messageFlows,
1284
+ textAnnotations: this.textAnnotations,
1285
+ associations: this.associations,
1286
+ groups: this.groups,
1287
+ extensionElements: this.extensionElements ?? [],
1288
+ unknownAttributes: unknownAttrs(this.attrs),
1289
+ ...(this.unknownChildren.length > 0 ? { unknownChildren: this.unknownChildren } : {}),
1290
+ });
1291
+ }
591
1292
  }
592
- function parseLabel(element) {
593
- const labelEl = findChild(element, "BPMNLabel");
594
- if (!labelEl)
595
- return undefined;
596
- const boundsEl = findChild(labelEl, "Bounds");
597
- return { bounds: boundsEl ? parseBounds(boundsEl) : undefined };
1293
+ class LabelFrame extends Frame {
1294
+ owner;
1295
+ bounds;
1296
+ boundsSeen = false;
1297
+ constructor(owner) {
1298
+ super();
1299
+ this.owner = owner;
1300
+ }
1301
+ child(local, _name, attrs) {
1302
+ if (local === "Bounds" && !this.boundsSeen) {
1303
+ this.boundsSeen = true;
1304
+ this.bounds = parseBounds(attrs);
1305
+ }
1306
+ return null;
1307
+ }
1308
+ finish() {
1309
+ this.owner.setLabel({ bounds: this.bounds });
1310
+ }
598
1311
  }
599
- function parseDiShape(element) {
600
- const boundsEl = findChild(element, "Bounds");
601
- if (!boundsEl)
602
- throw new Error(`Missing <dc:Bounds> in shape "${attr(element, "id")}"`);
603
- return {
604
- id: requiredAttr(element, "id"),
605
- bpmnElement: requiredAttr(element, "bpmnElement"),
606
- isMarkerVisible: attr(element, "isMarkerVisible") !== undefined
607
- ? attr(element, "isMarkerVisible") === "true"
608
- : undefined,
609
- isExpanded: attr(element, "isExpanded") !== undefined
610
- ? attr(element, "isExpanded") === "true"
611
- : undefined,
612
- isHorizontal: attr(element, "isHorizontal") !== undefined
613
- ? attr(element, "isHorizontal") === "true"
614
- : undefined,
615
- bounds: parseBounds(boundsEl),
616
- label: parseLabel(element),
617
- unknownAttributes: unknownAttrs(element),
618
- };
1312
+ class ShapeFrame extends Frame {
1313
+ name;
1314
+ attrs;
1315
+ target;
1316
+ bounds;
1317
+ label;
1318
+ labelSeen = false;
1319
+ constructor(name, attrs, target) {
1320
+ super();
1321
+ this.name = name;
1322
+ this.attrs = attrs;
1323
+ this.target = target;
1324
+ }
1325
+ child(local, _name, attrs) {
1326
+ switch (local) {
1327
+ case "Bounds":
1328
+ if (this.bounds === undefined)
1329
+ this.bounds = parseBounds(attrs);
1330
+ return null;
1331
+ case "BPMNLabel":
1332
+ if (this.labelSeen)
1333
+ return null;
1334
+ this.labelSeen = true;
1335
+ return new LabelFrame(this);
1336
+ default:
1337
+ return null;
1338
+ }
1339
+ }
1340
+ setLabel(label) {
1341
+ this.label = label;
1342
+ }
1343
+ finish() {
1344
+ const attrs = this.attrs;
1345
+ if (!this.bounds)
1346
+ throw new Error(`Missing <dc:Bounds> in shape "${attr(attrs, "id")}"`);
1347
+ this.target.push({
1348
+ id: requiredAttr(attrs, "id", this.name),
1349
+ bpmnElement: requiredAttr(attrs, "bpmnElement", this.name),
1350
+ isMarkerVisible: attr(attrs, "isMarkerVisible") !== undefined
1351
+ ? attr(attrs, "isMarkerVisible") === "true"
1352
+ : undefined,
1353
+ isExpanded: attr(attrs, "isExpanded") !== undefined ? attr(attrs, "isExpanded") === "true" : undefined,
1354
+ isHorizontal: attr(attrs, "isHorizontal") !== undefined
1355
+ ? attr(attrs, "isHorizontal") === "true"
1356
+ : undefined,
1357
+ bounds: this.bounds,
1358
+ label: this.label,
1359
+ unknownAttributes: unknownAttrs(attrs),
1360
+ });
1361
+ }
619
1362
  }
620
- function parseDiEdge(element) {
621
- const waypoints = findChildren(element, "waypoint").map((w) => ({
622
- x: Number(attr(w, "x") ?? "0"),
623
- y: Number(attr(w, "y") ?? "0"),
624
- }));
625
- return {
626
- id: requiredAttr(element, "id"),
627
- bpmnElement: requiredAttr(element, "bpmnElement"),
628
- waypoints,
629
- label: parseLabel(element),
630
- unknownAttributes: unknownAttrs(element),
631
- };
1363
+ class EdgeFrame extends Frame {
1364
+ name;
1365
+ attrs;
1366
+ target;
1367
+ waypoints = [];
1368
+ label;
1369
+ labelSeen = false;
1370
+ constructor(name, attrs, target) {
1371
+ super();
1372
+ this.name = name;
1373
+ this.attrs = attrs;
1374
+ this.target = target;
1375
+ }
1376
+ child(local, _name, attrs) {
1377
+ switch (local) {
1378
+ case "waypoint":
1379
+ this.waypoints.push({
1380
+ x: Number(attr(attrs, "x") ?? "0"),
1381
+ y: Number(attr(attrs, "y") ?? "0"),
1382
+ });
1383
+ return null;
1384
+ case "BPMNLabel":
1385
+ if (this.labelSeen)
1386
+ return null;
1387
+ this.labelSeen = true;
1388
+ return new LabelFrame(this);
1389
+ default:
1390
+ return null;
1391
+ }
1392
+ }
1393
+ setLabel(label) {
1394
+ this.label = label;
1395
+ }
1396
+ finish() {
1397
+ const attrs = this.attrs;
1398
+ this.target.push({
1399
+ id: requiredAttr(attrs, "id", this.name),
1400
+ bpmnElement: requiredAttr(attrs, "bpmnElement", this.name),
1401
+ waypoints: this.waypoints,
1402
+ label: this.label,
1403
+ unknownAttributes: unknownAttrs(attrs),
1404
+ });
1405
+ }
632
1406
  }
633
- function parseDiagram(element) {
634
- const planeEl = findChild(element, "BPMNPlane");
635
- if (!planeEl)
636
- throw new Error("Missing <bpmndi:BPMNPlane> in diagram");
637
- const shapes = [];
638
- const edges = [];
639
- for (const child of planeEl.children) {
640
- const ln = localName(child.name);
641
- if (ln === "BPMNShape")
642
- shapes.push(parseDiShape(child));
643
- else if (ln === "BPMNEdge")
644
- edges.push(parseDiEdge(child));
1407
+ class PlaneFrame extends Frame {
1408
+ name;
1409
+ attrs;
1410
+ owner;
1411
+ shapes = [];
1412
+ edges = [];
1413
+ constructor(name, attrs, owner) {
1414
+ super();
1415
+ this.name = name;
1416
+ this.attrs = attrs;
1417
+ this.owner = owner;
1418
+ }
1419
+ child(local, name, attrs) {
1420
+ if (local === "BPMNShape")
1421
+ return new ShapeFrame(name, attrs, this.shapes);
1422
+ if (local === "BPMNEdge")
1423
+ return new EdgeFrame(name, attrs, this.edges);
1424
+ return null;
1425
+ }
1426
+ finish() {
1427
+ this.owner.setPlane(this);
1428
+ }
1429
+ }
1430
+ class DiagramFrame extends Frame {
1431
+ name;
1432
+ attrs;
1433
+ target;
1434
+ plane;
1435
+ planeSeen = false;
1436
+ constructor(name, attrs, target) {
1437
+ super();
1438
+ this.name = name;
1439
+ this.attrs = attrs;
1440
+ this.target = target;
1441
+ }
1442
+ child(local, name, attrs) {
1443
+ if (local !== "BPMNPlane" || this.planeSeen)
1444
+ return null;
1445
+ this.planeSeen = true;
1446
+ return new PlaneFrame(name, attrs, this);
1447
+ }
1448
+ setPlane(plane) {
1449
+ this.plane = plane;
1450
+ }
1451
+ finish() {
1452
+ const plane = this.plane;
1453
+ if (!plane)
1454
+ throw new Error("Missing <bpmndi:BPMNPlane> in diagram");
1455
+ this.target.push({
1456
+ id: requiredAttr(this.attrs, "id", this.name),
1457
+ plane: {
1458
+ id: requiredAttr(plane.attrs, "id", plane.name),
1459
+ bpmnElement: requiredAttr(plane.attrs, "bpmnElement", plane.name),
1460
+ shapes: plane.shapes,
1461
+ edges: plane.edges,
1462
+ },
1463
+ });
645
1464
  }
646
- return {
647
- id: requiredAttr(element, "id"),
648
- plane: {
649
- id: requiredAttr(planeEl, "id"),
650
- bpmnElement: requiredAttr(planeEl, "bpmnElement"),
651
- shapes,
652
- edges,
653
- },
654
- };
655
1465
  }
656
1466
  // ---------------------------------------------------------------------------
657
- // Public API
1467
+ // Definitions (document root)
658
1468
  // ---------------------------------------------------------------------------
659
- /** Parse a BPMN XML string into a typed BpmnDefinitions model. */
660
- export function parseBpmn(xml) {
661
- const root = parseXml(xml);
662
- if (localName(root.name) !== "definitions") {
663
- throw new Error(`Expected <definitions> root element, got <${root.name}>`);
664
- }
665
- const namespaces = {};
666
- const unknownAttributes = {};
667
- for (const [key, value] of Object.entries(root.attributes)) {
668
- if (key.startsWith("xmlns:")) {
669
- namespaces[key.slice(6)] = value;
1469
+ class DefinitionsFrame extends Frame {
1470
+ name;
1471
+ attrs;
1472
+ sink;
1473
+ categories = [];
1474
+ documentation;
1475
+ documentationSeen = false;
1476
+ errors = [];
1477
+ escalations = [];
1478
+ messages = [];
1479
+ signals = [];
1480
+ collaborations = [];
1481
+ processes = [];
1482
+ diagrams = [];
1483
+ unknownChildren = [];
1484
+ constructor(name, attrs, sink) {
1485
+ super();
1486
+ this.name = name;
1487
+ this.attrs = attrs;
1488
+ this.sink = sink;
1489
+ }
1490
+ child(local, name, attrs) {
1491
+ switch (local) {
1492
+ case "documentation":
1493
+ if (this.documentationSeen)
1494
+ return null;
1495
+ this.documentationSeen = true;
1496
+ return new TextFrame(this, SLOT_DOCUMENTATION, attrs);
1497
+ case "error":
1498
+ return new BaseOnlyFrame(attrs, (a, base) => parseError(name, a, base), this.errors);
1499
+ case "escalation":
1500
+ return new BaseOnlyFrame(attrs, (a, base) => parseEscalation(name, a, base), this.escalations);
1501
+ case "message":
1502
+ return new BaseOnlyFrame(attrs, (a, base) => parseMessage(name, a, base), this.messages);
1503
+ case "signal":
1504
+ return new BaseOnlyFrame(attrs, (a, base) => parseSignal(name, a, base), this.signals);
1505
+ case "category":
1506
+ return new CategoryFrame(attrs, this.categories);
1507
+ case "collaboration":
1508
+ return new CollaborationFrame(name, attrs, this.collaborations);
1509
+ case "process":
1510
+ return new ProcessFrame(name, attrs, this.processes);
1511
+ case "BPMNDiagram":
1512
+ return new DiagramFrame(name, attrs, this.diagrams);
1513
+ default:
1514
+ return captureUnknown(this.unknownChildren, name, attrs);
670
1515
  }
671
- else if (key === "xmlns") {
672
- namespaces[""] = value;
1516
+ }
1517
+ setText(slot, text) {
1518
+ if (slot === SLOT_DOCUMENTATION)
1519
+ this.documentation = text;
1520
+ }
1521
+ finish() {
1522
+ const attrs = this.attrs;
1523
+ const namespaces = {};
1524
+ const unknownAttributes = {};
1525
+ for (const key in attrs) {
1526
+ const value = attrs[key];
1527
+ if (key.startsWith("xmlns:")) {
1528
+ namespaces[key.slice(6)] = value;
1529
+ }
1530
+ else if (key === "xmlns") {
1531
+ namespaces[""] = value;
1532
+ }
1533
+ else if (KNOWN_ATTRS.has(key)) {
1534
+ }
1535
+ else {
1536
+ unknownAttributes[key] = value;
1537
+ }
673
1538
  }
674
- else if (KNOWN_ATTRS.has(key)) {
1539
+ this.sink.result = {
1540
+ id: requiredAttr(attrs, "id", this.name),
1541
+ targetNamespace: requiredAttr(attrs, "targetNamespace", this.name),
1542
+ exporter: attr(attrs, "exporter"),
1543
+ exporterVersion: attr(attrs, "exporterVersion"),
1544
+ namespaces,
1545
+ unknownAttributes,
1546
+ ...(this.documentation !== undefined ? { documentation: this.documentation } : {}),
1547
+ ...(this.categories.length > 0 ? { categories: this.categories } : {}),
1548
+ errors: this.errors,
1549
+ escalations: this.escalations,
1550
+ messages: this.messages,
1551
+ signals: this.signals,
1552
+ collaborations: this.collaborations,
1553
+ processes: this.processes,
1554
+ diagrams: this.diagrams,
1555
+ ...(this.unknownChildren.length > 0 ? { unknownChildren: this.unknownChildren } : {}),
1556
+ };
1557
+ }
1558
+ }
1559
+ /** Routes scanner events to the frame stack. */
1560
+ class BpmnSink {
1561
+ result;
1562
+ /** Set when the root element is not <definitions>; reported once scanning is done. */
1563
+ rootName;
1564
+ stack = [];
1565
+ start(name, local, attrs) {
1566
+ const top = this.stack[this.stack.length - 1];
1567
+ let next;
1568
+ if (top === undefined) {
1569
+ if (local !== "definitions") {
1570
+ this.rootName = name;
1571
+ return Visit.Skip;
1572
+ }
1573
+ next = new DefinitionsFrame(name, attrs, this);
675
1574
  }
676
1575
  else {
677
- unknownAttributes[key] = value;
1576
+ next = top.child(local, name, attrs);
678
1577
  }
1578
+ if (next === null)
1579
+ return Visit.Skip;
1580
+ this.stack.push(next);
1581
+ return next.wantsText ? Visit.All : Visit.ElementsOnly;
679
1582
  }
680
- const errors = [];
681
- const escalations = [];
682
- const messages = [];
683
- const signals = [];
684
- const collaborations = [];
685
- const processes = [];
686
- const diagrams = [];
687
- for (const child of root.children) {
688
- const ln = localName(child.name);
689
- if (ln === "error")
690
- errors.push(parseError(child));
691
- else if (ln === "escalation")
692
- escalations.push(parseEscalation(child));
693
- else if (ln === "message")
694
- messages.push(parseMessage(child));
695
- else if (ln === "signal")
696
- signals.push(parseSignal(child));
697
- else if (ln === "collaboration")
698
- collaborations.push(parseCollaboration(child));
699
- else if (ln === "process")
700
- processes.push(parseProcess(child));
701
- else if (ln === "BPMNDiagram")
702
- diagrams.push(parseDiagram(child));
1583
+ text(text) {
1584
+ ;
1585
+ this.stack[this.stack.length - 1].text(text);
703
1586
  }
704
- return {
705
- id: requiredAttr(root, "id"),
706
- targetNamespace: requiredAttr(root, "targetNamespace"),
707
- exporter: attr(root, "exporter"),
708
- exporterVersion: attr(root, "exporterVersion"),
709
- namespaces,
710
- unknownAttributes,
711
- errors,
712
- escalations,
713
- messages,
714
- signals,
715
- collaborations,
716
- processes,
717
- diagrams,
718
- };
1587
+ end() {
1588
+ ;
1589
+ this.stack.pop().finish();
1590
+ }
1591
+ }
1592
+ // ---------------------------------------------------------------------------
1593
+ // Public API
1594
+ // ---------------------------------------------------------------------------
1595
+ /** Parse a BPMN XML string into a typed BpmnDefinitions model. */
1596
+ export function parseBpmn(xml) {
1597
+ const sink = new BpmnSink();
1598
+ if (!scanXml(xml, sink))
1599
+ throw new Error("Failed to parse XML: no root element found");
1600
+ if (sink.rootName !== undefined) {
1601
+ throw new Error(`Expected <definitions> root element, got <${sink.rootName}>`);
1602
+ }
1603
+ return sink.result;
719
1604
  }
720
1605
  //# sourceMappingURL=bpmn-parser.js.map