@bpmnkit/core 0.1.1 → 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 (83) hide show
  1. package/README.md +32 -1
  2. package/dist/bpmn/agentic.d.ts +121 -0
  3. package/dist/bpmn/agentic.js +97 -0
  4. package/dist/bpmn/auto-layout.d.ts +5 -5
  5. package/dist/bpmn/auto-layout.js +592 -36
  6. package/dist/bpmn/bpmn-builder.d.ts +265 -3
  7. package/dist/bpmn/bpmn-builder.js +603 -197
  8. package/dist/bpmn/bpmn-model.d.ts +114 -0
  9. package/dist/bpmn/bpmn-parser.js +1414 -521
  10. package/dist/bpmn/bpmn-serializer.js +107 -19
  11. package/dist/bpmn/compact.d.ts +17 -2
  12. package/dist/bpmn/compact.js +3 -3
  13. package/dist/bpmn/full-operations.d.ts +89 -0
  14. package/dist/bpmn/full-operations.js +478 -0
  15. package/dist/bpmn/index.d.ts +19 -0
  16. package/dist/bpmn/index.js +21 -0
  17. package/dist/bpmn/optimize/agentic.d.ts +10 -0
  18. package/dist/bpmn/optimize/agentic.js +88 -0
  19. package/dist/bpmn/optimize/deploy.d.ts +16 -0
  20. package/dist/bpmn/optimize/deploy.js +143 -0
  21. package/dist/bpmn/optimize/feel-syntax.d.ts +12 -0
  22. package/dist/bpmn/optimize/feel-syntax.js +87 -0
  23. package/dist/bpmn/optimize/feel.js +7 -4
  24. package/dist/bpmn/optimize/flow.js +22 -2
  25. package/dist/bpmn/optimize/index.js +20 -9
  26. package/dist/bpmn/optimize/patterns.js +23 -16
  27. package/dist/bpmn/optimize/tasks.js +30 -7
  28. package/dist/bpmn/optimize/types.d.ts +10 -1
  29. package/dist/bpmn/optimize/utils.js +2 -4
  30. package/dist/bpmn/optimize/variable-flow.js +58 -67
  31. package/dist/bpmn/semantic-hash.d.ts +93 -0
  32. package/dist/bpmn/semantic-hash.js +155 -0
  33. package/dist/bpmn/sha256.d.ts +17 -0
  34. package/dist/bpmn/sha256.js +95 -0
  35. package/dist/bpmn/zeebe-extensions.d.ts +83 -0
  36. package/dist/bpmn/zeebe-extensions.js +117 -0
  37. package/dist/bpmn/zeebe-placement.d.ts +12 -0
  38. package/dist/bpmn/zeebe-placement.js +140 -0
  39. package/dist/errors.d.ts +40 -1
  40. package/dist/errors.js +41 -0
  41. package/dist/index.d.ts +16 -5
  42. package/dist/index.js +9 -3
  43. package/dist/layout/annotations.js +36 -1
  44. package/dist/layout/collaboration/alignment.d.ts +26 -0
  45. package/dist/layout/collaboration/alignment.js +66 -0
  46. package/dist/layout/collaboration/ordering.d.ts +21 -0
  47. package/dist/layout/collaboration/ordering.js +102 -0
  48. package/dist/layout/index.d.ts +1 -0
  49. package/dist/layout/layout-engine.d.ts +13 -3
  50. package/dist/layout/layout-engine.js +9 -4
  51. package/dist/layout/semantic/bands.d.ts +19 -0
  52. package/dist/layout/semantic/bands.js +324 -0
  53. package/dist/layout/semantic/graph.d.ts +37 -0
  54. package/dist/layout/semantic/graph.js +242 -0
  55. package/dist/layout/semantic/index.d.ts +13 -0
  56. package/dist/layout/semantic/index.js +181 -0
  57. package/dist/layout/semantic/place.d.ts +40 -0
  58. package/dist/layout/semantic/place.js +271 -0
  59. package/dist/layout/semantic/route.d.ts +14 -0
  60. package/dist/layout/semantic/route.js +514 -0
  61. package/dist/layout/types.d.ts +17 -0
  62. package/dist/node/index.d.ts +10 -0
  63. package/dist/node/index.js +9 -0
  64. package/dist/node/write.d.ts +81 -0
  65. package/dist/node/write.js +167 -0
  66. package/dist/plan/compile.d.ts +39 -0
  67. package/dist/plan/compile.js +380 -0
  68. package/dist/plan/extract.d.ts +31 -0
  69. package/dist/plan/extract.js +248 -0
  70. package/dist/plan/index.d.ts +6 -0
  71. package/dist/plan/index.js +5 -0
  72. package/dist/plan/merge.d.ts +13 -0
  73. package/dist/plan/merge.js +80 -0
  74. package/dist/plan/slug.d.ts +5 -0
  75. package/dist/plan/slug.js +22 -0
  76. package/dist/plan/types.d.ts +225 -0
  77. package/dist/plan/types.js +13 -0
  78. package/dist/types/id-generator.js +11 -3
  79. package/dist/xml/index.d.ts +3 -1
  80. package/dist/xml/index.js +2 -1
  81. package/dist/xml/xml-parser.d.ts +32 -0
  82. package/dist/xml/xml-parser.js +394 -143
  83. package/package.json +9 -2
@@ -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,444 +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
- return {
333
- ...base,
334
- type: "adHocSubProcess",
335
- loopCharacteristics: parseLoopCharacteristics(element),
336
- ...parseProcessContents(element),
337
- };
338
- case "subProcess":
339
- return {
340
- ...base,
341
- type: "subProcess",
342
- triggeredByEvent: attr(element, "triggeredByEvent") !== undefined
343
- ? attr(element, "triggeredByEvent") === "true"
344
- : undefined,
345
- loopCharacteristics: parseLoopCharacteristics(element),
346
- ...parseProcessContents(element),
347
- };
348
- case "eventSubProcess":
349
- return {
350
- ...base,
351
- type: "eventSubProcess",
352
- ...parseProcessContents(element),
353
- };
354
- case "transaction":
355
- return {
356
- ...base,
357
- type: "transaction",
358
- loopCharacteristics: parseLoopCharacteristics(element),
359
- ...parseProcessContents(element),
360
- };
361
- case "exclusiveGateway":
362
- return { ...base, type: "exclusiveGateway", default: attr(element, "default") };
363
- case "parallelGateway":
364
- return { ...base, type: "parallelGateway" };
365
- case "inclusiveGateway":
366
- return { ...base, type: "inclusiveGateway", default: attr(element, "default") };
367
- case "eventBasedGateway":
368
- return { ...base, type: "eventBasedGateway" };
369
- case "complexGateway":
370
- return { ...base, type: "complexGateway", default: attr(element, "default") };
371
- case "dataObject":
372
- return {
373
- ...base,
374
- type: "dataObject",
375
- ...(attr(element, "isCollection") === "true" ? { isCollection: true } : {}),
376
- };
377
- case "dataObjectReference":
378
- return {
379
- ...base,
380
- type: "dataObjectReference",
381
- dataObjectRef: attr(element, "dataObjectRef"),
382
- ...(attr(element, "isCollection") === "true" ? { isCollection: true } : {}),
383
- };
384
- case "dataStoreReference":
385
- return {
386
- ...base,
387
- type: "dataStoreReference",
388
- dataStoreRef: attr(element, "dataStoreRef"),
389
- };
390
- default:
391
- return undefined;
563
+ if (this.eventDefinitions !== null && EVENT_DEFINITION_TYPES.has(local)) {
564
+ return eventDefinitionFrame(local, attrs, this.eventDefinitions);
565
+ }
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);
392
572
  }
393
- }
394
- // ---------------------------------------------------------------------------
395
- // Sequence flows
396
- // ---------------------------------------------------------------------------
397
- function parseSequenceFlow(element) {
398
- const condEl = findChild(element, "conditionExpression");
399
- let conditionExpression;
400
- if (condEl) {
401
- conditionExpression = {
402
- text: condEl.text ?? "",
403
- 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),
404
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);
405
749
  }
406
- return {
407
- id: requiredAttr(element, "id"),
408
- name: attr(element, "name"),
409
- sourceRef: requiredAttr(element, "sourceRef"),
410
- targetRef: requiredAttr(element, "targetRef"),
411
- conditionExpression,
412
- extensionElements: parseExtensionElements(element),
413
- unknownAttributes: unknownAttrs(element),
414
- };
415
750
  }
416
751
  // ---------------------------------------------------------------------------
417
- // Annotations
752
+ // Event definitions
418
753
  // ---------------------------------------------------------------------------
419
- function parseTextAnnotation(element) {
420
- const textEl = findChild(element, "text");
421
- return {
422
- id: requiredAttr(element, "id"),
423
- text: textEl?.text,
424
- unknownAttributes: unknownAttrs(element),
425
- };
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
+ }
426
795
  }
427
- function parseAssociation(element) {
428
- return {
429
- id: requiredAttr(element, "id"),
430
- sourceRef: requiredAttr(element, "sourceRef"),
431
- targetRef: requiredAttr(element, "targetRef"),
432
- associationDirection: attr(element, "associationDirection"),
433
- unknownAttributes: unknownAttrs(element),
434
- };
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
+ }
435
848
  }
436
- function parseGroup(element) {
437
- return {
438
- id: requiredAttr(element, "id"),
439
- categoryValueRef: attr(element, "categoryValueRef"),
440
- unknownAttributes: unknownAttrs(element),
441
- };
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
+ }
442
880
  }
443
881
  // ---------------------------------------------------------------------------
444
- // Process contents (shared between process and adHocSubProcess)
882
+ // Multi-instance loop
445
883
  // ---------------------------------------------------------------------------
446
- function parseProcessContents(element) {
447
- const flowElements = [];
448
- const sequenceFlows = [];
449
- const textAnnotations = [];
450
- const associations = [];
451
- const groups = [];
452
- for (const child of element.children) {
453
- const ln = localName(child.name);
454
- if (FLOW_ELEMENT_TYPES.has(ln)) {
455
- const fe = parseFlowElement(child);
456
- if (fe)
457
- flowElements.push(fe);
458
- }
459
- else if (ln === "sequenceFlow") {
460
- sequenceFlows.push(parseSequenceFlow(child));
461
- }
462
- else if (ln === "textAnnotation") {
463
- textAnnotations.push(parseTextAnnotation(child));
464
- }
465
- else if (ln === "association") {
466
- associations.push(parseAssociation(child));
467
- }
468
- else if (ln === "group") {
469
- 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);
470
917
  }
471
918
  }
472
- 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
+ }
473
935
  }
474
936
  // ---------------------------------------------------------------------------
475
- // Lanes
937
+ // Sequence flows and annotations
476
938
  // ---------------------------------------------------------------------------
477
- function parseLane(element) {
478
- const flowNodeRefs = findChildren(element, "flowNodeRef")
479
- .map((c) => c.text?.trim())
480
- .filter((t) => !!t);
481
- const childLaneSetEl = findChild(element, "childLaneSet");
482
- return {
483
- id: requiredAttr(element, "id"),
484
- name: attr(element, "name"),
485
- flowNodeRefs,
486
- childLaneSet: childLaneSetEl ? parseLaneSet(childLaneSetEl) : undefined,
487
- unknownAttributes: unknownAttrs(element),
488
- };
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
+ }
489
986
  }
490
- function parseLaneSet(element) {
491
- return {
492
- id: attr(element, "id"),
493
- lanes: findChildren(element, "lane").map(parseLane),
494
- };
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
+ }
495
1019
  }
496
1020
  // ---------------------------------------------------------------------------
497
- // Process
1021
+ // Categories
498
1022
  // ---------------------------------------------------------------------------
499
- function parseProcess(element) {
500
- const laneSetEl = findChild(element, "laneSet");
501
- return {
502
- id: requiredAttr(element, "id"),
503
- name: attr(element, "name"),
504
- isExecutable: attr(element, "isExecutable") === "true" ? true : undefined,
505
- extensionElements: parseExtensionElements(element),
506
- unknownAttributes: unknownAttrs(element),
507
- laneSet: laneSetEl ? parseLaneSet(laneSetEl) : undefined,
508
- ...parseProcessContents(element),
509
- };
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
+ }
510
1057
  }
511
1058
  // ---------------------------------------------------------------------------
512
- // Collaboration
1059
+ // Data associations
513
1060
  // ---------------------------------------------------------------------------
514
- function parseParticipant(element) {
515
- return {
516
- id: requiredAttr(element, "id"),
517
- name: attr(element, "name"),
518
- processRef: attr(element, "processRef"),
519
- unknownAttributes: unknownAttrs(element),
520
- };
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
+ }
521
1103
  }
522
- function parseMessageFlow(element) {
523
- return {
524
- id: requiredAttr(element, "id"),
525
- name: attr(element, "name"),
526
- sourceRef: requiredAttr(element, "sourceRef"),
527
- targetRef: requiredAttr(element, "targetRef"),
528
- unknownAttributes: unknownAttrs(element),
529
- };
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
+ }
530
1124
  }
531
- function parseCollaboration(element) {
532
- return {
533
- id: requiredAttr(element, "id"),
534
- participants: findChildren(element, "participant").map(parseParticipant),
535
- messageFlows: findChildren(element, "messageFlow").map(parseMessageFlow),
536
- textAnnotations: findChildren(element, "textAnnotation").map(parseTextAnnotation),
537
- associations: findChildren(element, "association").map(parseAssociation),
538
- groups: findChildren(element, "group").map(parseGroup),
539
- extensionElements: parseExtensionElements(element),
540
- unknownAttributes: unknownAttrs(element),
541
- };
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
+ }
542
1172
  }
543
1173
  // ---------------------------------------------------------------------------
544
- // Root elements
1174
+ // Process
545
1175
  // ---------------------------------------------------------------------------
546
- function parseError(element) {
547
- return {
548
- id: requiredAttr(element, "id"),
549
- name: attr(element, "name"),
550
- errorCode: attr(element, "errorCode"),
551
- };
552
- }
553
- function parseEscalation(element) {
554
- return {
555
- id: requiredAttr(element, "id"),
556
- name: attr(element, "name"),
557
- escalationCode: attr(element, "escalationCode"),
558
- };
559
- }
560
- function parseMessage(element) {
561
- return {
562
- id: requiredAttr(element, "id"),
563
- name: attr(element, "name"),
564
- unknownAttributes: unknownAttrs(element),
565
- };
566
- }
567
- function parseSignal(element) {
568
- return {
569
- id: requiredAttr(element, "id"),
570
- name: attr(element, "name"),
571
- };
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
+ }
572
1237
  }
573
1238
  // ---------------------------------------------------------------------------
574
- // Diagram interchange
1239
+ // Collaboration
575
1240
  // ---------------------------------------------------------------------------
576
- function parseBounds(element) {
577
- return {
578
- x: Number(attr(element, "x") ?? "0"),
579
- y: Number(attr(element, "y") ?? "0"),
580
- width: Number(attr(element, "width") ?? "0"),
581
- height: Number(attr(element, "height") ?? "0"),
582
- };
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
+ }
583
1292
  }
584
- function parseLabel(element) {
585
- const labelEl = findChild(element, "BPMNLabel");
586
- if (!labelEl)
587
- return undefined;
588
- const boundsEl = findChild(labelEl, "Bounds");
589
- 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
+ }
590
1311
  }
591
- function parseDiShape(element) {
592
- const boundsEl = findChild(element, "Bounds");
593
- if (!boundsEl)
594
- throw new Error(`Missing <dc:Bounds> in shape "${attr(element, "id")}"`);
595
- return {
596
- id: requiredAttr(element, "id"),
597
- bpmnElement: requiredAttr(element, "bpmnElement"),
598
- isMarkerVisible: attr(element, "isMarkerVisible") !== undefined
599
- ? attr(element, "isMarkerVisible") === "true"
600
- : undefined,
601
- isExpanded: attr(element, "isExpanded") !== undefined
602
- ? attr(element, "isExpanded") === "true"
603
- : undefined,
604
- isHorizontal: attr(element, "isHorizontal") !== undefined
605
- ? attr(element, "isHorizontal") === "true"
606
- : undefined,
607
- bounds: parseBounds(boundsEl),
608
- label: parseLabel(element),
609
- unknownAttributes: unknownAttrs(element),
610
- };
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
+ }
611
1362
  }
612
- function parseDiEdge(element) {
613
- const waypoints = findChildren(element, "waypoint").map((w) => ({
614
- x: Number(attr(w, "x") ?? "0"),
615
- y: Number(attr(w, "y") ?? "0"),
616
- }));
617
- return {
618
- id: requiredAttr(element, "id"),
619
- bpmnElement: requiredAttr(element, "bpmnElement"),
620
- waypoints,
621
- label: parseLabel(element),
622
- unknownAttributes: unknownAttrs(element),
623
- };
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
+ }
624
1406
  }
625
- function parseDiagram(element) {
626
- const planeEl = findChild(element, "BPMNPlane");
627
- if (!planeEl)
628
- throw new Error("Missing <bpmndi:BPMNPlane> in diagram");
629
- const shapes = [];
630
- const edges = [];
631
- for (const child of planeEl.children) {
632
- const ln = localName(child.name);
633
- if (ln === "BPMNShape")
634
- shapes.push(parseDiShape(child));
635
- else if (ln === "BPMNEdge")
636
- 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
+ });
637
1464
  }
638
- return {
639
- id: requiredAttr(element, "id"),
640
- plane: {
641
- id: requiredAttr(planeEl, "id"),
642
- bpmnElement: requiredAttr(planeEl, "bpmnElement"),
643
- shapes,
644
- edges,
645
- },
646
- };
647
1465
  }
648
1466
  // ---------------------------------------------------------------------------
649
- // Public API
1467
+ // Definitions (document root)
650
1468
  // ---------------------------------------------------------------------------
651
- /** Parse a BPMN XML string into a typed BpmnDefinitions model. */
652
- export function parseBpmn(xml) {
653
- const root = parseXml(xml);
654
- if (localName(root.name) !== "definitions") {
655
- throw new Error(`Expected <definitions> root element, got <${root.name}>`);
656
- }
657
- const namespaces = {};
658
- const unknownAttributes = {};
659
- for (const [key, value] of Object.entries(root.attributes)) {
660
- if (key.startsWith("xmlns:")) {
661
- 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);
662
1515
  }
663
- else if (key === "xmlns") {
664
- 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
+ }
665
1538
  }
666
- 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);
667
1574
  }
668
1575
  else {
669
- unknownAttributes[key] = value;
1576
+ next = top.child(local, name, attrs);
670
1577
  }
1578
+ if (next === null)
1579
+ return Visit.Skip;
1580
+ this.stack.push(next);
1581
+ return next.wantsText ? Visit.All : Visit.ElementsOnly;
671
1582
  }
672
- const errors = [];
673
- const escalations = [];
674
- const messages = [];
675
- const signals = [];
676
- const collaborations = [];
677
- const processes = [];
678
- const diagrams = [];
679
- for (const child of root.children) {
680
- const ln = localName(child.name);
681
- if (ln === "error")
682
- errors.push(parseError(child));
683
- else if (ln === "escalation")
684
- escalations.push(parseEscalation(child));
685
- else if (ln === "message")
686
- messages.push(parseMessage(child));
687
- else if (ln === "signal")
688
- signals.push(parseSignal(child));
689
- else if (ln === "collaboration")
690
- collaborations.push(parseCollaboration(child));
691
- else if (ln === "process")
692
- processes.push(parseProcess(child));
693
- else if (ln === "BPMNDiagram")
694
- diagrams.push(parseDiagram(child));
1583
+ text(text) {
1584
+ ;
1585
+ this.stack[this.stack.length - 1].text(text);
695
1586
  }
696
- return {
697
- id: requiredAttr(root, "id"),
698
- targetNamespace: requiredAttr(root, "targetNamespace"),
699
- exporter: attr(root, "exporter"),
700
- exporterVersion: attr(root, "exporterVersion"),
701
- namespaces,
702
- unknownAttributes,
703
- errors,
704
- escalations,
705
- messages,
706
- signals,
707
- collaborations,
708
- processes,
709
- diagrams,
710
- };
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;
711
1604
  }
712
1605
  //# sourceMappingURL=bpmn-parser.js.map