@formicoidea/labre-framework-bpmn 0.39.3 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions.d.ts +7 -7
- package/dist/actions.js +43 -16
- package/dist/consts.d.ts +42 -12
- package/dist/consts.js +43 -12
- package/dist/export.d.ts +18 -1
- package/dist/export.js +102 -63
- package/dist/import.d.ts +65 -13
- package/dist/import.js +205 -68
- package/dist/interchange.js +13 -1
- package/dist/templates/index.d.ts +55 -0
- package/dist/templates/index.js +143 -56
- package/dist/toolbar/icons.js +4 -3
- package/dist/translations.js +65 -3
- package/package.json +2 -2
package/dist/import.d.ts
CHANGED
|
@@ -1,22 +1,34 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type InterchangeImportContext, type InterchangeImportResult } from '@formicoidea/labre-core/blocks/surface';
|
|
2
2
|
import type { BpmnNodeKind } from '@formicoidea/labre-core/model';
|
|
3
3
|
import { BPMN_SCOPE } from './export.js';
|
|
4
4
|
/**
|
|
5
|
-
* The remarks whose wording is FIXED, as `[key, English]` pairs
|
|
5
|
+
* The remarks whose wording is FIXED, as `[key, English]` pairs — one entry
|
|
6
|
+
* per SHAPE of sentence, never one per exact string: a remark that names
|
|
7
|
+
* something out of the file (`<boundaryEvent>`, an id, a count of lanes)
|
|
8
|
+
* carries `{{name}}` / `{{count}}` holes in the fallback here, filled at the
|
|
9
|
+
* call site by `messageParams` (`InterchangeNote.messageParams`) — never by
|
|
10
|
+
* minting a new key for every value the file happens to carry.
|
|
6
11
|
*
|
|
7
12
|
* A reader is a pure function of text (`docs/adr/0012`, P3) and has no `std`,
|
|
8
|
-
* so it cannot ask the host's catalogue for anything: it declares the key
|
|
9
|
-
* the
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
+
* so it cannot ask the host's catalogue for anything: it declares the key
|
|
14
|
+
* (and, where the sentence has a hole, the params) on the note, and
|
|
15
|
+
* `reportInterchangeImport` resolves both when it draws the report
|
|
16
|
+
* (`InterchangeNote.messageKey` / `.messageParams`). The English string stays
|
|
17
|
+
* here and stays the fallback, so the console table and a playground with no
|
|
18
|
+
* catalogue read exactly what they read before: `translateKey` fills a
|
|
19
|
+
* `{{…}}` hole from `messageParams` even with no `TranslationProvider`
|
|
20
|
+
* registered (`fillPlaceholders`), so the ENGLISH sentence a call site builds
|
|
21
|
+
* is unchanged by giving it a key.
|
|
13
22
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
23
|
+
* Plural wording is the host's: where a count can be 0, 1 or many, the
|
|
24
|
+
* fallback stays grammatically neutral (`'{{count}} lane(s)…'`) rather than
|
|
25
|
+
* picking English's own singular/plural — the same rule
|
|
26
|
+
* `interchange-import.ts`'s own count labels follow.
|
|
27
|
+
*
|
|
28
|
+
* `BPMN_QUARANTINE_REASON` (declared further down, with its own key pairing)
|
|
29
|
+
* is the one exception: its plain string is ALSO written verbatim into
|
|
30
|
+
* `ForeignInterchange.quarantined[].reason` (data, never translated), so it
|
|
31
|
+
* cannot become a `[key, english]` pair without breaking that contract.
|
|
20
32
|
*
|
|
21
33
|
* They are contributed to the manifest by `./translations.ts`, with the
|
|
22
34
|
* framework, because they ship in the BPMN bundle rather than in core.
|
|
@@ -25,6 +37,32 @@ export declare const BPMN_IMPORT_REMARKS: {
|
|
|
25
37
|
readonly inventedPool: readonly ["com.labre.bpmn.import.remark.invented-pool", "This file names no participant, so its process was drawn in a pool of Labre's own. The pool is not the file's: exporting writes the process back without one."];
|
|
26
38
|
readonly laneGap: readonly ["com.labre.bpmn.import.remark.lane-gap", "The lanes of this pool are drawn with a gap or an overlap between them. Labre lays its bands end to end, so their heights were kept in proportion and the space between them was closed."];
|
|
27
39
|
readonly mustUnderstand: readonly ["com.labre.bpmn.import.remark.must-understand", "The file declares an extension that it says MUST be understood to read the model correctly. Labre does not understand it: the import went ahead, and this reading of the process may be wrong."];
|
|
40
|
+
readonly duplicateId: readonly ["com.labre.bpmn.import.remark.duplicate-id", "Two elements in this file share the id \"{{id}}\", which BPMN requires to be unique across a document. Both were imported; the second will be written back under an id Labre mints."];
|
|
41
|
+
readonly carriedChild: readonly ["com.labre.bpmn.import.remark.carried-child", "<{{tag}}> has no Labre artefact, so it is kept verbatim on the nearest element that has one. It is not drawn, and no validation rule sees it."];
|
|
42
|
+
readonly inventedPoolLayout: readonly ["com.labre.bpmn.import.remark.invented-pool-layout", "The participant \"{{name}}\" arrived with no diagram, so Labre placed its pool beside the drawing."];
|
|
43
|
+
readonly inventedLaneLayout: readonly ["com.labre.bpmn.import.remark.invented-lane-layout", "{{count}} lane(s) arrived with no diagram, so Labre split the pool into equal bands. The proportions are Labre's and not the file's."];
|
|
44
|
+
readonly undrawnTrigger: readonly ["com.labre.bpmn.import.remark.undrawn-trigger", "<{{tag}}> names its trigger by reference to \"{{ref}}\", which Labre does not draw. The event was kept whole rather than drawn as a plain one."];
|
|
45
|
+
readonly inventedNodeLayout: readonly ["com.labre.bpmn.import.remark.invented-node-layout", "<{{tag}}> arrived with no diagram, so Labre placed it beside the drawing. Its position is Labre's and not the file's."];
|
|
46
|
+
readonly singleEndedFlow: readonly ["com.labre.bpmn.import.remark.single-ended-flow", "<{{tag}}> names only one of its two ends, so there is no arrow to draw between them. It was left out."];
|
|
47
|
+
readonly danglingFlowEnd: readonly ["com.labre.bpmn.import.remark.dangling-flow-end", "<{{tag}}> runs to {{ends}}, not drawn on this canvas. The flow is kept whole beside them rather than drawn with a loose end."];
|
|
48
|
+
readonly undeclaredShape: readonly ["com.labre.bpmn.import.remark.undeclared-shape", "The diagram draws \"{{id}}\", which the file does not declare. The shape is kept, and nothing is drawn for it."];
|
|
49
|
+
readonly noProcessResidue: readonly ["com.labre.bpmn.import.remark.no-process-residue", "This file declares {{count}} root element(s) and no process to draw, so there was no artefact for them to be kept on. Nothing was imported."];
|
|
50
|
+
readonly laneDrift: readonly ["com.labre.bpmn.import.remark.lane-drift", "The file lists this artefact in the lane \"{{listedLane}}\" and draws it in \"{{drawnLane}}\". Labre reads the drawing: a lane holds what is drawn inside it."];
|
|
51
|
+
readonly inventedRouting: readonly ["com.labre.bpmn.import.remark.invented-routing", "{{count}} flow(s) carry an explicit routing in the file. Labre routes a flow between its two ends and re-routes it whenever they move, so their bend points are not kept."];
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* The reader's own refusals, as `[key, English]` pairs — thrown via
|
|
55
|
+
* {@link InterchangeImportError}, never returned, because none of these say
|
|
56
|
+
* "this is a file I can partly read" (see `parseDefinitions` and the
|
|
57
|
+
* choreography refusal, below). Same shape and the same reason as
|
|
58
|
+
* {@link BPMN_IMPORT_REMARKS}: one entry per shape of refusal, `{{name}}`
|
|
59
|
+
* holes filled by `InterchangeImportError.messageParams`.
|
|
60
|
+
*/
|
|
61
|
+
export declare const BPMN_IMPORT_ERRORS: {
|
|
62
|
+
readonly malformedXml: readonly ["com.labre.bpmn.import.error.malformed-xml", "This file is not well-formed XML, so no BPMN can be read out of it: {{detail}}"];
|
|
63
|
+
readonly notDefinitions: readonly ["com.labre.bpmn.import.error.not-definitions", "A BPMN file opens on <definitions>; this one opens on <{{tag}}>."];
|
|
64
|
+
readonly wrongNamespace: readonly ["com.labre.bpmn.import.error.wrong-namespace", "This <definitions> is in \"{{namespace}}\", not in BPMN 2.0's (\"{{bpmnNamespace}}\"). A DMN decision model and a BPMN process open on the same element name and are not the same file."];
|
|
65
|
+
readonly declinedRootKind: readonly ["com.labre.bpmn.import.error.declined-root-kind", "This file is a BPMN {{kind}}, which Labre does not draw. Only a process or a collaboration can be imported."];
|
|
28
66
|
};
|
|
29
67
|
/**
|
|
30
68
|
* `startEvent` + `messageEventDefinition` → `startEventMessage`, and the
|
|
@@ -60,6 +98,20 @@ export declare const BPMN_QUARANTINE_REASON: {
|
|
|
60
98
|
readonly nestedLanes: string;
|
|
61
99
|
readonly imported: string;
|
|
62
100
|
};
|
|
101
|
+
/**
|
|
102
|
+
* {@link BPMN_QUARANTINE_REASON}'s own i18n keys, one per reason.
|
|
103
|
+
*
|
|
104
|
+
* A SEPARATE table rather than folding the key into each reason string
|
|
105
|
+
* (`BPMN_IMPORT_REMARKS`'s `[key, english]` shape): `BPMN_QUARANTINE_REASON`'s
|
|
106
|
+
* plain strings are ALSO written verbatim into
|
|
107
|
+
* `ForeignInterchange.quarantined[].reason` — a DATA field the reader hands
|
|
108
|
+
* the writer, never re-emitted, never shown, and pinned in
|
|
109
|
+
* `import.unit.spec.ts` as the literal English text — so it cannot become a
|
|
110
|
+
* tuple without breaking that contract. Only the REPORT note (`messageKey`
|
|
111
|
+
* below) is resolved through the host's catalogue; the carried `reason` data
|
|
112
|
+
* stays English, exactly as it always has.
|
|
113
|
+
*/
|
|
114
|
+
export declare const BPMN_QUARANTINE_REASON_KEY: Record<keyof typeof BPMN_QUARANTINE_REASON, string>;
|
|
63
115
|
/**
|
|
64
116
|
* Read a BPMN 2.0 interchange document as element props plus a report.
|
|
65
117
|
*
|
package/dist/import.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { InterchangeImportError, } from '@formicoidea/labre-core/blocks/surface';
|
|
1
2
|
import { ConnectorMode, PointStyle, StrokeStyle } from '@formicoidea/labre-core/model';
|
|
2
3
|
import { Bound } from '@formicoidea/labre-core/global/gfx';
|
|
3
4
|
import { ASSOCIATION_STROKE, ASSOCIATION_WIDTH, MESSAGE_STROKE, MESSAGE_WIDTH, NODE_SIZE, POOL_BAND_WIDTH, POOL_REF_HEIGHT, POOL_REF_WIDTH, SEQUENCE_STROKE, SEQUENCE_WIDTH, } from './consts.js';
|
|
@@ -5,21 +6,33 @@ import { BPMN_FORMAT_ID, BPMN_NS, BPMN_OWN_DECLARATIONS, BPMN_SCOPE, BPMN_XML_OF
|
|
|
5
6
|
import { bpmnNodeProps } from './presets.js';
|
|
6
7
|
import { BPMN_ROLE } from './roles.js';
|
|
7
8
|
/**
|
|
8
|
-
* The remarks whose wording is FIXED, as `[key, English]` pairs
|
|
9
|
+
* The remarks whose wording is FIXED, as `[key, English]` pairs — one entry
|
|
10
|
+
* per SHAPE of sentence, never one per exact string: a remark that names
|
|
11
|
+
* something out of the file (`<boundaryEvent>`, an id, a count of lanes)
|
|
12
|
+
* carries `{{name}}` / `{{count}}` holes in the fallback here, filled at the
|
|
13
|
+
* call site by `messageParams` (`InterchangeNote.messageParams`) — never by
|
|
14
|
+
* minting a new key for every value the file happens to carry.
|
|
9
15
|
*
|
|
10
16
|
* A reader is a pure function of text (`docs/adr/0012`, P3) and has no `std`,
|
|
11
|
-
* so it cannot ask the host's catalogue for anything: it declares the key
|
|
12
|
-
* the
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
17
|
+
* so it cannot ask the host's catalogue for anything: it declares the key
|
|
18
|
+
* (and, where the sentence has a hole, the params) on the note, and
|
|
19
|
+
* `reportInterchangeImport` resolves both when it draws the report
|
|
20
|
+
* (`InterchangeNote.messageKey` / `.messageParams`). The English string stays
|
|
21
|
+
* here and stays the fallback, so the console table and a playground with no
|
|
22
|
+
* catalogue read exactly what they read before: `translateKey` fills a
|
|
23
|
+
* `{{…}}` hole from `messageParams` even with no `TranslationProvider`
|
|
24
|
+
* registered (`fillPlaceholders`), so the ENGLISH sentence a call site builds
|
|
25
|
+
* is unchanged by giving it a key.
|
|
16
26
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
27
|
+
* Plural wording is the host's: where a count can be 0, 1 or many, the
|
|
28
|
+
* fallback stays grammatically neutral (`'{{count}} lane(s)…'`) rather than
|
|
29
|
+
* picking English's own singular/plural — the same rule
|
|
30
|
+
* `interchange-import.ts`'s own count labels follow.
|
|
31
|
+
*
|
|
32
|
+
* `BPMN_QUARANTINE_REASON` (declared further down, with its own key pairing)
|
|
33
|
+
* is the one exception: its plain string is ALSO written verbatim into
|
|
34
|
+
* `ForeignInterchange.quarantined[].reason` (data, never translated), so it
|
|
35
|
+
* cannot become a `[key, english]` pair without breaking that contract.
|
|
23
36
|
*
|
|
24
37
|
* They are contributed to the manifest by `./translations.ts`, with the
|
|
25
38
|
* framework, because they ship in the BPMN bundle rather than in core.
|
|
@@ -37,6 +50,80 @@ export const BPMN_IMPORT_REMARKS = {
|
|
|
37
50
|
'com.labre.bpmn.import.remark.must-understand',
|
|
38
51
|
'The file declares an extension that it says MUST be understood to read the model correctly. Labre does not understand it: the import went ahead, and this reading of the process may be wrong.',
|
|
39
52
|
],
|
|
53
|
+
duplicateId: [
|
|
54
|
+
'com.labre.bpmn.import.remark.duplicate-id',
|
|
55
|
+
'Two elements in this file share the id "{{id}}", which BPMN requires to be unique across a document. Both were imported; the second will be written back under an id Labre mints.',
|
|
56
|
+
],
|
|
57
|
+
carriedChild: [
|
|
58
|
+
'com.labre.bpmn.import.remark.carried-child',
|
|
59
|
+
'<{{tag}}> has no Labre artefact, so it is kept verbatim on the nearest element that has one. It is not drawn, and no validation rule sees it.',
|
|
60
|
+
],
|
|
61
|
+
inventedPoolLayout: [
|
|
62
|
+
'com.labre.bpmn.import.remark.invented-pool-layout',
|
|
63
|
+
'The participant "{{name}}" arrived with no diagram, so Labre placed its pool beside the drawing.',
|
|
64
|
+
],
|
|
65
|
+
inventedLaneLayout: [
|
|
66
|
+
'com.labre.bpmn.import.remark.invented-lane-layout',
|
|
67
|
+
"{{count}} lane(s) arrived with no diagram, so Labre split the pool into equal bands. The proportions are Labre's and not the file's.",
|
|
68
|
+
],
|
|
69
|
+
undrawnTrigger: [
|
|
70
|
+
'com.labre.bpmn.import.remark.undrawn-trigger',
|
|
71
|
+
'<{{tag}}> names its trigger by reference to "{{ref}}", which Labre does not draw. The event was kept whole rather than drawn as a plain one.',
|
|
72
|
+
],
|
|
73
|
+
inventedNodeLayout: [
|
|
74
|
+
'com.labre.bpmn.import.remark.invented-node-layout',
|
|
75
|
+
"<{{tag}}> arrived with no diagram, so Labre placed it beside the drawing. Its position is Labre's and not the file's.",
|
|
76
|
+
],
|
|
77
|
+
singleEndedFlow: [
|
|
78
|
+
'com.labre.bpmn.import.remark.single-ended-flow',
|
|
79
|
+
'<{{tag}}> names only one of its two ends, so there is no arrow to draw between them. It was left out.',
|
|
80
|
+
],
|
|
81
|
+
danglingFlowEnd: [
|
|
82
|
+
'com.labre.bpmn.import.remark.dangling-flow-end',
|
|
83
|
+
'<{{tag}}> runs to {{ends}}, not drawn on this canvas. The flow is kept whole beside them rather than drawn with a loose end.',
|
|
84
|
+
],
|
|
85
|
+
undeclaredShape: [
|
|
86
|
+
'com.labre.bpmn.import.remark.undeclared-shape',
|
|
87
|
+
'The diagram draws "{{id}}", which the file does not declare. The shape is kept, and nothing is drawn for it.',
|
|
88
|
+
],
|
|
89
|
+
noProcessResidue: [
|
|
90
|
+
'com.labre.bpmn.import.remark.no-process-residue',
|
|
91
|
+
'This file declares {{count}} root element(s) and no process to draw, so there was no artefact for them to be kept on. Nothing was imported.',
|
|
92
|
+
],
|
|
93
|
+
laneDrift: [
|
|
94
|
+
'com.labre.bpmn.import.remark.lane-drift',
|
|
95
|
+
'The file lists this artefact in the lane "{{listedLane}}" and draws it in "{{drawnLane}}". Labre reads the drawing: a lane holds what is drawn inside it.',
|
|
96
|
+
],
|
|
97
|
+
inventedRouting: [
|
|
98
|
+
'com.labre.bpmn.import.remark.invented-routing',
|
|
99
|
+
'{{count}} flow(s) carry an explicit routing in the file. Labre routes a flow between its two ends and re-routes it whenever they move, so their bend points are not kept.',
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* The reader's own refusals, as `[key, English]` pairs — thrown via
|
|
104
|
+
* {@link InterchangeImportError}, never returned, because none of these say
|
|
105
|
+
* "this is a file I can partly read" (see `parseDefinitions` and the
|
|
106
|
+
* choreography refusal, below). Same shape and the same reason as
|
|
107
|
+
* {@link BPMN_IMPORT_REMARKS}: one entry per shape of refusal, `{{name}}`
|
|
108
|
+
* holes filled by `InterchangeImportError.messageParams`.
|
|
109
|
+
*/
|
|
110
|
+
export const BPMN_IMPORT_ERRORS = {
|
|
111
|
+
malformedXml: [
|
|
112
|
+
'com.labre.bpmn.import.error.malformed-xml',
|
|
113
|
+
'This file is not well-formed XML, so no BPMN can be read out of it: {{detail}}',
|
|
114
|
+
],
|
|
115
|
+
notDefinitions: [
|
|
116
|
+
'com.labre.bpmn.import.error.not-definitions',
|
|
117
|
+
'A BPMN file opens on <definitions>; this one opens on <{{tag}}>.',
|
|
118
|
+
],
|
|
119
|
+
wrongNamespace: [
|
|
120
|
+
'com.labre.bpmn.import.error.wrong-namespace',
|
|
121
|
+
'This <definitions> is in "{{namespace}}", not in BPMN 2.0\'s ("{{bpmnNamespace}}"). A DMN decision model and a BPMN process open on the same element name and are not the same file.',
|
|
122
|
+
],
|
|
123
|
+
declinedRootKind: [
|
|
124
|
+
'com.labre.bpmn.import.error.declined-root-kind',
|
|
125
|
+
'This file is a BPMN {{kind}}, which Labre does not draw. Only a process or a collaboration can be imported.',
|
|
126
|
+
],
|
|
40
127
|
};
|
|
41
128
|
/**
|
|
42
129
|
* A BPMN 2.0 interchange document, read as a board — the inverse of `export.ts`
|
|
@@ -223,6 +310,25 @@ export const BPMN_QUARANTINE_REASON = {
|
|
|
223
310
|
'self-contained set), so writing this back would claim a resolution of ' +
|
|
224
311
|
'another document that never happened.',
|
|
225
312
|
};
|
|
313
|
+
/**
|
|
314
|
+
* {@link BPMN_QUARANTINE_REASON}'s own i18n keys, one per reason.
|
|
315
|
+
*
|
|
316
|
+
* A SEPARATE table rather than folding the key into each reason string
|
|
317
|
+
* (`BPMN_IMPORT_REMARKS`'s `[key, english]` shape): `BPMN_QUARANTINE_REASON`'s
|
|
318
|
+
* plain strings are ALSO written verbatim into
|
|
319
|
+
* `ForeignInterchange.quarantined[].reason` — a DATA field the reader hands
|
|
320
|
+
* the writer, never re-emitted, never shown, and pinned in
|
|
321
|
+
* `import.unit.spec.ts` as the literal English text — so it cannot become a
|
|
322
|
+
* tuple without breaking that contract. Only the REPORT note (`messageKey`
|
|
323
|
+
* below) is resolved through the host's catalogue; the carried `reason` data
|
|
324
|
+
* stays English, exactly as it always has.
|
|
325
|
+
*/
|
|
326
|
+
export const BPMN_QUARANTINE_REASON_KEY = {
|
|
327
|
+
colour: 'com.labre.bpmn.import.quarantine.colour',
|
|
328
|
+
expanded: 'com.labre.bpmn.import.quarantine.expanded',
|
|
329
|
+
nestedLanes: 'com.labre.bpmn.import.quarantine.nested-lanes',
|
|
330
|
+
imported: 'com.labre.bpmn.import.quarantine.imported',
|
|
331
|
+
};
|
|
226
332
|
/**
|
|
227
333
|
* The children of an activity that are NOT its body.
|
|
228
334
|
*
|
|
@@ -344,21 +450,32 @@ function parseDefinitions(source) {
|
|
|
344
450
|
const doc = new DOMParser().parseFromString(source, 'application/xml');
|
|
345
451
|
const error = doc.querySelector('parsererror');
|
|
346
452
|
if (error) {
|
|
347
|
-
|
|
348
|
-
|
|
453
|
+
const detail = (error.textContent ?? '').trim().slice(0, 200);
|
|
454
|
+
throw new InterchangeImportError(`This file is not well-formed XML, so no BPMN can be read out of it: ${detail}`, {
|
|
455
|
+
messageKey: BPMN_IMPORT_ERRORS.malformedXml[0],
|
|
456
|
+
messageParams: { detail },
|
|
457
|
+
});
|
|
349
458
|
}
|
|
350
459
|
const root = doc.documentElement;
|
|
351
460
|
if (!root || root.localName !== 'definitions') {
|
|
352
|
-
|
|
461
|
+
const tag = root?.localName ?? 'nothing';
|
|
462
|
+
throw new InterchangeImportError(`A BPMN file opens on <definitions>; this one opens on <${tag}>.`, {
|
|
463
|
+
messageKey: BPMN_IMPORT_ERRORS.notDefinitions[0],
|
|
464
|
+
messageParams: { tag },
|
|
465
|
+
});
|
|
353
466
|
}
|
|
354
467
|
// The NAMESPACE, not the element name — `<definitions>` is also the root of a
|
|
355
468
|
// DMN decision model, and of anything else built on the same OMG scaffolding.
|
|
356
469
|
// Without this a `.dmn` imports as an empty board, which is exactly the
|
|
357
470
|
// "three zeroes claiming an empty process" this reader refuses to return.
|
|
358
471
|
if (root.namespaceURI !== BPMN_NS.model) {
|
|
359
|
-
|
|
472
|
+
const namespace = root.namespaceURI ?? 'no namespace';
|
|
473
|
+
throw new InterchangeImportError(`This <definitions> is in "${namespace}", not ` +
|
|
360
474
|
`in BPMN 2.0's ("${BPMN_NS.model}"). A DMN decision model and a BPMN ` +
|
|
361
|
-
`process open on the same element name and are not the same file
|
|
475
|
+
`process open on the same element name and are not the same file.`, {
|
|
476
|
+
messageKey: BPMN_IMPORT_ERRORS.wrongNamespace[0],
|
|
477
|
+
messageParams: { namespace, bpmnNamespace: BPMN_NS.model },
|
|
478
|
+
});
|
|
362
479
|
}
|
|
363
480
|
return root;
|
|
364
481
|
}
|
|
@@ -612,8 +729,12 @@ export function importBpmnXml(source, context = {}) {
|
|
|
612
729
|
if (processes.length === 0 && collaborations.length === 0) {
|
|
613
730
|
const declined = roots.find(root => ['choreography', 'globalChoreographyTask', 'conversation'].includes(root.localName));
|
|
614
731
|
if (declined) {
|
|
615
|
-
|
|
616
|
-
|
|
732
|
+
const kind = declined.localName;
|
|
733
|
+
throw new InterchangeImportError(`This file is a BPMN ${kind}, which Labre does not draw. ` +
|
|
734
|
+
`Only a process or a collaboration can be imported.`, {
|
|
735
|
+
messageKey: BPMN_IMPORT_ERRORS.declinedRootKind[0],
|
|
736
|
+
messageParams: { kind },
|
|
737
|
+
});
|
|
617
738
|
}
|
|
618
739
|
}
|
|
619
740
|
/** `categoryValue` id → the label a `group` pointing at it wears. */
|
|
@@ -637,9 +758,9 @@ export function importBpmnXml(source, context = {}) {
|
|
|
637
758
|
kind: 'substituted-id',
|
|
638
759
|
sourceId,
|
|
639
760
|
element,
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
761
|
+
messageKey: BPMN_IMPORT_REMARKS.duplicateId[0],
|
|
762
|
+
message: `Two elements in this file share the id "${sourceId}", which BPMN requires to be unique across a document. Both were imported; the second will be written back under an id Labre mints.`,
|
|
763
|
+
messageParams: { id: sourceId },
|
|
643
764
|
});
|
|
644
765
|
}
|
|
645
766
|
seenSourceIds.add(sourceId);
|
|
@@ -671,9 +792,9 @@ export function importBpmnXml(source, context = {}) {
|
|
|
671
792
|
kind: 'carried',
|
|
672
793
|
element: child.nodeName,
|
|
673
794
|
sourceId,
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
795
|
+
messageKey: BPMN_IMPORT_REMARKS.carriedChild[0],
|
|
796
|
+
message: `<${child.nodeName}> has no Labre artefact, so it is kept verbatim on the nearest element that has one. It is not drawn, and no validation rule sees it.`,
|
|
797
|
+
messageParams: { tag: child.nodeName },
|
|
677
798
|
});
|
|
678
799
|
};
|
|
679
800
|
/** One diagram fragment, kept under the scope of what it DRAWS. */
|
|
@@ -684,13 +805,23 @@ export function importBpmnXml(source, context = {}) {
|
|
|
684
805
|
};
|
|
685
806
|
carried++;
|
|
686
807
|
};
|
|
687
|
-
const quarantine = (payload, fragment, reason, entry
|
|
808
|
+
const quarantine = (payload, fragment, reason, entry,
|
|
809
|
+
// The reason's OWN key ({@link BPMN_QUARANTINE_REASON_KEY}) — separate
|
|
810
|
+
// from `reason` because that string is also written into
|
|
811
|
+
// `payload.quarantined[].reason` (data, never translated); only the
|
|
812
|
+
// REPORT note below carries the key.
|
|
813
|
+
reasonKey) => {
|
|
688
814
|
payload.quarantined = [
|
|
689
815
|
...(payload.quarantined ?? []),
|
|
690
816
|
{ fragment, reason },
|
|
691
817
|
];
|
|
692
818
|
quarantined++;
|
|
693
|
-
note({
|
|
819
|
+
note({
|
|
820
|
+
kind: 'quarantined',
|
|
821
|
+
...entry,
|
|
822
|
+
message: reason,
|
|
823
|
+
...(reasonKey ? { messageKey: reasonKey } : {}),
|
|
824
|
+
});
|
|
694
825
|
};
|
|
695
826
|
/** Every attribute the reader does not model — colours quarantined (D5). */
|
|
696
827
|
const sortAttributes = (element, payload, scope, sourceId, understood = READ_ATTRS[element.localName] ??
|
|
@@ -701,7 +832,7 @@ export function importBpmnXml(source, context = {}) {
|
|
|
701
832
|
if (understood.includes(attr.name))
|
|
702
833
|
continue;
|
|
703
834
|
if (isColourAttr(attr)) {
|
|
704
|
-
quarantine(payload, `${attr.name}="${escapeAttr(attr.value)}"`, BPMN_QUARANTINE_REASON.colour, { sourceId, element: attr.name });
|
|
835
|
+
quarantine(payload, `${attr.name}="${escapeAttr(attr.value)}"`, BPMN_QUARANTINE_REASON.colour, { sourceId, element: attr.name }, BPMN_QUARANTINE_REASON_KEY.colour);
|
|
705
836
|
continue;
|
|
706
837
|
}
|
|
707
838
|
carryAttr(payload, scope, attr.name, attr.value);
|
|
@@ -792,12 +923,14 @@ export function importBpmnXml(source, context = {}) {
|
|
|
792
923
|
if (process)
|
|
793
924
|
poolOfProcess.set(process, draft);
|
|
794
925
|
if (!bounds) {
|
|
926
|
+
const name = draft.props.name || 'unnamed';
|
|
795
927
|
note({
|
|
796
928
|
kind: 'invented-layout',
|
|
797
929
|
sourceId,
|
|
798
930
|
element: 'participant',
|
|
799
|
-
|
|
800
|
-
|
|
931
|
+
messageKey: BPMN_IMPORT_REMARKS.inventedPoolLayout[0],
|
|
932
|
+
message: `The participant "${name}" arrived with no diagram, so Labre placed its pool beside the drawing.`,
|
|
933
|
+
messageParams: { name },
|
|
801
934
|
});
|
|
802
935
|
}
|
|
803
936
|
}
|
|
@@ -887,7 +1020,7 @@ export function importBpmnXml(source, context = {}) {
|
|
|
887
1020
|
const name = attrOf(lane, 'name') ?? '';
|
|
888
1021
|
const nested = modelChild(lane, 'childLaneSet');
|
|
889
1022
|
if (nested) {
|
|
890
|
-
quarantine(pool.payload, fragmentOf(nested), BPMN_QUARANTINE_REASON.nestedLanes, { sourceId, element: 'childLaneSet' });
|
|
1023
|
+
quarantine(pool.payload, fragmentOf(nested), BPMN_QUARANTINE_REASON.nestedLanes, { sourceId, element: 'childLaneSet' }, BPMN_QUARANTINE_REASON_KEY.nestedLanes);
|
|
891
1024
|
// A lane holding a child set is not a band Labre paints — its LEAVES
|
|
892
1025
|
// are — so the shape drawing it describes a subdivision the flat pool
|
|
893
1026
|
// does not have. Accounted for by the quarantine, and kept out of the
|
|
@@ -959,9 +1092,9 @@ export function importBpmnXml(source, context = {}) {
|
|
|
959
1092
|
kind: 'invented-layout',
|
|
960
1093
|
sourceId: attrOf(laneSet, 'id'),
|
|
961
1094
|
element: 'laneSet',
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1095
|
+
messageKey: BPMN_IMPORT_REMARKS.inventedLaneLayout[0],
|
|
1096
|
+
message: `${bands.length} lane(s) arrived with no diagram, so Labre split the pool into equal bands. The proportions are Labre's and not the file's.`,
|
|
1097
|
+
messageParams: { count: bands.length },
|
|
965
1098
|
});
|
|
966
1099
|
}
|
|
967
1100
|
// Bands that do not tile the pool are still only WEIGHTS here — Labre lays
|
|
@@ -1035,13 +1168,14 @@ export function importBpmnXml(source, context = {}) {
|
|
|
1035
1168
|
// whole into the carried branch below rather than onto the canvas claiming
|
|
1036
1169
|
// something the file did not say.
|
|
1037
1170
|
if (kind === undefined && triggerRef !== undefined) {
|
|
1171
|
+
const ref = triggerRef.textContent?.trim() ?? '';
|
|
1038
1172
|
note({
|
|
1039
1173
|
kind: 'warning',
|
|
1040
1174
|
sourceId,
|
|
1041
1175
|
element: local,
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1176
|
+
messageKey: BPMN_IMPORT_REMARKS.undrawnTrigger[0],
|
|
1177
|
+
message: `<${local}> names its trigger by reference to "${ref}", which Labre does not draw. The event was kept whole rather than drawn as a plain one.`,
|
|
1178
|
+
messageParams: { tag: local, ref },
|
|
1045
1179
|
});
|
|
1046
1180
|
}
|
|
1047
1181
|
if (kind === undefined) {
|
|
@@ -1100,7 +1234,7 @@ export function importBpmnXml(source, context = {}) {
|
|
|
1100
1234
|
continue;
|
|
1101
1235
|
}
|
|
1102
1236
|
if (expanded && isModel(child) && !NOT_A_BODY.has(child.localName)) {
|
|
1103
|
-
quarantine(payload, fragmentOf(child), BPMN_QUARANTINE_REASON.expanded, { sourceId, element: child.nodeName });
|
|
1237
|
+
quarantine(payload, fragmentOf(child), BPMN_QUARANTINE_REASON.expanded, { sourceId, element: child.nodeName }, BPMN_QUARANTINE_REASON_KEY.expanded);
|
|
1104
1238
|
// The body's diagram goes with the body, all the way down. A shape left
|
|
1105
1239
|
// behind here is an ORPHAN — nothing declares what it draws any more —
|
|
1106
1240
|
// so the residue sweep at the end of this reader would pick it up and
|
|
@@ -1152,8 +1286,9 @@ export function importBpmnXml(source, context = {}) {
|
|
|
1152
1286
|
kind: 'invented-layout',
|
|
1153
1287
|
sourceId,
|
|
1154
1288
|
element: local,
|
|
1155
|
-
|
|
1156
|
-
|
|
1289
|
+
messageKey: BPMN_IMPORT_REMARKS.inventedNodeLayout[0],
|
|
1290
|
+
message: `<${local}> arrived with no diagram, so Labre placed it beside the drawing. Its position is Labre's and not the file's.`,
|
|
1291
|
+
messageParams: { tag: local },
|
|
1157
1292
|
});
|
|
1158
1293
|
}
|
|
1159
1294
|
};
|
|
@@ -1188,8 +1323,9 @@ export function importBpmnXml(source, context = {}) {
|
|
|
1188
1323
|
kind: 'warning',
|
|
1189
1324
|
sourceId,
|
|
1190
1325
|
element: local,
|
|
1191
|
-
|
|
1192
|
-
|
|
1326
|
+
messageKey: BPMN_IMPORT_REMARKS.singleEndedFlow[0],
|
|
1327
|
+
message: `<${local}> names only one of its two ends, so there is no arrow to draw between them. It was left out.`,
|
|
1328
|
+
messageParams: { tag: local },
|
|
1193
1329
|
});
|
|
1194
1330
|
continue;
|
|
1195
1331
|
}
|
|
@@ -1208,16 +1344,17 @@ export function importBpmnXml(source, context = {}) {
|
|
|
1208
1344
|
if (edgeDi) {
|
|
1209
1345
|
carryDi(host.payload, sourceId ?? hostScope, fragmentOf(edgeDi.element));
|
|
1210
1346
|
}
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1347
|
+
{
|
|
1348
|
+
const ends = dangling.map(end => `"${end}"`).join(' and ');
|
|
1349
|
+
note({
|
|
1350
|
+
kind: 'warning',
|
|
1351
|
+
sourceId,
|
|
1352
|
+
element: local,
|
|
1353
|
+
messageKey: BPMN_IMPORT_REMARKS.danglingFlowEnd[0],
|
|
1354
|
+
message: `<${local}> runs to ${ends}, not drawn on this canvas. The flow is kept whole beside them rather than drawn with a loose end.`,
|
|
1355
|
+
messageParams: { tag: local, ends },
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1221
1358
|
continue;
|
|
1222
1359
|
}
|
|
1223
1360
|
claim(sourceId, local);
|
|
@@ -1340,7 +1477,7 @@ export function importBpmnXml(source, context = {}) {
|
|
|
1340
1477
|
// file, so writing an `<import>` back would claim a resolution we never
|
|
1341
1478
|
// made.
|
|
1342
1479
|
if (isModel(root) && root.localName === 'import') {
|
|
1343
|
-
quarantine(host.payload, fragmentOf(root), BPMN_QUARANTINE_REASON.imported, { element: 'import' });
|
|
1480
|
+
quarantine(host.payload, fragmentOf(root), BPMN_QUARANTINE_REASON.imported, { element: 'import' }, BPMN_QUARANTINE_REASON_KEY.imported);
|
|
1344
1481
|
continue;
|
|
1345
1482
|
}
|
|
1346
1483
|
// Not quarantine: the file telling us our reading of it may be wrong,
|
|
@@ -1376,8 +1513,9 @@ export function importBpmnXml(source, context = {}) {
|
|
|
1376
1513
|
kind: 'warning',
|
|
1377
1514
|
sourceId: target,
|
|
1378
1515
|
element: shape.localName,
|
|
1379
|
-
|
|
1380
|
-
|
|
1516
|
+
messageKey: BPMN_IMPORT_REMARKS.undeclaredShape[0],
|
|
1517
|
+
message: `The diagram draws "${target}", which the file does not declare. The shape is kept, and nothing is drawn for it.`,
|
|
1518
|
+
messageParams: { id: target },
|
|
1381
1519
|
});
|
|
1382
1520
|
}
|
|
1383
1521
|
}
|
|
@@ -1387,11 +1525,9 @@ export function importBpmnXml(source, context = {}) {
|
|
|
1387
1525
|
// process and no participant has none.
|
|
1388
1526
|
note({
|
|
1389
1527
|
kind: 'warning',
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
`${residue.length === 1 ? 'it' : 'them'} to be kept on. Nothing was ` +
|
|
1394
|
-
`imported.`,
|
|
1528
|
+
messageKey: BPMN_IMPORT_REMARKS.noProcessResidue[0],
|
|
1529
|
+
message: `This file declares ${residue.length} root element(s) and no process to draw, so there was no artefact for them to be kept on. Nothing was imported.`,
|
|
1530
|
+
messageParams: { count: residue.length },
|
|
1395
1531
|
});
|
|
1396
1532
|
}
|
|
1397
1533
|
/* ── Lane membership: checked against the drawing, never stored (D3) ─ */
|
|
@@ -1413,9 +1549,12 @@ export function importBpmnXml(source, context = {}) {
|
|
|
1413
1549
|
kind: 'warning',
|
|
1414
1550
|
sourceId: ref,
|
|
1415
1551
|
element: 'flowNodeRef',
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1552
|
+
messageKey: BPMN_IMPORT_REMARKS.laneDrift[0],
|
|
1553
|
+
message: `The file lists this artefact in the lane "${band.lane.name ?? ''}" and draws it in "${drawnIn.lane.name ?? ''}". Labre reads the drawing: a lane holds what is drawn inside it.`,
|
|
1554
|
+
messageParams: {
|
|
1555
|
+
listedLane: band.lane.name ?? '',
|
|
1556
|
+
drawnLane: drawnIn.lane.name ?? '',
|
|
1557
|
+
},
|
|
1419
1558
|
});
|
|
1420
1559
|
}
|
|
1421
1560
|
}
|
|
@@ -1425,11 +1564,9 @@ export function importBpmnXml(source, context = {}) {
|
|
|
1425
1564
|
if (explicitRoutes > 0) {
|
|
1426
1565
|
note({
|
|
1427
1566
|
kind: 'invented-layout',
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
`${explicitRoutes === 1 ? 'its bend points are' : 'their bend points are'} ` +
|
|
1432
|
-
`not kept.`,
|
|
1567
|
+
messageKey: BPMN_IMPORT_REMARKS.inventedRouting[0],
|
|
1568
|
+
message: `${explicitRoutes} flow(s) carry an explicit routing in the file. Labre routes a flow between its two ends and re-routes it whenever they move, so their bend points are not kept.`,
|
|
1569
|
+
messageParams: { count: explicitRoutes },
|
|
1433
1570
|
});
|
|
1434
1571
|
}
|
|
1435
1572
|
/* ── Out ───────────────────────────────────────────────────────────── */
|
package/dist/interchange.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { interchangeCapabilityId, parseSvgSketch, SVG_SKETCH_EXTENSION, SVG_SKETCH_FORMAT_ID, SVG_SKETCH_MIME, } from '@formicoidea/labre-core/blocks/surface';
|
|
2
2
|
import { BpmnNodeElementModel, BpmnPoolElementModel, ConnectorElementModel, } from '@formicoidea/labre-core/model';
|
|
3
|
+
import { fillPlaceholders } from '@formicoidea/labre-core/shared/services';
|
|
3
4
|
import { BPMN_FORMAT_ID, exportBpmnXmlWithWarnings, } from './export.js';
|
|
4
5
|
import { importBpmnXml } from './import.js';
|
|
5
6
|
/**
|
|
@@ -115,7 +116,18 @@ const runBpmnXmlExport = (elements, context) => {
|
|
|
115
116
|
text,
|
|
116
117
|
filename: `${name}${BPMN_XML_EXTENSION}`,
|
|
117
118
|
mime: BPMN_XML_MIME,
|
|
118
|
-
|
|
119
|
+
// `InterchangeExportResult.warnings` is `readonly string[]` — a seam this
|
|
120
|
+
// capability shares with every other format and does not own — so each
|
|
121
|
+
// KEYED warning (`BpmnExportWarning`, `export.ts`) is resolved to its
|
|
122
|
+
// English fallback here. This adapter is `std`-free like the exporter it
|
|
123
|
+
// wraps (P3), so it cannot ask a host's catalogue either; the translated
|
|
124
|
+
// command path is `exportBpmnXmlFile` (`actions.ts`), which reads the
|
|
125
|
+
// structured warnings directly instead of going through this capability.
|
|
126
|
+
...(warnings.length > 0
|
|
127
|
+
? {
|
|
128
|
+
warnings: warnings.map(w => fillPlaceholders(w.fallback, w.params)),
|
|
129
|
+
}
|
|
130
|
+
: {}),
|
|
119
131
|
};
|
|
120
132
|
};
|
|
121
133
|
/** `bpmn:bpmn:export` — the registry's first capability. */
|