state-machine-cat 10.1.9 → 10.1.10

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.
@@ -1,291 +0,0 @@
1
- /* eslint-disable no-invalid-this */
2
- /* eslint-disable security/detect-object-injection */
3
- import fastxml from "fast-xml-parser";
4
- import he from "he";
5
- import castArray from "lodash/castArray.js";
6
- import traverse from "traverse";
7
- import utl from "../../transform/utl.mjs";
8
- import parserHelpers from "../parser-helpers.mjs";
9
- import { normalizeMachine } from "./normalize-machine.mjs";
10
-
11
- const formatLabel = utl.formatLabel;
12
-
13
- function extractActions(pState, pActionType) {
14
- return castArray(pState[pActionType]).map((pAction) => ({
15
- type: pActionType === "onexit" ? "exit" : "entry",
16
- body: he.decode(pAction).trim(),
17
- }));
18
- }
19
-
20
- function extractActionsFromInvokes(pInvokeTriggers) {
21
- return castArray(pInvokeTriggers).map((pInvokeTrigger) => {
22
- const lId = he.decode(pInvokeTrigger.id || "").trim();
23
-
24
- return {
25
- type: "activity",
26
- body: lId || he.decode(pInvokeTrigger || "").trim(),
27
- };
28
- });
29
- }
30
-
31
- /**
32
- * @param {import("./scxml").INormalizedSCXMLState} pState
33
- * @returns {any[]}
34
- */
35
- function deriveActions(pState) {
36
- let lReturnValue = [];
37
-
38
- if (pState.onentry) {
39
- lReturnValue = lReturnValue.concat(extractActions(pState, "onentry"));
40
- }
41
- if (pState.invoke) {
42
- lReturnValue = lReturnValue.concat(
43
- extractActionsFromInvokes(pState.invoke)
44
- );
45
- }
46
- if (pState.onexit) {
47
- lReturnValue = lReturnValue.concat(extractActions(pState, "onexit"));
48
- }
49
- return lReturnValue;
50
- }
51
-
52
- /**
53
- * @param {import("../../..").StateType} pType
54
- * @param {import("./scxml").ISCXMLHistoryState} pState
55
- * @param {any} pState
56
- * @returns {import("../../..").StateType}
57
- */
58
- function deriveStateType(pType, pState) {
59
- return pType === "history" && pState.type === "deep" ? "deephistory" : pType;
60
- }
61
-
62
- /**
63
- * @param {import("../../../types/state-machine-cat").StateType} pType
64
- * @returns {(any) => import("../../..").IState}
65
- */
66
- function mapState(pType) {
67
- return (pState) => {
68
- /** @type {import("../../../types/state-machine-cat").IState} */
69
- const lReturnValue = {
70
- name: pState.id,
71
- type: deriveStateType(pType, pState),
72
- };
73
-
74
- if (parserHelpers.getStateType(pState.id) !== lReturnValue.type) {
75
- lReturnValue.typeExplicitlySet = true;
76
- }
77
- if (pState.onentry || pState.onexit || pState.invoke) {
78
- lReturnValue.actions = deriveActions(pState);
79
- }
80
- if (
81
- Object.keys(pState).some((pKey) =>
82
- ["initial", "state", "history", "parallel", "final"].includes(pKey)
83
- )
84
- ) {
85
- // recursion, so ...
86
- // eslint-disable-next-line no-use-before-define
87
- lReturnValue.statemachine = mapMachine(pState);
88
- }
89
- return lReturnValue;
90
- };
91
- }
92
-
93
- /**
94
- * @param {import("./scxml").ISCXMLTransition} pTransition
95
- * @returns {{event?: string; cond?: string; action?: string; type?: string;}}
96
- */
97
- function extractTransitionAttributesFromObject(pTransition) {
98
- const lReturnValue = {};
99
-
100
- if (pTransition.event) {
101
- // SCXML uses spaces to distinguish multiple events
102
- // the smcat ast uses linebreaks
103
- lReturnValue.event = pTransition.event.split(/\s+/).join("\n");
104
- }
105
- if (pTransition.cond) {
106
- lReturnValue.cond = pTransition.cond;
107
- }
108
- if (pTransition["#text"]) {
109
- lReturnValue.action = he.decode(pTransition["#text"]).trim();
110
- }
111
-
112
- if (pTransition.type) {
113
- lReturnValue.type = pTransition.type;
114
- }
115
-
116
- return lReturnValue;
117
- }
118
-
119
- /**
120
- * @param {import("./scxml").ISCXMLTransition} pTransition
121
- * @returns {{action?: string; label?: string;event?: string; cond?: string; type?: string}}
122
- */
123
- function extractTransitionAttributes(pTransition) {
124
- const lReturnValue = {};
125
-
126
- if (typeof pTransition === "string") {
127
- lReturnValue.action = he.decode(pTransition).trim();
128
- } else {
129
- Object.assign(
130
- lReturnValue,
131
- extractTransitionAttributesFromObject(pTransition)
132
- );
133
- }
134
-
135
- const lLabel = formatLabel(
136
- lReturnValue.event,
137
- lReturnValue.cond,
138
- lReturnValue.action
139
- );
140
-
141
- if (lLabel) {
142
- lReturnValue.label = lLabel;
143
- }
144
-
145
- return lReturnValue;
146
- }
147
-
148
- /**
149
- * @param {import("./scxml").INormalizedSCXMLState} pState
150
- */
151
- function reduceTransition(pState) {
152
- /**
153
- * @param {import("./scxml").ISCXMLTransition[]} pAllTransitions
154
- * @param {import("./scxml").ISCXMLTransition} pTransition
155
- * @returns {import("../../..").ITransition}
156
- */
157
- return (pAllTransitions, pTransition) => {
158
- // in SCXML spaces denote references to multiple states
159
- // => split into multiple transitions
160
- const lTargets = (pTransition?.target ?? pState.id).split(/\s+/);
161
- const lTransitionAttributes = extractTransitionAttributes(pTransition);
162
-
163
- return pAllTransitions.concat(
164
- lTargets.map((pTarget) => ({
165
- from: pState.id,
166
- // a 'target-less transition' is typically
167
- // a self-transition
168
- to: pTarget,
169
- ...lTransitionAttributes,
170
- }))
171
- );
172
- };
173
- }
174
-
175
- /**
176
- * @param {import("./scxml").INormalizedSCXMLState[]} pStates
177
- * @returns {import("../../../types/state-machine-cat").ITransition[]}
178
- */
179
- function extractTransitions(pStates) {
180
- return pStates
181
- .filter((pState) =>
182
- Object.prototype.hasOwnProperty.call(pState, "transition")
183
- )
184
- .reduce(
185
- (pAllTransitions, pThisState) =>
186
- pAllTransitions.concat(
187
- castArray(pThisState.transition).reduce(
188
- reduceTransition(pThisState),
189
- []
190
- )
191
- ),
192
- []
193
- );
194
- }
195
-
196
- /**
197
- * @param {any} pSCXMLStateMachine
198
- * @returns {import("../../..").IStateMachine}
199
- */
200
- function mapMachine(pSCXMLStateMachine) {
201
- const lNormalizedMachine = normalizeMachine(pSCXMLStateMachine);
202
- const lReturnValue = {
203
- states: lNormalizedMachine.initial
204
- .map(mapState("initial"))
205
- .concat(lNormalizedMachine.state.map(mapState("regular")))
206
- .concat(lNormalizedMachine.parallel.map(mapState("parallel")))
207
- .concat(lNormalizedMachine.history.map(mapState("history")))
208
- .concat(lNormalizedMachine.final.map(mapState("final"))),
209
- };
210
-
211
- const lTransitions = extractTransitions(lNormalizedMachine.initial)
212
- .concat(extractTransitions(lNormalizedMachine.state))
213
- .concat(extractTransitions(lNormalizedMachine.parallel));
214
-
215
- if (lTransitions.length > 0) {
216
- lReturnValue.transitions = lTransitions;
217
- }
218
- return lReturnValue;
219
- }
220
-
221
- /**
222
- * This funky looking replace exists to make the output of the fast-xml-parser
223
- * backwards compatible with its version 3 that in case of conflicts between
224
- * attribute names and tag names gave preference to the attribute name (version 4
225
- * does the opposite). The previous behaviour was undocumented and for fast-xml-parser
226
- * likely a kind of edge case (normal people probably don't pass an empty attributeNamePrefix).
227
- *
228
- * @param {any} pObject
229
- * @param {string} pAttributeNamePrefix
230
- * @returns {any} the object, but
231
- * - with attributes that have the same name as tags in the same parent removed,
232
- * - attributes that don't have an equally named tag get their key renamed back
233
- * to the one without the pAttributeNamePrefix
234
- */
235
- function deDuplicateAttributesAndTags(pObject, pAttributeNamePrefix) {
236
- // - 'traverse' relies on the 'this' property a 'normal' function provides,
237
- // so this is not an arrow function.
238
- // - while it looks iffy to have a map function without a return statement
239
- // it's canonical traverse use (as per https://github.com/ljharb/js-traverse/blob/v0.6.7/README.md)
240
- // eslint-disable-next-line array-callback-return
241
- return traverse(pObject).map(function deDuplicate() {
242
- if (this.key?.startsWith(pAttributeNamePrefix)) {
243
- const pUnprefixedAttributeName = this.key.slice(
244
- pAttributeNamePrefix.length
245
- );
246
- if (this.parent.keys.includes(pUnprefixedAttributeName)) {
247
- this.remove();
248
- } else {
249
- this.parent.node[pUnprefixedAttributeName] = this.node;
250
- this.remove();
251
- }
252
- }
253
- });
254
- }
255
-
256
- /**
257
- * Parses SCXML into a state machine AST.
258
- *
259
- * @param {string} pSCXMLString The SCXML to parse
260
- * @returns {import("../../../types/state-machine-cat").IStateMachine} state machine AST
261
- */
262
- export function parse(pSCXMLString) {
263
- const lTrimmedSCXMLString = pSCXMLString.trim();
264
- const lAttributeNamePrefix = "@_";
265
- /** @type {import("./scxml").ISCXMLAsJSON} */
266
- let lXMLAsJSON = {};
267
-
268
- const lXMLParser = new fastxml.XMLParser({
269
- attributeNamePrefix: lAttributeNamePrefix,
270
- ignoreAttributes: false,
271
- parseTagValue: true,
272
- processEntities: false,
273
- tagValueProcessor: (_pTagName, pTagValue) => he.decode(pTagValue),
274
- stopNodes: ["*.onentry", "*.onexit", "*.transition"],
275
- });
276
-
277
- try {
278
- lXMLAsJSON = deDuplicateAttributesAndTags(
279
- lXMLParser.parse(lTrimmedSCXMLString, true),
280
- lAttributeNamePrefix
281
- );
282
- } catch (pError) {
283
- throw new Error("That doesn't look like valid xml ...\n");
284
- }
285
- return mapMachine(
286
- lXMLAsJSON?.scxml ?? {
287
- xmlns: "http://www.w3.org/2005/07/scxml",
288
- version: "1.0",
289
- }
290
- );
291
- }