@sapui5/sap.fe.base 1.125.0 → 1.126.1
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/package.json +1 -1
- package/src/sap/fe/base/.library +1 -1
- package/src/sap/fe/base/AnnotationEnum.js +33 -0
- package/src/sap/fe/base/AnnotationEnum.ts +21 -0
- package/src/sap/fe/base/BindingToolkit.js +1822 -0
- package/src/sap/fe/base/BindingToolkit.ts +2452 -0
- package/src/sap/fe/base/ClassSupport.js +24 -4
- package/src/sap/fe/base/ClassSupport.ts +26 -3
- package/src/sap/fe/base/jsx-runtime/ViewLoader.js +1 -1
- package/src/sap/fe/base/jsx-runtime/ViewLoader.tsx +2 -1
- package/src/sap/fe/base/jsx-runtime/jsx-control.js +31 -3
- package/src/sap/fe/base/jsx-runtime/jsx-control.ts +34 -6
- package/src/sap/fe/base/jsx-runtime/jsx-renderManager.js +7 -3
- package/src/sap/fe/base/jsx-runtime/jsx-renderManager.ts +8 -3
- package/src/sap/fe/base/jsx-runtime/jsx-xml.js +73 -21
- package/src/sap/fe/base/jsx-runtime/jsx-xml.ts +90 -22
- package/src/sap/fe/base/library.js +1 -1
|
@@ -0,0 +1,2452 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ActionParameter,
|
|
3
|
+
AndAnnotationExpression,
|
|
4
|
+
AndConditionalExpression,
|
|
5
|
+
ApplyAnnotationExpression,
|
|
6
|
+
Boolean,
|
|
7
|
+
ConditionalCheckOrValue,
|
|
8
|
+
EntitySet,
|
|
9
|
+
EntityType,
|
|
10
|
+
EqAnnotationExpression,
|
|
11
|
+
EqConditionalExpression,
|
|
12
|
+
GeAnnotationExpression,
|
|
13
|
+
GeConditionalExpression,
|
|
14
|
+
GtAnnotationExpression,
|
|
15
|
+
GtConditionalExpression,
|
|
16
|
+
IfAnnotationExpression,
|
|
17
|
+
IfAnnotationExpressionValue,
|
|
18
|
+
LeAnnotationExpression,
|
|
19
|
+
LeConditionalExpression,
|
|
20
|
+
LtAnnotationExpression,
|
|
21
|
+
LtConditionalExpression,
|
|
22
|
+
NeAnnotationExpression,
|
|
23
|
+
NeConditionalExpression,
|
|
24
|
+
NotAnnotationExpression,
|
|
25
|
+
NotConditionalExpression,
|
|
26
|
+
OrAnnotationExpression,
|
|
27
|
+
OrConditionalExpression,
|
|
28
|
+
PathAnnotationExpression,
|
|
29
|
+
PathConditionExpression,
|
|
30
|
+
Property,
|
|
31
|
+
PropertyAnnotationValue
|
|
32
|
+
} from "@sap-ux/vocabularies-types";
|
|
33
|
+
import { CommonAnnotationTerms } from "@sap-ux/vocabularies-types/vocabularies/Common";
|
|
34
|
+
import { resolveEnumValue } from "sap/fe/base/AnnotationEnum";
|
|
35
|
+
|
|
36
|
+
export type PrimitiveType = string | number | bigint | boolean | object | null | undefined;
|
|
37
|
+
type DefinedPrimitiveType = string | number | bigint | boolean | object;
|
|
38
|
+
type PrimitiveTypeCast<P> = P extends String ? string : P extends Boolean ? boolean : P extends Number ? number : P;
|
|
39
|
+
type BaseExpression<_T> = {
|
|
40
|
+
_type: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type FormatOptions = Partial<{
|
|
44
|
+
customUnits: Record<string, object>;
|
|
45
|
+
customCurrencies: Record<string, object>;
|
|
46
|
+
decimals: number;
|
|
47
|
+
emptyString: null | number | string;
|
|
48
|
+
fiscalType: string;
|
|
49
|
+
groupingEnabled: boolean;
|
|
50
|
+
maxFractionDigits: number;
|
|
51
|
+
maxIntegerDigits: number;
|
|
52
|
+
minFractionDigits: number;
|
|
53
|
+
minIntegerDigits: number;
|
|
54
|
+
parseAsString: boolean;
|
|
55
|
+
parseKeepsEmptyString: boolean;
|
|
56
|
+
pattern: string;
|
|
57
|
+
precision: number;
|
|
58
|
+
preserveDecimals: boolean;
|
|
59
|
+
shortDecimals: number;
|
|
60
|
+
shortRefNumber: number;
|
|
61
|
+
showMeasure: boolean;
|
|
62
|
+
showNumber: boolean;
|
|
63
|
+
showScale: boolean;
|
|
64
|
+
style: string;
|
|
65
|
+
}>;
|
|
66
|
+
|
|
67
|
+
export type ConstantExpression<T> = BaseExpression<T> & {
|
|
68
|
+
_type: "Constant";
|
|
69
|
+
value: T;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
type SetOperator = "&&" | "||";
|
|
73
|
+
export type SetExpression = BaseExpression<boolean> & {
|
|
74
|
+
_type: "Set";
|
|
75
|
+
operator: SetOperator;
|
|
76
|
+
operands: BindingToolkitExpression<boolean>[];
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export type NotExpression = BaseExpression<boolean> & {
|
|
80
|
+
_type: "Not";
|
|
81
|
+
operand: BindingToolkitExpression<boolean>;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export type TruthyExpression = BaseExpression<boolean> & {
|
|
85
|
+
_type: "Truthy";
|
|
86
|
+
operand: BindingToolkitExpression<string>;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export type ReferenceExpression = BaseExpression<object> & {
|
|
90
|
+
_type: "Ref";
|
|
91
|
+
ref: string | null;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export type FormatterExpression<T> = BaseExpression<T> & {
|
|
95
|
+
_type: "Formatter";
|
|
96
|
+
fn: string;
|
|
97
|
+
parameters: BindingToolkitExpression<unknown>[];
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
type ComplexTypeExpression<T> = BaseExpression<T> & {
|
|
101
|
+
_type: "ComplexType";
|
|
102
|
+
type: string;
|
|
103
|
+
formatOptions: FormatOptions;
|
|
104
|
+
parameters: object;
|
|
105
|
+
bindingParameters: BindingToolkitExpression<unknown>[];
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export type FunctionExpression<T> = BaseExpression<T> & {
|
|
109
|
+
_type: "Function";
|
|
110
|
+
obj?: BindingToolkitExpression<object>;
|
|
111
|
+
fn: string;
|
|
112
|
+
isFormattingFn: boolean;
|
|
113
|
+
parameters: BindingToolkitExpression<unknown>[];
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export type ConcatExpression = BaseExpression<string> & {
|
|
117
|
+
_type: "Concat";
|
|
118
|
+
expressions: BindingToolkitExpression<string>[];
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export type LengthExpression = BaseExpression<string> & {
|
|
122
|
+
_type: "Length";
|
|
123
|
+
pathInModel: PathInModelExpression<unknown>;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
type UnresolvablePathExpression = BaseExpression<string> & {
|
|
127
|
+
_type: "Unresolvable";
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @typedef PathInModelExpression
|
|
132
|
+
*/
|
|
133
|
+
export type PathInModelExpression<T> = BaseExpression<T> & {
|
|
134
|
+
_type: "PathInModel";
|
|
135
|
+
modelName?: string;
|
|
136
|
+
path: string;
|
|
137
|
+
targetEntitySet?: EntitySet;
|
|
138
|
+
type?: string;
|
|
139
|
+
constraints?: BindingConstraints;
|
|
140
|
+
parameters?: object;
|
|
141
|
+
targetType?: string;
|
|
142
|
+
mode?: string;
|
|
143
|
+
formatOptions?: FormatOptions;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export type EmbeddedUI5BindingExpression<T> = BaseExpression<T> & {
|
|
147
|
+
_type: "EmbeddedBinding";
|
|
148
|
+
value: string;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export type EmbeddedUI5ExpressionBindingExpression<T> = BaseExpression<T> & {
|
|
152
|
+
_type: "EmbeddedExpressionBinding";
|
|
153
|
+
value: string;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export type ComparisonOperator = "===" | "!==" | ">=" | ">" | "<=" | "<";
|
|
157
|
+
export type ComparisonExpression = BaseExpression<boolean> & {
|
|
158
|
+
_type: "Comparison";
|
|
159
|
+
operator: ComparisonOperator;
|
|
160
|
+
operand1: BindingToolkitExpression<unknown>;
|
|
161
|
+
operand2: BindingToolkitExpression<unknown>;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
export type IfElseExpression<T> = BaseExpression<T> & {
|
|
165
|
+
_type: "IfElse";
|
|
166
|
+
condition: BindingToolkitExpression<boolean>;
|
|
167
|
+
onTrue: BindingToolkitExpression<T>;
|
|
168
|
+
onFalse: BindingToolkitExpression<T>;
|
|
169
|
+
};
|
|
170
|
+
export function isProperty(serviceObject: unknown): serviceObject is Property {
|
|
171
|
+
return (serviceObject as Property)?._type === "Property";
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* An expression that evaluates to type T.
|
|
176
|
+
*
|
|
177
|
+
* @typedef BindingToolkitExpression
|
|
178
|
+
*/
|
|
179
|
+
export type BindingToolkitExpression<T> =
|
|
180
|
+
| UnresolvablePathExpression
|
|
181
|
+
| ConstantExpression<T>
|
|
182
|
+
| SetExpression
|
|
183
|
+
| NotExpression
|
|
184
|
+
| TruthyExpression
|
|
185
|
+
| ConcatExpression
|
|
186
|
+
| LengthExpression
|
|
187
|
+
| PathInModelExpression<T>
|
|
188
|
+
| EmbeddedUI5BindingExpression<T>
|
|
189
|
+
| EmbeddedUI5ExpressionBindingExpression<T>
|
|
190
|
+
| ComparisonExpression
|
|
191
|
+
| IfElseExpression<T>
|
|
192
|
+
| FormatterExpression<T>
|
|
193
|
+
| ComplexTypeExpression<T>
|
|
194
|
+
| ReferenceExpression
|
|
195
|
+
| FunctionExpression<T>;
|
|
196
|
+
|
|
197
|
+
type TypeMappingDefinition = {
|
|
198
|
+
type: string;
|
|
199
|
+
constraints?: {
|
|
200
|
+
$Precision?: string;
|
|
201
|
+
$V4?: string;
|
|
202
|
+
$Scale?: string;
|
|
203
|
+
$MaxLength?: string;
|
|
204
|
+
$Nullable?: string;
|
|
205
|
+
"@Org.OData.Validation.V1.Minimum/$Decimal"?: string;
|
|
206
|
+
"@Org.OData.Validation.V1.Minimum@Org.OData.Validation.V1.Exclusive"?: string;
|
|
207
|
+
"@Org.OData.Validation.V1.Maximum/$Decimal"?: string;
|
|
208
|
+
"@Org.OData.Validation.V1.Maximum@Org.OData.Validation.V1.Exclusive"?: string;
|
|
209
|
+
"@com.sap.vocabularies.Common.v1.IsDigitSequence"?: string;
|
|
210
|
+
};
|
|
211
|
+
};
|
|
212
|
+
export const EDM_TYPE_MAPPING: Record<string, TypeMappingDefinition> = {
|
|
213
|
+
"Edm.Boolean": { type: "sap.ui.model.odata.type.Boolean" },
|
|
214
|
+
"Edm.Byte": { type: "sap.ui.model.odata.type.Byte" },
|
|
215
|
+
"Edm.Date": { type: "sap.ui.model.odata.type.Date" },
|
|
216
|
+
"Edm.DateTimeOffset": {
|
|
217
|
+
constraints: {
|
|
218
|
+
$Precision: "precision",
|
|
219
|
+
$V4: "V4"
|
|
220
|
+
},
|
|
221
|
+
type: "sap.ui.model.odata.type.DateTimeOffset"
|
|
222
|
+
},
|
|
223
|
+
"Edm.Decimal": {
|
|
224
|
+
constraints: {
|
|
225
|
+
"@Org.OData.Validation.V1.Minimum/$Decimal": "minimum",
|
|
226
|
+
"@Org.OData.Validation.V1.Minimum@Org.OData.Validation.V1.Exclusive": "minimumExclusive",
|
|
227
|
+
"@Org.OData.Validation.V1.Maximum/$Decimal": "maximum",
|
|
228
|
+
"@Org.OData.Validation.V1.Maximum@Org.OData.Validation.V1.Exclusive": "maximumExclusive",
|
|
229
|
+
$Precision: "precision",
|
|
230
|
+
$Scale: "scale"
|
|
231
|
+
},
|
|
232
|
+
type: "sap.ui.model.odata.type.Decimal"
|
|
233
|
+
},
|
|
234
|
+
"Edm.Double": { type: "sap.ui.model.odata.type.Double" },
|
|
235
|
+
"Edm.Guid": { type: "sap.ui.model.odata.type.Guid" },
|
|
236
|
+
"Edm.Int16": { type: "sap.ui.model.odata.type.Int16" },
|
|
237
|
+
"Edm.Int32": { type: "sap.ui.model.odata.type.Int32" },
|
|
238
|
+
"Edm.Int64": { type: "sap.ui.model.odata.type.Int64" },
|
|
239
|
+
"Edm.SByte": { type: "sap.ui.model.odata.type.SByte" },
|
|
240
|
+
"Edm.Single": { type: "sap.ui.model.odata.type.Single" },
|
|
241
|
+
"Edm.Stream": { type: "sap.ui.model.odata.type.Stream" },
|
|
242
|
+
"Edm.Binary": { type: "sap.ui.model.odata.type.Stream" },
|
|
243
|
+
"Edm.String": {
|
|
244
|
+
constraints: {
|
|
245
|
+
"@com.sap.vocabularies.Common.v1.IsDigitSequence": "isDigitSequence",
|
|
246
|
+
$MaxLength: "maxLength",
|
|
247
|
+
$Nullable: "nullable"
|
|
248
|
+
},
|
|
249
|
+
type: "sap.ui.model.odata.type.String"
|
|
250
|
+
},
|
|
251
|
+
"Edm.TimeOfDay": {
|
|
252
|
+
constraints: {
|
|
253
|
+
$Precision: "precision"
|
|
254
|
+
},
|
|
255
|
+
type: "sap.ui.model.odata.type.TimeOfDay"
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* An expression that evaluates to type T, or a constant value of type T
|
|
261
|
+
*/
|
|
262
|
+
export type ExpressionOrPrimitive<T extends PrimitiveType> = BindingToolkitExpression<T> | T;
|
|
263
|
+
|
|
264
|
+
export const unresolvableExpression: UnresolvablePathExpression = {
|
|
265
|
+
_type: "Unresolvable"
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export function escapeXmlAttribute(inputString: string): string {
|
|
269
|
+
return inputString.replace(/'/g, "\\'");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function hasUnresolvableExpression(...expressions: BindingToolkitExpression<unknown>[]): boolean {
|
|
273
|
+
return expressions.find((expr) => expr._type === "Unresolvable") !== undefined;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Check two expressions for (deep) equality.
|
|
277
|
+
*
|
|
278
|
+
* @param a
|
|
279
|
+
* @param b
|
|
280
|
+
* @returns `true` if the two expressions are equal
|
|
281
|
+
*/
|
|
282
|
+
export function _checkExpressionsAreEqual<T>(a: BindingToolkitExpression<T>, b: BindingToolkitExpression<T>): boolean {
|
|
283
|
+
if (a._type !== b._type) {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
switch (a._type) {
|
|
288
|
+
case "Unresolvable":
|
|
289
|
+
return false; // Unresolvable is never equal to anything even itself
|
|
290
|
+
case "Constant":
|
|
291
|
+
case "EmbeddedBinding":
|
|
292
|
+
case "EmbeddedExpressionBinding":
|
|
293
|
+
return a.value === (b as ConstantExpression<T>).value;
|
|
294
|
+
|
|
295
|
+
case "Not":
|
|
296
|
+
return _checkExpressionsAreEqual(a.operand, (b as NotExpression).operand);
|
|
297
|
+
case "Truthy":
|
|
298
|
+
return _checkExpressionsAreEqual(a.operand, (b as TruthyExpression).operand);
|
|
299
|
+
case "Set":
|
|
300
|
+
return (
|
|
301
|
+
a.operator === (b as SetExpression).operator &&
|
|
302
|
+
a.operands.length === (b as SetExpression).operands.length &&
|
|
303
|
+
a.operands.every((expression) =>
|
|
304
|
+
(b as SetExpression).operands.some((otherExpression) => _checkExpressionsAreEqual(expression, otherExpression))
|
|
305
|
+
)
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
case "IfElse":
|
|
309
|
+
return (
|
|
310
|
+
_checkExpressionsAreEqual(a.condition, (b as IfElseExpression<T>).condition) &&
|
|
311
|
+
_checkExpressionsAreEqual(a.onTrue, (b as IfElseExpression<T>).onTrue) &&
|
|
312
|
+
_checkExpressionsAreEqual(a.onFalse, (b as IfElseExpression<T>).onFalse)
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
case "Comparison":
|
|
316
|
+
return (
|
|
317
|
+
a.operator === (b as ComparisonExpression).operator &&
|
|
318
|
+
_checkExpressionsAreEqual(a.operand1, (b as ComparisonExpression).operand1) &&
|
|
319
|
+
_checkExpressionsAreEqual(a.operand2, (b as ComparisonExpression).operand2)
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
case "Concat":
|
|
323
|
+
const aExpressions = a.expressions;
|
|
324
|
+
const bExpressions = (b as ConcatExpression).expressions;
|
|
325
|
+
if (aExpressions.length !== bExpressions.length) {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
return aExpressions.every((expression, index) => {
|
|
329
|
+
return _checkExpressionsAreEqual(expression, bExpressions[index]);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
case "Length":
|
|
333
|
+
return _checkExpressionsAreEqual(a.pathInModel, (b as LengthExpression).pathInModel);
|
|
334
|
+
|
|
335
|
+
case "PathInModel":
|
|
336
|
+
return (
|
|
337
|
+
a.modelName === (b as PathInModelExpression<T>).modelName &&
|
|
338
|
+
a.path === (b as PathInModelExpression<T>).path &&
|
|
339
|
+
a.targetEntitySet === (b as PathInModelExpression<T>).targetEntitySet
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
case "Formatter":
|
|
343
|
+
return (
|
|
344
|
+
a.fn === (b as FormatterExpression<T>).fn &&
|
|
345
|
+
a.parameters.length === (b as FormatterExpression<T>).parameters.length &&
|
|
346
|
+
a.parameters.every((value, index) => _checkExpressionsAreEqual((b as FormatterExpression<T>).parameters[index], value))
|
|
347
|
+
);
|
|
348
|
+
case "ComplexType":
|
|
349
|
+
return (
|
|
350
|
+
a.type === (b as ComplexTypeExpression<T>).type &&
|
|
351
|
+
a.bindingParameters.length === (b as ComplexTypeExpression<T>).bindingParameters.length &&
|
|
352
|
+
a.bindingParameters.every((value, index) =>
|
|
353
|
+
_checkExpressionsAreEqual((b as ComplexTypeExpression<T>).bindingParameters[index], value)
|
|
354
|
+
)
|
|
355
|
+
);
|
|
356
|
+
case "Function":
|
|
357
|
+
const otherFunction = b as FunctionExpression<T>;
|
|
358
|
+
if (a.obj === undefined || otherFunction.obj === undefined) {
|
|
359
|
+
return a.obj === otherFunction;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return (
|
|
363
|
+
a.fn === otherFunction.fn &&
|
|
364
|
+
_checkExpressionsAreEqual(a.obj, otherFunction.obj) &&
|
|
365
|
+
a.parameters.length === otherFunction.parameters.length &&
|
|
366
|
+
a.parameters.every((value, index) => _checkExpressionsAreEqual(otherFunction.parameters[index], value))
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
case "Ref":
|
|
370
|
+
return a.ref === (b as ReferenceExpression).ref;
|
|
371
|
+
}
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Converts a nested SetExpression by inlining operands of type SetExpression with the same operator.
|
|
377
|
+
*
|
|
378
|
+
* @param expression The expression to flatten
|
|
379
|
+
* @returns A new SetExpression with the same operator
|
|
380
|
+
*/
|
|
381
|
+
function flattenSetExpression(expression: SetExpression): SetExpression {
|
|
382
|
+
return expression.operands.reduce(
|
|
383
|
+
(result: SetExpression, operand) => {
|
|
384
|
+
const candidatesForFlattening =
|
|
385
|
+
operand._type === "Set" && operand.operator === expression.operator ? operand.operands : [operand];
|
|
386
|
+
candidatesForFlattening.forEach((candidate) => {
|
|
387
|
+
if (result.operands.every((e) => !_checkExpressionsAreEqual(e, candidate))) {
|
|
388
|
+
result.operands.push(candidate);
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
return result;
|
|
392
|
+
},
|
|
393
|
+
{ _type: "Set", operator: expression.operator, operands: [] }
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Detects whether an array of boolean expressions contains an expression and its negation.
|
|
399
|
+
*
|
|
400
|
+
* @param expressions Array of expressions
|
|
401
|
+
* @returns `true` if the set of expressions contains an expression and its negation
|
|
402
|
+
*/
|
|
403
|
+
function hasOppositeExpressions(expressions: BindingToolkitExpression<boolean>[]): boolean {
|
|
404
|
+
const negatedExpressions = expressions.map(not);
|
|
405
|
+
return expressions.some((expression, index) => {
|
|
406
|
+
for (let i = index + 1; i < negatedExpressions.length; i++) {
|
|
407
|
+
if (_checkExpressionsAreEqual(expression, negatedExpressions[i])) {
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return false;
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Logical `and` expression.
|
|
417
|
+
*
|
|
418
|
+
* The expression is simplified to false if this can be decided statically (that is, if one operand is a constant
|
|
419
|
+
* false or if the expression contains an operand and its negation).
|
|
420
|
+
*
|
|
421
|
+
* @param operands Expressions to connect by `and`
|
|
422
|
+
* @returns Expression evaluating to boolean
|
|
423
|
+
*/
|
|
424
|
+
export function and(...operands: ExpressionOrPrimitive<boolean>[]): BindingToolkitExpression<boolean> {
|
|
425
|
+
const expressions = flattenSetExpression({
|
|
426
|
+
_type: "Set",
|
|
427
|
+
operator: "&&",
|
|
428
|
+
operands: operands.map(wrapPrimitive)
|
|
429
|
+
}).operands;
|
|
430
|
+
|
|
431
|
+
if (hasUnresolvableExpression(...expressions)) {
|
|
432
|
+
return unresolvableExpression;
|
|
433
|
+
}
|
|
434
|
+
let isStaticFalse = false;
|
|
435
|
+
const nonTrivialExpression = expressions.filter((expression) => {
|
|
436
|
+
if (isFalse(expression)) {
|
|
437
|
+
isStaticFalse = true;
|
|
438
|
+
}
|
|
439
|
+
return !isConstant(expression);
|
|
440
|
+
});
|
|
441
|
+
if (isStaticFalse) {
|
|
442
|
+
return constant(false);
|
|
443
|
+
} else if (nonTrivialExpression.length === 0) {
|
|
444
|
+
// Resolve the constant then
|
|
445
|
+
const isValid = expressions.reduce((result, expression) => result && isTrue(expression), true);
|
|
446
|
+
return constant(isValid);
|
|
447
|
+
} else if (nonTrivialExpression.length === 1) {
|
|
448
|
+
return nonTrivialExpression[0];
|
|
449
|
+
} else if (hasOppositeExpressions(nonTrivialExpression)) {
|
|
450
|
+
return constant(false);
|
|
451
|
+
} else {
|
|
452
|
+
return {
|
|
453
|
+
_type: "Set",
|
|
454
|
+
operator: "&&",
|
|
455
|
+
operands: nonTrivialExpression
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Logical `or` expression.
|
|
462
|
+
*
|
|
463
|
+
* The expression is simplified to true if this can be decided statically (that is, if one operand is a constant
|
|
464
|
+
* true or if the expression contains an operand and its negation).
|
|
465
|
+
*
|
|
466
|
+
* @param operands Expressions to connect by `or`
|
|
467
|
+
* @returns Expression evaluating to boolean
|
|
468
|
+
*/
|
|
469
|
+
export function or(...operands: ExpressionOrPrimitive<boolean>[]): BindingToolkitExpression<boolean> {
|
|
470
|
+
const expressions = flattenSetExpression({
|
|
471
|
+
_type: "Set",
|
|
472
|
+
operator: "||",
|
|
473
|
+
operands: operands.map(wrapPrimitive)
|
|
474
|
+
}).operands;
|
|
475
|
+
if (hasUnresolvableExpression(...expressions)) {
|
|
476
|
+
return unresolvableExpression;
|
|
477
|
+
}
|
|
478
|
+
let isStaticTrue = false;
|
|
479
|
+
const nonTrivialExpression = expressions.filter((expression) => {
|
|
480
|
+
if (isTrue(expression)) {
|
|
481
|
+
isStaticTrue = true;
|
|
482
|
+
}
|
|
483
|
+
return !isConstant(expression) || expression.value;
|
|
484
|
+
});
|
|
485
|
+
if (isStaticTrue) {
|
|
486
|
+
return constant(true);
|
|
487
|
+
} else if (nonTrivialExpression.length === 0) {
|
|
488
|
+
// Resolve the constant then
|
|
489
|
+
const isValid = expressions.reduce((result, expression) => result && isTrue(expression), true);
|
|
490
|
+
return constant(isValid);
|
|
491
|
+
} else if (nonTrivialExpression.length === 1) {
|
|
492
|
+
return nonTrivialExpression[0];
|
|
493
|
+
} else if (hasOppositeExpressions(nonTrivialExpression)) {
|
|
494
|
+
return constant(true);
|
|
495
|
+
} else {
|
|
496
|
+
return {
|
|
497
|
+
_type: "Set",
|
|
498
|
+
operator: "||",
|
|
499
|
+
operands: nonTrivialExpression
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Logical `not` operator.
|
|
506
|
+
*
|
|
507
|
+
* @param operand The expression to reverse
|
|
508
|
+
* @returns The resulting expression that evaluates to boolean
|
|
509
|
+
*/
|
|
510
|
+
export function not(operand: ExpressionOrPrimitive<boolean>): BindingToolkitExpression<boolean> {
|
|
511
|
+
operand = wrapPrimitive(operand);
|
|
512
|
+
if (hasUnresolvableExpression(operand)) {
|
|
513
|
+
return unresolvableExpression;
|
|
514
|
+
} else if (isConstant(operand)) {
|
|
515
|
+
return constant(!operand.value);
|
|
516
|
+
} else if (
|
|
517
|
+
typeof operand === "object" &&
|
|
518
|
+
operand._type === "Set" &&
|
|
519
|
+
operand.operator === "||" &&
|
|
520
|
+
operand.operands.every((expression) => isConstant(expression) || isComparison(expression))
|
|
521
|
+
) {
|
|
522
|
+
return and(...operand.operands.map((expression) => not(expression)));
|
|
523
|
+
} else if (
|
|
524
|
+
typeof operand === "object" &&
|
|
525
|
+
operand._type === "Set" &&
|
|
526
|
+
operand.operator === "&&" &&
|
|
527
|
+
operand.operands.every((expression) => isConstant(expression) || isComparison(expression))
|
|
528
|
+
) {
|
|
529
|
+
return or(...operand.operands.map((expression) => not(expression)));
|
|
530
|
+
} else if (isComparison(operand)) {
|
|
531
|
+
// Create the reverse comparison
|
|
532
|
+
switch (operand.operator) {
|
|
533
|
+
case "!==":
|
|
534
|
+
return { ...operand, operator: "===" };
|
|
535
|
+
case "<":
|
|
536
|
+
return { ...operand, operator: ">=" };
|
|
537
|
+
case "<=":
|
|
538
|
+
return { ...operand, operator: ">" };
|
|
539
|
+
case "===":
|
|
540
|
+
return { ...operand, operator: "!==" };
|
|
541
|
+
case ">":
|
|
542
|
+
return { ...operand, operator: "<=" };
|
|
543
|
+
case ">=":
|
|
544
|
+
return { ...operand, operator: "<" };
|
|
545
|
+
}
|
|
546
|
+
} else if (operand._type === "Not") {
|
|
547
|
+
return operand.operand;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return {
|
|
551
|
+
_type: "Not",
|
|
552
|
+
operand: operand
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Evaluates whether a binding expression is equal to true with a loose equality.
|
|
558
|
+
*
|
|
559
|
+
* @param operand The expression to check
|
|
560
|
+
* @returns The resulting expression that evaluates to boolean
|
|
561
|
+
*/
|
|
562
|
+
export function isTruthy(operand: BindingToolkitExpression<string>): BindingToolkitExpression<boolean> {
|
|
563
|
+
if (isConstant(operand)) {
|
|
564
|
+
return constant(!!operand.value);
|
|
565
|
+
} else {
|
|
566
|
+
return {
|
|
567
|
+
_type: "Truthy",
|
|
568
|
+
operand: operand
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Creates a binding expression that will be evaluated by the corresponding model.
|
|
575
|
+
*
|
|
576
|
+
* @param path
|
|
577
|
+
* @param modelName
|
|
578
|
+
* @param visitedNavigationPaths
|
|
579
|
+
* @param pathVisitor
|
|
580
|
+
* @returns An expression representating that path in the model
|
|
581
|
+
* @deprecated use pathInModel instead
|
|
582
|
+
*/
|
|
583
|
+
export function bindingExpression<TargetType extends PrimitiveType>(
|
|
584
|
+
path: string,
|
|
585
|
+
modelName?: string,
|
|
586
|
+
visitedNavigationPaths: string[] = [],
|
|
587
|
+
pathVisitor?: Function
|
|
588
|
+
): PathInModelExpression<TargetType> | UnresolvablePathExpression {
|
|
589
|
+
return pathInModel(path, modelName, visitedNavigationPaths, pathVisitor);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Creates a binding expression that will be evaluated by the corresponding model.
|
|
594
|
+
*
|
|
595
|
+
* @template TargetType
|
|
596
|
+
* @param path The path on the model
|
|
597
|
+
* @param [modelName] The name of the model
|
|
598
|
+
* @param [visitedNavigationPaths] The paths from the root entitySet
|
|
599
|
+
* @param [pathVisitor] A function to modify the resulting path
|
|
600
|
+
* @returns An expression representating that path in the model
|
|
601
|
+
*/
|
|
602
|
+
export function pathInModel(
|
|
603
|
+
path: undefined,
|
|
604
|
+
modelName?: string,
|
|
605
|
+
visitedNavigationPaths?: string[],
|
|
606
|
+
pathVisitor?: Function
|
|
607
|
+
): UnresolvablePathExpression;
|
|
608
|
+
export function pathInModel<TargetType extends PrimitiveType>(
|
|
609
|
+
path: string,
|
|
610
|
+
modelName?: string,
|
|
611
|
+
visitedNavigationPaths?: string[],
|
|
612
|
+
pathVisitor?: undefined
|
|
613
|
+
): PathInModelExpression<TargetType>;
|
|
614
|
+
export function pathInModel<TargetType extends PrimitiveType>(
|
|
615
|
+
path: string | undefined,
|
|
616
|
+
modelName?: string,
|
|
617
|
+
visitedNavigationPaths?: string[],
|
|
618
|
+
pathVisitor?: Function
|
|
619
|
+
): UnresolvablePathExpression | PathInModelExpression<TargetType>;
|
|
620
|
+
export function pathInModel<TargetType extends PrimitiveType>(
|
|
621
|
+
path: string | undefined,
|
|
622
|
+
modelName?: string,
|
|
623
|
+
visitedNavigationPaths: string[] = [],
|
|
624
|
+
pathVisitor?: Function
|
|
625
|
+
): UnresolvablePathExpression | PathInModelExpression<TargetType> {
|
|
626
|
+
if (path === undefined) {
|
|
627
|
+
return unresolvableExpression;
|
|
628
|
+
}
|
|
629
|
+
let targetPath;
|
|
630
|
+
if (pathVisitor) {
|
|
631
|
+
targetPath = pathVisitor(path);
|
|
632
|
+
if (targetPath === undefined) {
|
|
633
|
+
return unresolvableExpression;
|
|
634
|
+
}
|
|
635
|
+
} else {
|
|
636
|
+
const localPath = visitedNavigationPaths.concat();
|
|
637
|
+
localPath.push(path);
|
|
638
|
+
targetPath = localPath.join("/");
|
|
639
|
+
}
|
|
640
|
+
return {
|
|
641
|
+
_type: "PathInModel",
|
|
642
|
+
modelName: modelName,
|
|
643
|
+
path: targetPath
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
type PlainExpressionObject = { [index: string]: BindingToolkitExpression<unknown> };
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Creates a constant expression based on a primitive value.
|
|
651
|
+
*
|
|
652
|
+
* @template T
|
|
653
|
+
* @param value The constant to wrap in an expression
|
|
654
|
+
* @returns The constant expression
|
|
655
|
+
*/
|
|
656
|
+
export function constant<T extends PrimitiveType>(value: T): ConstantExpression<T> {
|
|
657
|
+
let constantValue: T;
|
|
658
|
+
|
|
659
|
+
if (typeof value === "object" && value !== null && value !== undefined) {
|
|
660
|
+
if (Array.isArray(value)) {
|
|
661
|
+
constantValue = value.map(wrapPrimitive) as T;
|
|
662
|
+
} else if (isPrimitiveObject(value)) {
|
|
663
|
+
constantValue = value.valueOf() as T;
|
|
664
|
+
} else {
|
|
665
|
+
constantValue = Object.entries(value).reduce((plainExpression, [key, val]) => {
|
|
666
|
+
const wrappedValue = wrapPrimitive(val);
|
|
667
|
+
if (wrappedValue._type !== "Constant" || wrappedValue.value !== undefined) {
|
|
668
|
+
plainExpression[key] = wrappedValue;
|
|
669
|
+
}
|
|
670
|
+
return plainExpression;
|
|
671
|
+
}, {} as PlainExpressionObject) as T;
|
|
672
|
+
}
|
|
673
|
+
} else {
|
|
674
|
+
constantValue = value;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
return { _type: "Constant", value: constantValue };
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
export function resolveBindingString<T extends PrimitiveType>(
|
|
681
|
+
value: string | boolean | number,
|
|
682
|
+
targetType?: string
|
|
683
|
+
): ConstantExpression<T> | PathInModelExpression<T> | EmbeddedUI5BindingExpression<T> | EmbeddedUI5ExpressionBindingExpression<T> {
|
|
684
|
+
if (value !== undefined && typeof value === "string" && value.startsWith("{")) {
|
|
685
|
+
const pathInModelRegex = /^{(.*)>(.+)}$/; // Matches model paths like "model>path" or ">path" (default model)
|
|
686
|
+
const pathInModelRegexMatch = pathInModelRegex.exec(value);
|
|
687
|
+
|
|
688
|
+
if (value.startsWith("{=")) {
|
|
689
|
+
// Expression binding, we can just remove the outer binding things
|
|
690
|
+
return {
|
|
691
|
+
_type: "EmbeddedExpressionBinding",
|
|
692
|
+
value: value
|
|
693
|
+
};
|
|
694
|
+
} else if (pathInModelRegexMatch) {
|
|
695
|
+
return pathInModel(pathInModelRegexMatch[2] || "", pathInModelRegexMatch[1] || undefined);
|
|
696
|
+
} else {
|
|
697
|
+
return {
|
|
698
|
+
_type: "EmbeddedBinding",
|
|
699
|
+
value: value
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
} else if (targetType === "boolean" && typeof value === "string" && (value === "true" || value === "false")) {
|
|
703
|
+
return constant(value === "true") as ConstantExpression<T>;
|
|
704
|
+
} else if (targetType === "number" && typeof value === "string" && (!isNaN(Number(value)) || value === "NaN")) {
|
|
705
|
+
return constant(Number(value)) as ConstantExpression<T>;
|
|
706
|
+
} else {
|
|
707
|
+
return constant(value) as ConstantExpression<T>;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* A named reference.
|
|
713
|
+
*
|
|
714
|
+
* @see fn
|
|
715
|
+
* @param reference Reference
|
|
716
|
+
* @returns The object reference binding part
|
|
717
|
+
*/
|
|
718
|
+
export function ref(reference: string | null): ReferenceExpression {
|
|
719
|
+
return { _type: "Ref", ref: reference };
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Wrap a primitive into a constant expression if it is not already an expression.
|
|
724
|
+
*
|
|
725
|
+
* @template T
|
|
726
|
+
* @param something The object to wrap in a Constant expression
|
|
727
|
+
* @returns Either the original object or the wrapped one depending on the case
|
|
728
|
+
*/
|
|
729
|
+
export function wrapPrimitive<T extends PrimitiveType>(something: ExpressionOrPrimitive<T>): BindingToolkitExpression<T> {
|
|
730
|
+
if (isBindingToolkitExpression(something)) {
|
|
731
|
+
return something as BindingToolkitExpression<T>;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
return constant(something);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Checks if the expression or value provided is a binding tooling expression or not.
|
|
739
|
+
*
|
|
740
|
+
* Every object having a property named `_type` of some value is considered an expression, even if there is actually
|
|
741
|
+
* no such expression type supported.
|
|
742
|
+
*
|
|
743
|
+
* @param expression
|
|
744
|
+
* @returns `true` if the expression is a binding toolkit expression
|
|
745
|
+
*/
|
|
746
|
+
export function isBindingToolkitExpression(
|
|
747
|
+
expression: BindingToolkitExpression<unknown> | unknown
|
|
748
|
+
): expression is BindingToolkitExpression<unknown> {
|
|
749
|
+
return (expression as BindingToolkitExpression<unknown>)?._type !== undefined;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Checks if the expression or value provided is constant or not.
|
|
754
|
+
*
|
|
755
|
+
* @template T The target type
|
|
756
|
+
* @param maybeConstant The expression or primitive value that is to be checked
|
|
757
|
+
* @returns `true` if it is constant
|
|
758
|
+
*/
|
|
759
|
+
export function isConstant<T extends PrimitiveType>(maybeConstant: ExpressionOrPrimitive<T>): maybeConstant is ConstantExpression<T> {
|
|
760
|
+
return typeof maybeConstant !== "object" || (maybeConstant as BaseExpression<T>)._type === "Constant";
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function isTrue(expression: BindingToolkitExpression<PrimitiveType>): boolean {
|
|
764
|
+
return isConstant(expression) && expression.value === true;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function isFalse(expression: BindingToolkitExpression<PrimitiveType>): boolean {
|
|
768
|
+
return isConstant(expression) && expression.value === false;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Checks if the expression or value provided is a path in model expression or not.
|
|
773
|
+
*
|
|
774
|
+
* @template T The target type
|
|
775
|
+
* @param maybeBinding The expression or primitive value that is to be checked
|
|
776
|
+
* @returns `true` if it is a path in model expression
|
|
777
|
+
*/
|
|
778
|
+
export function isPathInModelExpression<T extends PrimitiveType>(
|
|
779
|
+
maybeBinding: ExpressionOrPrimitive<T>
|
|
780
|
+
): maybeBinding is PathInModelExpression<T> {
|
|
781
|
+
return (maybeBinding as BaseExpression<T>)?._type === "PathInModel";
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* Checks if the expression or value provided is a complex type expression.
|
|
786
|
+
*
|
|
787
|
+
* @template T The target type
|
|
788
|
+
* @param maybeBinding The expression or primitive value that is to be checked
|
|
789
|
+
* @returns `true` if it is a path in model expression
|
|
790
|
+
*/
|
|
791
|
+
export function isComplexTypeExpression<T extends PrimitiveType>(
|
|
792
|
+
maybeBinding: ExpressionOrPrimitive<T>
|
|
793
|
+
): maybeBinding is ComplexTypeExpression<T> {
|
|
794
|
+
return (maybeBinding as BaseExpression<T>)?._type === "ComplexType";
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* Checks if the expression or value provided is a concat expression or not.
|
|
799
|
+
*
|
|
800
|
+
* @param expression
|
|
801
|
+
* @returns `true` if the expression is a ConcatExpression
|
|
802
|
+
*/
|
|
803
|
+
function isConcatExpression(expression: BindingToolkitExpression<PrimitiveType>): expression is ConcatExpression {
|
|
804
|
+
return (expression as BaseExpression<PrimitiveType>)?._type === "Concat";
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Checks if the expression or value provided is a IfElse expression or not.
|
|
809
|
+
*
|
|
810
|
+
* @param expression
|
|
811
|
+
* @returns `true` if the expression is a IfElseExpression
|
|
812
|
+
*/
|
|
813
|
+
function isIfElseExpression(expression: BindingToolkitExpression<PrimitiveType>): expression is ConcatExpression {
|
|
814
|
+
return (expression as BaseExpression<PrimitiveType>)?._type === "IfElse";
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* Checks if the expression provided is a comparison or not.
|
|
819
|
+
*
|
|
820
|
+
* @template T The target type
|
|
821
|
+
* @param expression The expression
|
|
822
|
+
* @returns `true` if the expression is a ComparisonExpression
|
|
823
|
+
*/
|
|
824
|
+
function isComparison<T extends PrimitiveType>(expression: BindingToolkitExpression<T>): expression is ComparisonExpression {
|
|
825
|
+
return expression._type === "Comparison";
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Checks whether the input parameter is a constant expression of type undefined.
|
|
830
|
+
*
|
|
831
|
+
* @param expression The input expression or object in general
|
|
832
|
+
* @returns `true` if the input is constant which has undefined for value
|
|
833
|
+
*/
|
|
834
|
+
export function isUndefinedExpression(expression: unknown): expression is ConstantExpression<undefined> {
|
|
835
|
+
const expressionAsExpression = expression as BindingToolkitExpression<unknown>;
|
|
836
|
+
return expressionAsExpression?._type === "Constant" && expressionAsExpression?.value === undefined;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
type ComplexAnnotationExpression<P> =
|
|
840
|
+
| PathAnnotationExpression<P>
|
|
841
|
+
| ApplyAnnotationExpression<P>
|
|
842
|
+
| IfAnnotationExpression<P>
|
|
843
|
+
| OrAnnotationExpression<P>
|
|
844
|
+
| AndAnnotationExpression<P>
|
|
845
|
+
| NeAnnotationExpression<P>
|
|
846
|
+
| EqAnnotationExpression<P>
|
|
847
|
+
| NotAnnotationExpression<P>
|
|
848
|
+
| GtAnnotationExpression<P>
|
|
849
|
+
| GeAnnotationExpression<P>
|
|
850
|
+
| LeAnnotationExpression<P>
|
|
851
|
+
| LtAnnotationExpression<P>;
|
|
852
|
+
|
|
853
|
+
function isPrimitiveObject(objectType: object): boolean {
|
|
854
|
+
switch (objectType.constructor.name) {
|
|
855
|
+
case "String":
|
|
856
|
+
case "Number":
|
|
857
|
+
case "Boolean":
|
|
858
|
+
return true;
|
|
859
|
+
default:
|
|
860
|
+
return false;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Check if the passed annotation annotationValue is a ComplexAnnotationExpression.
|
|
865
|
+
*
|
|
866
|
+
* @template T The target type
|
|
867
|
+
* @param annotationValue The annotation annotationValue to evaluate
|
|
868
|
+
* @returns `true` if the object is a {ComplexAnnotationExpression}
|
|
869
|
+
*/
|
|
870
|
+
function isComplexAnnotationExpression<T>(annotationValue: PropertyAnnotationValue<T>): annotationValue is ComplexAnnotationExpression<T> {
|
|
871
|
+
return typeof annotationValue === "object" && !isPrimitiveObject(annotationValue as object);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/**
|
|
875
|
+
* Generate the corresponding annotationValue for a given annotation annotationValue.
|
|
876
|
+
*
|
|
877
|
+
* @template T The target type
|
|
878
|
+
* @param annotationValue The source annotation annotationValue
|
|
879
|
+
* @param visitedNavigationPaths The path from the root entity set
|
|
880
|
+
* @param defaultValue Default value if the annotationValue is undefined
|
|
881
|
+
* @param pathVisitor A function to modify the resulting path
|
|
882
|
+
* @returns The annotationValue equivalent to that annotation annotationValue
|
|
883
|
+
*/
|
|
884
|
+
export function getExpressionFromAnnotation<T extends PrimitiveType>(
|
|
885
|
+
annotationValue: PropertyAnnotationValue<T> | undefined,
|
|
886
|
+
visitedNavigationPaths: string[] = [],
|
|
887
|
+
defaultValue?: ExpressionOrPrimitive<T>,
|
|
888
|
+
pathVisitor?: Function
|
|
889
|
+
): BindingToolkitExpression<PrimitiveTypeCast<T>> {
|
|
890
|
+
if (annotationValue === undefined) {
|
|
891
|
+
return wrapPrimitive(defaultValue as PrimitiveTypeCast<T>);
|
|
892
|
+
}
|
|
893
|
+
annotationValue = annotationValue?.valueOf() as PropertyAnnotationValue<T>;
|
|
894
|
+
if (!isComplexAnnotationExpression(annotationValue)) {
|
|
895
|
+
return constant(annotationValue);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
switch (annotationValue.type) {
|
|
899
|
+
case "Path":
|
|
900
|
+
return pathInModel(annotationValue.path, undefined, visitedNavigationPaths, pathVisitor);
|
|
901
|
+
case "If":
|
|
902
|
+
return annotationIfExpression(annotationValue.If, visitedNavigationPaths, pathVisitor) as BindingToolkitExpression<
|
|
903
|
+
PrimitiveTypeCast<T>
|
|
904
|
+
>;
|
|
905
|
+
case "Not":
|
|
906
|
+
return not(parseAnnotationCondition(annotationValue.Not, visitedNavigationPaths, pathVisitor)) as BindingToolkitExpression<
|
|
907
|
+
PrimitiveTypeCast<T>
|
|
908
|
+
>;
|
|
909
|
+
case "Eq":
|
|
910
|
+
return equal(
|
|
911
|
+
parseAnnotationCondition(annotationValue.Eq[0], visitedNavigationPaths, pathVisitor),
|
|
912
|
+
parseAnnotationCondition(annotationValue.Eq[1], visitedNavigationPaths, pathVisitor)
|
|
913
|
+
) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
|
|
914
|
+
case "Ne":
|
|
915
|
+
return notEqual(
|
|
916
|
+
parseAnnotationCondition(annotationValue.Ne[0], visitedNavigationPaths, pathVisitor),
|
|
917
|
+
parseAnnotationCondition(annotationValue.Ne[1], visitedNavigationPaths, pathVisitor)
|
|
918
|
+
) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
|
|
919
|
+
case "Gt":
|
|
920
|
+
return greaterThan(
|
|
921
|
+
parseAnnotationCondition(annotationValue.Gt[0], visitedNavigationPaths, pathVisitor),
|
|
922
|
+
parseAnnotationCondition(annotationValue.Gt[1], visitedNavigationPaths, pathVisitor)
|
|
923
|
+
) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
|
|
924
|
+
case "Ge":
|
|
925
|
+
return greaterOrEqual(
|
|
926
|
+
parseAnnotationCondition(annotationValue.Ge[0], visitedNavigationPaths, pathVisitor),
|
|
927
|
+
parseAnnotationCondition(annotationValue.Ge[1], visitedNavigationPaths, pathVisitor)
|
|
928
|
+
) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
|
|
929
|
+
case "Lt":
|
|
930
|
+
return lessThan(
|
|
931
|
+
parseAnnotationCondition(annotationValue.Lt[0], visitedNavigationPaths, pathVisitor),
|
|
932
|
+
parseAnnotationCondition(annotationValue.Lt[1], visitedNavigationPaths, pathVisitor)
|
|
933
|
+
) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
|
|
934
|
+
case "Le":
|
|
935
|
+
return lessOrEqual(
|
|
936
|
+
parseAnnotationCondition(annotationValue.Le[0], visitedNavigationPaths, pathVisitor),
|
|
937
|
+
parseAnnotationCondition(annotationValue.Le[1], visitedNavigationPaths, pathVisitor)
|
|
938
|
+
) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
|
|
939
|
+
case "Or":
|
|
940
|
+
return or(
|
|
941
|
+
...annotationValue.Or.map(function (orCondition) {
|
|
942
|
+
return parseAnnotationCondition<boolean>(orCondition, visitedNavigationPaths, pathVisitor);
|
|
943
|
+
})
|
|
944
|
+
) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
|
|
945
|
+
case "And":
|
|
946
|
+
return and(
|
|
947
|
+
...annotationValue.And.map(function (andCondition) {
|
|
948
|
+
return parseAnnotationCondition<boolean>(andCondition, visitedNavigationPaths, pathVisitor);
|
|
949
|
+
})
|
|
950
|
+
) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
|
|
951
|
+
case "Apply":
|
|
952
|
+
return annotationApplyExpression(
|
|
953
|
+
annotationValue as ApplyAnnotationExpression<string>,
|
|
954
|
+
visitedNavigationPaths,
|
|
955
|
+
pathVisitor
|
|
956
|
+
) as BindingToolkitExpression<PrimitiveTypeCast<T>>;
|
|
957
|
+
}
|
|
958
|
+
return unresolvableExpression;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
/**
|
|
962
|
+
* Parse the annotation condition into an expression.
|
|
963
|
+
*
|
|
964
|
+
* @template T The target type
|
|
965
|
+
* @param annotationValue The condition or value from the annotation
|
|
966
|
+
* @param visitedNavigationPaths The path from the root entity set
|
|
967
|
+
* @param pathVisitor A function to modify the resulting path
|
|
968
|
+
* @returns An equivalent expression
|
|
969
|
+
*/
|
|
970
|
+
function parseAnnotationCondition<T extends PrimitiveType>(
|
|
971
|
+
annotationValue: ConditionalCheckOrValue,
|
|
972
|
+
visitedNavigationPaths: string[] = [],
|
|
973
|
+
pathVisitor?: Function
|
|
974
|
+
): BindingToolkitExpression<T> {
|
|
975
|
+
if (annotationValue === null || typeof annotationValue !== "object") {
|
|
976
|
+
return constant(annotationValue as T);
|
|
977
|
+
} else if (annotationValue.hasOwnProperty("$Or")) {
|
|
978
|
+
return or(
|
|
979
|
+
...((annotationValue as OrConditionalExpression).$Or.map(function (orCondition) {
|
|
980
|
+
return parseAnnotationCondition(orCondition, visitedNavigationPaths, pathVisitor);
|
|
981
|
+
}) as unknown as BindingToolkitExpression<boolean>[])
|
|
982
|
+
) as BindingToolkitExpression<T>;
|
|
983
|
+
} else if (annotationValue.hasOwnProperty("$And")) {
|
|
984
|
+
return and(
|
|
985
|
+
...((annotationValue as AndConditionalExpression).$And.map(function (andCondition) {
|
|
986
|
+
return parseAnnotationCondition(andCondition, visitedNavigationPaths, pathVisitor);
|
|
987
|
+
}) as unknown as BindingToolkitExpression<boolean>[])
|
|
988
|
+
) as BindingToolkitExpression<T>;
|
|
989
|
+
} else if (annotationValue.hasOwnProperty("$Not")) {
|
|
990
|
+
return not(
|
|
991
|
+
parseAnnotationCondition((annotationValue as NotConditionalExpression).$Not, visitedNavigationPaths, pathVisitor)
|
|
992
|
+
) as BindingToolkitExpression<T>;
|
|
993
|
+
} else if (annotationValue.hasOwnProperty("$Eq")) {
|
|
994
|
+
return equal(
|
|
995
|
+
parseAnnotationCondition((annotationValue as EqConditionalExpression).$Eq[0], visitedNavigationPaths, pathVisitor),
|
|
996
|
+
parseAnnotationCondition((annotationValue as EqConditionalExpression).$Eq[1], visitedNavigationPaths, pathVisitor)
|
|
997
|
+
) as BindingToolkitExpression<T>;
|
|
998
|
+
} else if (annotationValue.hasOwnProperty("$Ne")) {
|
|
999
|
+
return notEqual(
|
|
1000
|
+
parseAnnotationCondition((annotationValue as NeConditionalExpression).$Ne[0], visitedNavigationPaths, pathVisitor),
|
|
1001
|
+
parseAnnotationCondition((annotationValue as NeConditionalExpression).$Ne[1], visitedNavigationPaths, pathVisitor)
|
|
1002
|
+
) as BindingToolkitExpression<T>;
|
|
1003
|
+
} else if (annotationValue.hasOwnProperty("$Gt")) {
|
|
1004
|
+
return greaterThan(
|
|
1005
|
+
parseAnnotationCondition((annotationValue as GtConditionalExpression).$Gt[0], visitedNavigationPaths, pathVisitor),
|
|
1006
|
+
parseAnnotationCondition((annotationValue as GtConditionalExpression).$Gt[1], visitedNavigationPaths, pathVisitor)
|
|
1007
|
+
) as BindingToolkitExpression<T>;
|
|
1008
|
+
} else if (annotationValue.hasOwnProperty("$Ge")) {
|
|
1009
|
+
return greaterOrEqual(
|
|
1010
|
+
parseAnnotationCondition((annotationValue as GeConditionalExpression).$Ge[0], visitedNavigationPaths, pathVisitor),
|
|
1011
|
+
parseAnnotationCondition((annotationValue as GeConditionalExpression).$Ge[1], visitedNavigationPaths, pathVisitor)
|
|
1012
|
+
) as BindingToolkitExpression<T>;
|
|
1013
|
+
} else if (annotationValue.hasOwnProperty("$Lt")) {
|
|
1014
|
+
return lessThan(
|
|
1015
|
+
parseAnnotationCondition((annotationValue as LtConditionalExpression).$Lt[0], visitedNavigationPaths, pathVisitor),
|
|
1016
|
+
parseAnnotationCondition((annotationValue as LtConditionalExpression).$Lt[1], visitedNavigationPaths, pathVisitor)
|
|
1017
|
+
) as BindingToolkitExpression<T>;
|
|
1018
|
+
} else if (annotationValue.hasOwnProperty("$Le")) {
|
|
1019
|
+
return lessOrEqual(
|
|
1020
|
+
parseAnnotationCondition((annotationValue as LeConditionalExpression).$Le[0], visitedNavigationPaths, pathVisitor),
|
|
1021
|
+
parseAnnotationCondition((annotationValue as LeConditionalExpression).$Le[1], visitedNavigationPaths, pathVisitor)
|
|
1022
|
+
) as BindingToolkitExpression<T>;
|
|
1023
|
+
} else if (annotationValue.hasOwnProperty("$Path")) {
|
|
1024
|
+
return pathInModel((annotationValue as PathConditionExpression<T>).$Path, undefined, visitedNavigationPaths, pathVisitor);
|
|
1025
|
+
} else if (annotationValue.hasOwnProperty("$Apply")) {
|
|
1026
|
+
return getExpressionFromAnnotation(
|
|
1027
|
+
{
|
|
1028
|
+
type: "Apply",
|
|
1029
|
+
Function: (annotationValue as { $Function?: string }).$Function,
|
|
1030
|
+
Apply: (annotationValue as { $Apply?: unknown }).$Apply
|
|
1031
|
+
} as ApplyAnnotationExpression<T>,
|
|
1032
|
+
visitedNavigationPaths,
|
|
1033
|
+
undefined,
|
|
1034
|
+
pathVisitor
|
|
1035
|
+
) as BindingToolkitExpression<T>;
|
|
1036
|
+
} else if (annotationValue.hasOwnProperty("$If")) {
|
|
1037
|
+
return getExpressionFromAnnotation(
|
|
1038
|
+
{
|
|
1039
|
+
type: "If",
|
|
1040
|
+
If: (annotationValue as { $If?: unknown }).$If
|
|
1041
|
+
} as IfAnnotationExpression<T>,
|
|
1042
|
+
visitedNavigationPaths,
|
|
1043
|
+
undefined,
|
|
1044
|
+
pathVisitor
|
|
1045
|
+
) as BindingToolkitExpression<T>;
|
|
1046
|
+
} else if (annotationValue.hasOwnProperty("$EnumMember")) {
|
|
1047
|
+
return constant(resolveEnumValue((annotationValue as { $EnumMember?: string }).$EnumMember) as T);
|
|
1048
|
+
}
|
|
1049
|
+
return constant(false as T);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/**
|
|
1053
|
+
* Process the {IfAnnotationExpressionValue} into an expression.
|
|
1054
|
+
*
|
|
1055
|
+
* @template T The target type
|
|
1056
|
+
* @param annotationValue An If expression returning the type T
|
|
1057
|
+
* @param visitedNavigationPaths The path from the root entity set
|
|
1058
|
+
* @param pathVisitor A function to modify the resulting path
|
|
1059
|
+
* @returns The equivalent ifElse expression
|
|
1060
|
+
*/
|
|
1061
|
+
export function annotationIfExpression<T extends PrimitiveType>(
|
|
1062
|
+
annotationValue: IfAnnotationExpressionValue<T>,
|
|
1063
|
+
visitedNavigationPaths: string[] = [],
|
|
1064
|
+
pathVisitor?: Function
|
|
1065
|
+
): BindingToolkitExpression<T> {
|
|
1066
|
+
return ifElse(
|
|
1067
|
+
parseAnnotationCondition(annotationValue[0], visitedNavigationPaths, pathVisitor),
|
|
1068
|
+
parseAnnotationCondition(annotationValue[1] as ConditionalCheckOrValue, visitedNavigationPaths, pathVisitor),
|
|
1069
|
+
parseAnnotationCondition(annotationValue[2] as ConditionalCheckOrValue, visitedNavigationPaths, pathVisitor)
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
// This type is not recursively transformed from the metamodel content, as such we have some ugly things there
|
|
1073
|
+
type SubApplyExpressionFromMetamodel = Record<string, unknown>;
|
|
1074
|
+
function convertSubApplyParameters(applyParam: SubApplyExpressionFromMetamodel): SubApplyExpressionFromMetamodel {
|
|
1075
|
+
let applyParamConverted = applyParam;
|
|
1076
|
+
if (applyParam.hasOwnProperty("$Path")) {
|
|
1077
|
+
applyParamConverted = {
|
|
1078
|
+
type: "Path",
|
|
1079
|
+
path: applyParam.$Path
|
|
1080
|
+
} as PathAnnotationExpression<string>;
|
|
1081
|
+
} else if (applyParam.hasOwnProperty("$If")) {
|
|
1082
|
+
applyParamConverted = {
|
|
1083
|
+
type: "If",
|
|
1084
|
+
If: applyParam.$If
|
|
1085
|
+
} as IfAnnotationExpression<string>;
|
|
1086
|
+
} else if (applyParam.hasOwnProperty("$Apply")) {
|
|
1087
|
+
applyParamConverted = {
|
|
1088
|
+
type: "Apply",
|
|
1089
|
+
Function: applyParam.$Function,
|
|
1090
|
+
Apply: applyParam.$Apply
|
|
1091
|
+
} as ApplyAnnotationExpression<string>;
|
|
1092
|
+
}
|
|
1093
|
+
return applyParamConverted;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
type ODataFunction = "odata.concat" | "odata.fillUriTemplate" | "odata.uriEncode";
|
|
1097
|
+
export function annotationApplyExpression(
|
|
1098
|
+
applyExpression: ApplyAnnotationExpression<string>,
|
|
1099
|
+
visitedNavigationPaths: string[] = [],
|
|
1100
|
+
pathVisitor?: Function
|
|
1101
|
+
): BindingToolkitExpression<string> {
|
|
1102
|
+
switch (applyExpression.Function as ODataFunction) {
|
|
1103
|
+
case "odata.concat":
|
|
1104
|
+
return concat(
|
|
1105
|
+
...applyExpression.Apply.map((applyParam) => {
|
|
1106
|
+
return getExpressionFromAnnotation(
|
|
1107
|
+
convertSubApplyParameters(applyParam) as PropertyAnnotationValue<string>,
|
|
1108
|
+
visitedNavigationPaths,
|
|
1109
|
+
undefined,
|
|
1110
|
+
pathVisitor
|
|
1111
|
+
);
|
|
1112
|
+
})
|
|
1113
|
+
);
|
|
1114
|
+
case "odata.uriEncode":
|
|
1115
|
+
const parameter = getExpressionFromAnnotation(
|
|
1116
|
+
convertSubApplyParameters(
|
|
1117
|
+
applyExpression.Apply[0] as unknown as SubApplyExpressionFromMetamodel
|
|
1118
|
+
) as PropertyAnnotationValue<string>,
|
|
1119
|
+
visitedNavigationPaths,
|
|
1120
|
+
undefined,
|
|
1121
|
+
pathVisitor
|
|
1122
|
+
);
|
|
1123
|
+
// The second parameter for uriEncode is always a string since the target evaluation is against a formatValue call in ODataUtils which expect the target type as second parameter
|
|
1124
|
+
return fn("odata.uriEncode", [parameter, "Edm.String"], undefined, true);
|
|
1125
|
+
case "odata.fillUriTemplate":
|
|
1126
|
+
const template = applyExpression.Apply[0];
|
|
1127
|
+
const templateParams = applyExpression.Apply.slice(1) as unknown as SubApplyExpressionFromMetamodel[];
|
|
1128
|
+
const targetObject: Record<string, BindingToolkitExpression<unknown>> = {};
|
|
1129
|
+
templateParams.forEach((applyParam) => {
|
|
1130
|
+
targetObject[applyParam.$Name as string] = getExpressionFromAnnotation(
|
|
1131
|
+
convertSubApplyParameters(
|
|
1132
|
+
applyParam.$LabeledElement as SubApplyExpressionFromMetamodel
|
|
1133
|
+
) as PropertyAnnotationValue<string>,
|
|
1134
|
+
visitedNavigationPaths,
|
|
1135
|
+
undefined,
|
|
1136
|
+
pathVisitor
|
|
1137
|
+
);
|
|
1138
|
+
});
|
|
1139
|
+
return fn("odata.fillUriTemplate", [template, targetObject], undefined, true);
|
|
1140
|
+
}
|
|
1141
|
+
return unresolvableExpression;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
/**
|
|
1145
|
+
* Generic helper for the comparison operations (equal, notEqual, ...).
|
|
1146
|
+
*
|
|
1147
|
+
* @template T The target type
|
|
1148
|
+
* @param operator The operator to apply
|
|
1149
|
+
* @param leftOperand The operand on the left side of the operator
|
|
1150
|
+
* @param rightOperand The operand on the right side of the operator
|
|
1151
|
+
* @returns An expression representing the comparison
|
|
1152
|
+
*/
|
|
1153
|
+
function comparison<T extends PrimitiveType>(
|
|
1154
|
+
operator: ComparisonOperator,
|
|
1155
|
+
leftOperand: ExpressionOrPrimitive<T>,
|
|
1156
|
+
rightOperand: ExpressionOrPrimitive<T>
|
|
1157
|
+
): BindingToolkitExpression<boolean> {
|
|
1158
|
+
const leftExpression = wrapPrimitive(leftOperand);
|
|
1159
|
+
const rightExpression = wrapPrimitive(rightOperand);
|
|
1160
|
+
if (hasUnresolvableExpression(leftExpression, rightExpression)) {
|
|
1161
|
+
return unresolvableExpression;
|
|
1162
|
+
}
|
|
1163
|
+
if (isConstant(leftExpression) && isConstant(rightExpression)) {
|
|
1164
|
+
switch (operator) {
|
|
1165
|
+
case "!==":
|
|
1166
|
+
return constant(leftExpression.value !== rightExpression.value);
|
|
1167
|
+
case "===":
|
|
1168
|
+
return constant(leftExpression.value === rightExpression.value);
|
|
1169
|
+
case "<":
|
|
1170
|
+
if (
|
|
1171
|
+
leftExpression.value === null ||
|
|
1172
|
+
leftExpression.value === undefined ||
|
|
1173
|
+
rightExpression.value === null ||
|
|
1174
|
+
rightExpression.value === undefined
|
|
1175
|
+
) {
|
|
1176
|
+
return constant(false);
|
|
1177
|
+
}
|
|
1178
|
+
return constant(leftExpression.value < rightExpression.value);
|
|
1179
|
+
case "<=":
|
|
1180
|
+
if (
|
|
1181
|
+
leftExpression.value === null ||
|
|
1182
|
+
leftExpression.value === undefined ||
|
|
1183
|
+
rightExpression.value === null ||
|
|
1184
|
+
rightExpression.value === undefined
|
|
1185
|
+
) {
|
|
1186
|
+
return constant(false);
|
|
1187
|
+
}
|
|
1188
|
+
return constant(leftExpression.value <= rightExpression.value);
|
|
1189
|
+
case ">":
|
|
1190
|
+
if (
|
|
1191
|
+
leftExpression.value === null ||
|
|
1192
|
+
leftExpression.value === undefined ||
|
|
1193
|
+
rightExpression.value === null ||
|
|
1194
|
+
rightExpression.value === undefined
|
|
1195
|
+
) {
|
|
1196
|
+
return constant(false);
|
|
1197
|
+
}
|
|
1198
|
+
return constant(leftExpression.value > rightExpression.value);
|
|
1199
|
+
case ">=":
|
|
1200
|
+
if (
|
|
1201
|
+
leftExpression.value === null ||
|
|
1202
|
+
leftExpression.value === undefined ||
|
|
1203
|
+
rightExpression.value === null ||
|
|
1204
|
+
rightExpression.value === undefined
|
|
1205
|
+
) {
|
|
1206
|
+
return constant(false);
|
|
1207
|
+
}
|
|
1208
|
+
return constant(leftExpression.value >= rightExpression.value);
|
|
1209
|
+
}
|
|
1210
|
+
} else {
|
|
1211
|
+
return {
|
|
1212
|
+
_type: "Comparison",
|
|
1213
|
+
operator: operator,
|
|
1214
|
+
operand1: leftExpression,
|
|
1215
|
+
operand2: rightExpression
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
/**
|
|
1221
|
+
* Generic helper for the length of an expression.
|
|
1222
|
+
*
|
|
1223
|
+
* @param expression The input expression pointing to an array
|
|
1224
|
+
* @param checkUndefined Is the array potentially undefined
|
|
1225
|
+
* @returns An expression representing the length
|
|
1226
|
+
*/
|
|
1227
|
+
export function length(
|
|
1228
|
+
expression: PathInModelExpression<unknown> | UnresolvablePathExpression,
|
|
1229
|
+
checkUndefined = false
|
|
1230
|
+
): BindingToolkitExpression<number> {
|
|
1231
|
+
if (expression._type === "Unresolvable") {
|
|
1232
|
+
return expression;
|
|
1233
|
+
}
|
|
1234
|
+
if (!checkUndefined) {
|
|
1235
|
+
return {
|
|
1236
|
+
_type: "Length",
|
|
1237
|
+
pathInModel: expression
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
return ifElse(equal(expression, undefined), -1, length(expression));
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
/**
|
|
1244
|
+
* Comparison: "equal" (===).
|
|
1245
|
+
*
|
|
1246
|
+
* @template T The target type
|
|
1247
|
+
* @param leftOperand The operand on the left side
|
|
1248
|
+
* @param rightOperand The operand on the right side of the comparison
|
|
1249
|
+
* @returns An expression representing the comparison
|
|
1250
|
+
*/
|
|
1251
|
+
export function equal<T extends PrimitiveType>(
|
|
1252
|
+
leftOperand: ExpressionOrPrimitive<T>,
|
|
1253
|
+
rightOperand: ExpressionOrPrimitive<T>
|
|
1254
|
+
): BindingToolkitExpression<boolean> {
|
|
1255
|
+
const leftExpression = wrapPrimitive(leftOperand);
|
|
1256
|
+
const rightExpression = wrapPrimitive(rightOperand);
|
|
1257
|
+
if (hasUnresolvableExpression(leftExpression, rightExpression)) {
|
|
1258
|
+
return unresolvableExpression;
|
|
1259
|
+
}
|
|
1260
|
+
if (_checkExpressionsAreEqual(leftExpression, rightExpression)) {
|
|
1261
|
+
return constant(true);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
function reduce(left: BindingToolkitExpression<T>, right: BindingToolkitExpression<T>): BindingToolkitExpression<boolean> | undefined {
|
|
1265
|
+
if (left._type === "Comparison" && isTrue(right)) {
|
|
1266
|
+
// compare(a, b) === true ~~> compare(a, b)
|
|
1267
|
+
return left;
|
|
1268
|
+
} else if (left._type === "Comparison" && isFalse(right)) {
|
|
1269
|
+
// compare(a, b) === false ~~> !compare(a, b)
|
|
1270
|
+
return not(left);
|
|
1271
|
+
} else if (left._type === "IfElse" && _checkExpressionsAreEqual(left.onTrue, right)) {
|
|
1272
|
+
// (if (x) { a } else { b }) === a ~~> x || (b === a)
|
|
1273
|
+
return or(left.condition, equal(left.onFalse, right));
|
|
1274
|
+
} else if (left._type === "IfElse" && _checkExpressionsAreEqual(left.onFalse, right)) {
|
|
1275
|
+
// (if (x) { a } else { b }) === b ~~> !x || (a === b)
|
|
1276
|
+
return or(not(left.condition), equal(left.onTrue, right));
|
|
1277
|
+
} else if (
|
|
1278
|
+
left._type === "IfElse" &&
|
|
1279
|
+
isConstant(left.onTrue) &&
|
|
1280
|
+
isConstant(left.onFalse) &&
|
|
1281
|
+
isConstant(right) &&
|
|
1282
|
+
!_checkExpressionsAreEqual(left.onTrue, right) &&
|
|
1283
|
+
!_checkExpressionsAreEqual(left.onFalse, right)
|
|
1284
|
+
) {
|
|
1285
|
+
return constant(false);
|
|
1286
|
+
}
|
|
1287
|
+
return undefined;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// exploit symmetry: a === b <~> b === a
|
|
1291
|
+
const reduced = reduce(leftExpression, rightExpression) ?? reduce(rightExpression, leftExpression);
|
|
1292
|
+
return reduced ?? comparison("===", leftExpression, rightExpression);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
/**
|
|
1296
|
+
* Comparison: "not equal" (!==).
|
|
1297
|
+
*
|
|
1298
|
+
* @template T The target type
|
|
1299
|
+
* @param leftOperand The operand on the left side
|
|
1300
|
+
* @param rightOperand The operand on the right side of the comparison
|
|
1301
|
+
* @returns An expression representing the comparison
|
|
1302
|
+
*/
|
|
1303
|
+
export function notEqual<T extends PrimitiveType>(
|
|
1304
|
+
leftOperand: ExpressionOrPrimitive<T>,
|
|
1305
|
+
rightOperand: ExpressionOrPrimitive<T>
|
|
1306
|
+
): BindingToolkitExpression<boolean> {
|
|
1307
|
+
return not(equal(leftOperand, rightOperand));
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
/**
|
|
1311
|
+
* Comparison: "greater or equal" (>=).
|
|
1312
|
+
*
|
|
1313
|
+
* @template T The target type
|
|
1314
|
+
* @param leftOperand The operand on the left side
|
|
1315
|
+
* @param rightOperand The operand on the right side of the comparison
|
|
1316
|
+
* @returns An expression representing the comparison
|
|
1317
|
+
*/
|
|
1318
|
+
export function greaterOrEqual<T extends DefinedPrimitiveType>(
|
|
1319
|
+
leftOperand: ExpressionOrPrimitive<T>,
|
|
1320
|
+
rightOperand: ExpressionOrPrimitive<T>
|
|
1321
|
+
): BindingToolkitExpression<boolean> {
|
|
1322
|
+
return comparison(">=", leftOperand, rightOperand);
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
/**
|
|
1326
|
+
* Comparison: "greater than" (>).
|
|
1327
|
+
*
|
|
1328
|
+
* @template T The target type
|
|
1329
|
+
* @param leftOperand The operand on the left side
|
|
1330
|
+
* @param rightOperand The operand on the right side of the comparison
|
|
1331
|
+
* @returns An expression representing the comparison
|
|
1332
|
+
*/
|
|
1333
|
+
export function greaterThan<T extends DefinedPrimitiveType>(
|
|
1334
|
+
leftOperand: ExpressionOrPrimitive<T>,
|
|
1335
|
+
rightOperand: ExpressionOrPrimitive<T>
|
|
1336
|
+
): BindingToolkitExpression<boolean> {
|
|
1337
|
+
return comparison(">", leftOperand, rightOperand);
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
/**
|
|
1341
|
+
* Comparison: "less or equal" (<=).
|
|
1342
|
+
*
|
|
1343
|
+
* @template T The target type
|
|
1344
|
+
* @param leftOperand The operand on the left side
|
|
1345
|
+
* @param rightOperand The operand on the right side of the comparison
|
|
1346
|
+
* @returns An expression representing the comparison
|
|
1347
|
+
*/
|
|
1348
|
+
export function lessOrEqual<T extends DefinedPrimitiveType>(
|
|
1349
|
+
leftOperand: ExpressionOrPrimitive<T>,
|
|
1350
|
+
rightOperand: ExpressionOrPrimitive<T>
|
|
1351
|
+
): BindingToolkitExpression<boolean> {
|
|
1352
|
+
return comparison("<=", leftOperand, rightOperand);
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
/**
|
|
1356
|
+
* Comparison: "less than" (<).
|
|
1357
|
+
*
|
|
1358
|
+
* @template T The target type
|
|
1359
|
+
* @param leftOperand The operand on the left side
|
|
1360
|
+
* @param rightOperand The operand on the right side of the comparison
|
|
1361
|
+
* @returns An expression representing the comparison
|
|
1362
|
+
*/
|
|
1363
|
+
export function lessThan<T extends DefinedPrimitiveType>(
|
|
1364
|
+
leftOperand: ExpressionOrPrimitive<T>,
|
|
1365
|
+
rightOperand: ExpressionOrPrimitive<T>
|
|
1366
|
+
): BindingToolkitExpression<boolean> {
|
|
1367
|
+
return comparison("<", leftOperand, rightOperand);
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
/**
|
|
1371
|
+
* If-then-else expression.
|
|
1372
|
+
*
|
|
1373
|
+
* Evaluates to onTrue if the condition evaluates to true, else evaluates to onFalse.
|
|
1374
|
+
*
|
|
1375
|
+
* @template T The target type
|
|
1376
|
+
* @param condition The condition to evaluate
|
|
1377
|
+
* @param onTrue Expression result if the condition evaluates to true
|
|
1378
|
+
* @param onFalse Expression result if the condition evaluates to false
|
|
1379
|
+
* @returns The expression that represents this conditional check
|
|
1380
|
+
*/
|
|
1381
|
+
export function ifElse<T extends PrimitiveType>(
|
|
1382
|
+
condition: ExpressionOrPrimitive<boolean>,
|
|
1383
|
+
onTrue: ExpressionOrPrimitive<T>,
|
|
1384
|
+
onFalse: ExpressionOrPrimitive<T>
|
|
1385
|
+
): BindingToolkitExpression<T> {
|
|
1386
|
+
let conditionExpression = wrapPrimitive(condition);
|
|
1387
|
+
let onTrueExpression = wrapPrimitive(onTrue);
|
|
1388
|
+
let onFalseExpression = wrapPrimitive(onFalse);
|
|
1389
|
+
|
|
1390
|
+
// swap branches if the condition is a negation
|
|
1391
|
+
if (conditionExpression._type === "Not") {
|
|
1392
|
+
// ifElse(not(X), a, b) --> ifElse(X, b, a)
|
|
1393
|
+
[onTrueExpression, onFalseExpression] = [onFalseExpression, onTrueExpression];
|
|
1394
|
+
conditionExpression = not(conditionExpression);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// inline nested if-else expressions: onTrue branch
|
|
1398
|
+
// ifElse(X, ifElse(X, a, b), c) ==> ifElse(X, a, c)
|
|
1399
|
+
if (onTrueExpression._type === "IfElse" && _checkExpressionsAreEqual(conditionExpression, onTrueExpression.condition)) {
|
|
1400
|
+
onTrueExpression = onTrueExpression.onTrue;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// inline nested if-else expressions: onFalse branch
|
|
1404
|
+
// ifElse(X, a, ifElse(X, b, c)) ==> ifElse(X, a, c)
|
|
1405
|
+
if (onFalseExpression._type === "IfElse" && _checkExpressionsAreEqual(conditionExpression, onFalseExpression.condition)) {
|
|
1406
|
+
onFalseExpression = onFalseExpression.onFalse;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
// (if true then a else b) ~~> a
|
|
1410
|
+
// (if false then a else b) ~~> b
|
|
1411
|
+
if (isConstant(conditionExpression)) {
|
|
1412
|
+
return conditionExpression.value ? onTrueExpression : onFalseExpression;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
// if (isConstantBoolean(onTrueExpression) || isConstantBoolean(onFalseExpression)) {
|
|
1416
|
+
// return or(and(condition, onTrueExpression as Expression<boolean>), and(not(condition), onFalseExpression as Expression<boolean>)) as Expression<T>
|
|
1417
|
+
// }
|
|
1418
|
+
|
|
1419
|
+
// (if X then a else a) ~~> a
|
|
1420
|
+
if (_checkExpressionsAreEqual(onTrueExpression, onFalseExpression)) {
|
|
1421
|
+
return onTrueExpression;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
// if X then a else false ~~> X && a
|
|
1425
|
+
if (isFalse(onFalseExpression)) {
|
|
1426
|
+
return and(conditionExpression, onTrueExpression as BindingToolkitExpression<boolean>) as BindingToolkitExpression<T>;
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
// if X then a else true ~~> !X || a
|
|
1430
|
+
if (isTrue(onFalseExpression)) {
|
|
1431
|
+
return or(not(conditionExpression), onTrueExpression as BindingToolkitExpression<boolean>) as BindingToolkitExpression<T>;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
// if X then false else a ~~> !X && a
|
|
1435
|
+
if (isFalse(onTrueExpression)) {
|
|
1436
|
+
return and(not(conditionExpression), onFalseExpression as BindingToolkitExpression<boolean>) as BindingToolkitExpression<T>;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
// if X then true else a ~~> X || a
|
|
1440
|
+
if (isTrue(onTrueExpression)) {
|
|
1441
|
+
return or(conditionExpression, onFalseExpression as BindingToolkitExpression<boolean>) as BindingToolkitExpression<T>;
|
|
1442
|
+
}
|
|
1443
|
+
if (hasUnresolvableExpression(conditionExpression, onTrueExpression, onFalseExpression)) {
|
|
1444
|
+
return unresolvableExpression;
|
|
1445
|
+
}
|
|
1446
|
+
if (isComplexTypeExpression(condition) || isComplexTypeExpression(onTrue) || isComplexTypeExpression(onFalse)) {
|
|
1447
|
+
let pathIdx = 0;
|
|
1448
|
+
const myIfElseExpression = formatResult([condition, onTrue, onFalse], "sap.fe.core.formatters.StandardFormatter#ifElse");
|
|
1449
|
+
const allParts = [];
|
|
1450
|
+
transformRecursively(
|
|
1451
|
+
myIfElseExpression,
|
|
1452
|
+
"PathInModel",
|
|
1453
|
+
(constantPath: PathInModelExpression<unknown>) => {
|
|
1454
|
+
allParts.push(constantPath);
|
|
1455
|
+
return pathInModel(`$${pathIdx++}`, "$");
|
|
1456
|
+
},
|
|
1457
|
+
true
|
|
1458
|
+
);
|
|
1459
|
+
allParts.unshift(constant(JSON.stringify(myIfElseExpression)));
|
|
1460
|
+
return formatResult(allParts, "sap.fe.core.formatters.StandardFormatter#evaluateComplexExpression", undefined, true);
|
|
1461
|
+
}
|
|
1462
|
+
return {
|
|
1463
|
+
_type: "IfElse",
|
|
1464
|
+
condition: conditionExpression,
|
|
1465
|
+
onTrue: onTrueExpression,
|
|
1466
|
+
onFalse: onFalseExpression
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
/**
|
|
1471
|
+
* Checks whether the current expression has a reference to the default model (undefined).
|
|
1472
|
+
*
|
|
1473
|
+
* @param expression The expression to evaluate
|
|
1474
|
+
* @returns `true` if there is a reference to the default context
|
|
1475
|
+
*/
|
|
1476
|
+
function hasReferenceToDefaultContext(expression: BindingToolkitExpression<unknown>): boolean {
|
|
1477
|
+
switch (expression._type) {
|
|
1478
|
+
case "Constant":
|
|
1479
|
+
case "Formatter":
|
|
1480
|
+
case "ComplexType":
|
|
1481
|
+
return false;
|
|
1482
|
+
case "Set":
|
|
1483
|
+
return expression.operands.some(hasReferenceToDefaultContext);
|
|
1484
|
+
case "PathInModel":
|
|
1485
|
+
return expression.modelName === undefined;
|
|
1486
|
+
case "Comparison":
|
|
1487
|
+
return hasReferenceToDefaultContext(expression.operand1) || hasReferenceToDefaultContext(expression.operand2);
|
|
1488
|
+
case "IfElse":
|
|
1489
|
+
return (
|
|
1490
|
+
hasReferenceToDefaultContext(expression.condition) ||
|
|
1491
|
+
hasReferenceToDefaultContext(expression.onTrue) ||
|
|
1492
|
+
hasReferenceToDefaultContext(expression.onFalse)
|
|
1493
|
+
);
|
|
1494
|
+
case "Not":
|
|
1495
|
+
case "Truthy":
|
|
1496
|
+
return hasReferenceToDefaultContext(expression.operand);
|
|
1497
|
+
default:
|
|
1498
|
+
return false;
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
// This is one case where any does make sense...
|
|
1503
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1504
|
+
export type Fn<T> = ((...params: any) => T | Promise<T>) & {
|
|
1505
|
+
__functionName: string;
|
|
1506
|
+
};
|
|
1507
|
+
|
|
1508
|
+
/**
|
|
1509
|
+
* @typedef WrappedTuple
|
|
1510
|
+
*/
|
|
1511
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
1512
|
+
// @ts-ignore
|
|
1513
|
+
type WrappedTuple<T> = { [K in keyof T]: WrappedTuple<T[K]> | ExpressionOrPrimitive<T[K]> | undefined };
|
|
1514
|
+
|
|
1515
|
+
// So, this works but I cannot get it to compile :D, but it still does what is expected...
|
|
1516
|
+
|
|
1517
|
+
/**
|
|
1518
|
+
* A function reference or a function name.
|
|
1519
|
+
*/
|
|
1520
|
+
type FunctionOrName<T> = Fn<T> | string;
|
|
1521
|
+
|
|
1522
|
+
/**
|
|
1523
|
+
* Function parameters, either derived from the function or an untyped array.
|
|
1524
|
+
*/
|
|
1525
|
+
type FunctionParameters<T, F extends FunctionOrName<T>> = F extends Fn<T> ? Parameters<F> : unknown[];
|
|
1526
|
+
|
|
1527
|
+
/**
|
|
1528
|
+
* Calls a formatter function to process the parameters.
|
|
1529
|
+
* If requireContext is set to true and no context is passed a default context will be added automatically.
|
|
1530
|
+
*
|
|
1531
|
+
* @template T
|
|
1532
|
+
* @template U
|
|
1533
|
+
* @param parameters The list of parameter that should match the type and number of the formatter function
|
|
1534
|
+
* @param formatterFunction The function to call
|
|
1535
|
+
* @param [contextEntityType] If no parameter refers to the context then we use this information to add a reference to the keys from the entity type.
|
|
1536
|
+
* @param [ignoreComplexType] Whether to ignore the transgformation to the StandardFormatter or not
|
|
1537
|
+
* @returns The corresponding expression
|
|
1538
|
+
*/
|
|
1539
|
+
export function formatResult<T, U extends Fn<T>>(
|
|
1540
|
+
parameters: WrappedTuple<Parameters<U>>,
|
|
1541
|
+
formatterFunction: U | string,
|
|
1542
|
+
contextEntityType?: EntityType,
|
|
1543
|
+
ignoreComplexType = false
|
|
1544
|
+
): BindingToolkitExpression<T> {
|
|
1545
|
+
const parameterExpressions = (parameters as ExpressionOrPrimitive<PrimitiveType>[]).map(wrapPrimitive);
|
|
1546
|
+
|
|
1547
|
+
if (hasUnresolvableExpression(...parameterExpressions)) {
|
|
1548
|
+
return unresolvableExpression;
|
|
1549
|
+
}
|
|
1550
|
+
if (contextEntityType) {
|
|
1551
|
+
// Otherwise, if the context is required and no context is provided make sure to add the default binding
|
|
1552
|
+
if (!parameterExpressions.some(hasReferenceToDefaultContext)) {
|
|
1553
|
+
contextEntityType.keys.forEach((key) => parameterExpressions.push(pathInModel(key.name, "")));
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
let functionName = "";
|
|
1557
|
+
if (typeof formatterFunction === "string") {
|
|
1558
|
+
functionName = formatterFunction;
|
|
1559
|
+
} else {
|
|
1560
|
+
functionName = formatterFunction.__functionName;
|
|
1561
|
+
}
|
|
1562
|
+
// FormatterName can be of format sap.fe.core.xxx#methodName to have multiple formatter in one class
|
|
1563
|
+
const [formatterClass, formatterName] = functionName.split("#");
|
|
1564
|
+
|
|
1565
|
+
// In some case we also cannot call directly a function because of too complex input, in that case we need to convert to a simpler function call
|
|
1566
|
+
if (
|
|
1567
|
+
!ignoreComplexType &&
|
|
1568
|
+
(parameterExpressions.some(isComplexTypeExpression) ||
|
|
1569
|
+
parameterExpressions.some(isConcatExpression) ||
|
|
1570
|
+
parameterExpressions.some(isIfElseExpression))
|
|
1571
|
+
) {
|
|
1572
|
+
let pathIdx = 0;
|
|
1573
|
+
const myFormatExpression = formatResult(parameterExpressions, functionName, undefined, true);
|
|
1574
|
+
const allParts = [];
|
|
1575
|
+
transformRecursively(
|
|
1576
|
+
myFormatExpression,
|
|
1577
|
+
"PathInModel",
|
|
1578
|
+
(constantPath: PathInModelExpression<unknown>) => {
|
|
1579
|
+
allParts.push(constantPath);
|
|
1580
|
+
return pathInModel(`$${pathIdx++}`, "$");
|
|
1581
|
+
},
|
|
1582
|
+
true
|
|
1583
|
+
);
|
|
1584
|
+
allParts.unshift(constant(JSON.stringify(myFormatExpression)));
|
|
1585
|
+
return formatResult(allParts, "sap.fe.core.formatters.StandardFormatter#evaluateComplexExpression", undefined, true);
|
|
1586
|
+
} else if (!!formatterName && formatterName.length > 0) {
|
|
1587
|
+
parameterExpressions.unshift(constant(formatterName));
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
return {
|
|
1591
|
+
_type: "Formatter",
|
|
1592
|
+
fn: formatterClass,
|
|
1593
|
+
parameters: parameterExpressions
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
type BindingConstraints = {
|
|
1598
|
+
scale?: number;
|
|
1599
|
+
precision?: number;
|
|
1600
|
+
maxLength?: number;
|
|
1601
|
+
nullable?: boolean;
|
|
1602
|
+
minimum?: string;
|
|
1603
|
+
maximum?: string;
|
|
1604
|
+
isDigitSequence?: boolean;
|
|
1605
|
+
V4?: boolean;
|
|
1606
|
+
};
|
|
1607
|
+
export function setUpConstraints(targetMapping: TypeMappingDefinition, property: Property | ActionParameter): BindingConstraints {
|
|
1608
|
+
const constraints: BindingConstraints = {};
|
|
1609
|
+
if (targetMapping?.constraints?.$Scale && property.scale !== undefined) {
|
|
1610
|
+
constraints.scale = property.scale;
|
|
1611
|
+
}
|
|
1612
|
+
if (targetMapping?.constraints?.$Precision && property.precision !== undefined) {
|
|
1613
|
+
constraints.precision = property.precision;
|
|
1614
|
+
}
|
|
1615
|
+
if (targetMapping?.constraints?.$MaxLength && property.maxLength !== undefined) {
|
|
1616
|
+
constraints.maxLength = property.maxLength;
|
|
1617
|
+
}
|
|
1618
|
+
if (property.nullable === false) {
|
|
1619
|
+
constraints.nullable = false;
|
|
1620
|
+
}
|
|
1621
|
+
if (targetMapping?.constraints?.["@Org.OData.Validation.V1.Minimum/$Decimal"] && !isNaN(property.annotations?.Validation?.Minimum)) {
|
|
1622
|
+
constraints.minimum = `${property.annotations?.Validation?.Minimum}`;
|
|
1623
|
+
}
|
|
1624
|
+
if (targetMapping?.constraints?.["@Org.OData.Validation.V1.Maximum/$Decimal"] && !isNaN(property.annotations?.Validation?.Maximum)) {
|
|
1625
|
+
constraints.maximum = `${property.annotations?.Validation?.Maximum}`;
|
|
1626
|
+
}
|
|
1627
|
+
if (
|
|
1628
|
+
property.annotations?.Common?.IsDigitSequence &&
|
|
1629
|
+
targetMapping.type === "sap.ui.model.odata.type.String" &&
|
|
1630
|
+
targetMapping?.constraints?.["@com.sap.vocabularies.Common.v1.IsDigitSequence"]
|
|
1631
|
+
) {
|
|
1632
|
+
constraints.isDigitSequence = true;
|
|
1633
|
+
}
|
|
1634
|
+
if (targetMapping?.constraints?.$V4) {
|
|
1635
|
+
constraints.V4 = true;
|
|
1636
|
+
}
|
|
1637
|
+
return constraints;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
/**
|
|
1641
|
+
* Generates the binding expression for the property, and sets up the formatOptions and constraints.
|
|
1642
|
+
*
|
|
1643
|
+
* @param property The Property for which we are setting up the binding
|
|
1644
|
+
* @param propertyBindingExpression The BindingExpression of the property above. Serves as the basis to which information can be added
|
|
1645
|
+
* @param ignoreConstraints Ignore constraints of the property
|
|
1646
|
+
* @returns The binding expression for the property with formatOptions and constraints
|
|
1647
|
+
*/
|
|
1648
|
+
export function formatWithTypeInformation<T>(
|
|
1649
|
+
property: Property | ActionParameter,
|
|
1650
|
+
propertyBindingExpression: BindingToolkitExpression<string>,
|
|
1651
|
+
ignoreConstraints = false
|
|
1652
|
+
): PathInModelExpression<T> {
|
|
1653
|
+
const outExpression: PathInModelExpression<unknown> = propertyBindingExpression as PathInModelExpression<unknown>;
|
|
1654
|
+
if (property._type !== "Property" && property._type !== "ActionParameter") {
|
|
1655
|
+
return outExpression;
|
|
1656
|
+
}
|
|
1657
|
+
const targetMapping = EDM_TYPE_MAPPING[property.type];
|
|
1658
|
+
if (!targetMapping) {
|
|
1659
|
+
return outExpression;
|
|
1660
|
+
}
|
|
1661
|
+
if (!outExpression.formatOptions) {
|
|
1662
|
+
outExpression.formatOptions = {};
|
|
1663
|
+
}
|
|
1664
|
+
outExpression.constraints = {};
|
|
1665
|
+
|
|
1666
|
+
outExpression.type = targetMapping.type;
|
|
1667
|
+
if (!ignoreConstraints) {
|
|
1668
|
+
outExpression.constraints = setUpConstraints(targetMapping, property);
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
if (
|
|
1672
|
+
(outExpression?.type?.indexOf("sap.ui.model.odata.type.Int") === 0 && outExpression?.type !== "sap.ui.model.odata.type.Int64") ||
|
|
1673
|
+
outExpression?.type === "sap.ui.model.odata.type.Double"
|
|
1674
|
+
) {
|
|
1675
|
+
outExpression.formatOptions = Object.assign(outExpression.formatOptions, {
|
|
1676
|
+
parseAsString: false
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
if (outExpression.type === "sap.ui.model.odata.type.String" && isProperty(property)) {
|
|
1680
|
+
if (!outExpression.constraints?.isDigitSequence || outExpression.constraints?.nullable === false) {
|
|
1681
|
+
outExpression.formatOptions.parseKeepsEmptyString = true;
|
|
1682
|
+
}
|
|
1683
|
+
const fiscalType = getFiscalType(property);
|
|
1684
|
+
if (fiscalType) {
|
|
1685
|
+
outExpression.formatOptions.fiscalType = fiscalType;
|
|
1686
|
+
outExpression.type = "sap.fe.core.type.FiscalDate";
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
const typesSupportingParseEmptyValueToZero = [
|
|
1690
|
+
"sap.ui.model.odata.type.Int16",
|
|
1691
|
+
"sap.ui.model.odata.type.Int32",
|
|
1692
|
+
"sap.ui.model.odata.type.Int64",
|
|
1693
|
+
"sap.ui.model.odata.type.Single",
|
|
1694
|
+
"sap.ui.model.odata.type.Double",
|
|
1695
|
+
"sap.ui.model.odata.type.Decimal",
|
|
1696
|
+
"sap.ui.model.odata.type.Byte",
|
|
1697
|
+
"sap.ui.model.odata.type.SByte"
|
|
1698
|
+
];
|
|
1699
|
+
if (
|
|
1700
|
+
outExpression.constraints?.nullable === false &&
|
|
1701
|
+
outExpression.type &&
|
|
1702
|
+
typesSupportingParseEmptyValueToZero.includes(outExpression.type)
|
|
1703
|
+
) {
|
|
1704
|
+
outExpression.formatOptions = Object.assign(outExpression.formatOptions, {
|
|
1705
|
+
parseEmptyValueToZero: true
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
if (
|
|
1709
|
+
outExpression.constraints?.nullable !== false &&
|
|
1710
|
+
(outExpression.type === "sap.ui.model.odata.type.Int64" || outExpression.type === "sap.ui.model.odata.type.Decimal")
|
|
1711
|
+
) {
|
|
1712
|
+
outExpression.formatOptions = Object.assign(outExpression.formatOptions, {
|
|
1713
|
+
emptyString: ""
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
return outExpression;
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
export const getFiscalType = function (property: Property): string | undefined {
|
|
1721
|
+
if (property.annotations?.Common?.IsFiscalYear) {
|
|
1722
|
+
return CommonAnnotationTerms.IsFiscalYear;
|
|
1723
|
+
}
|
|
1724
|
+
if (property.annotations?.Common?.IsFiscalPeriod) {
|
|
1725
|
+
return CommonAnnotationTerms.IsFiscalPeriod;
|
|
1726
|
+
}
|
|
1727
|
+
if (property.annotations?.Common?.IsFiscalYearPeriod) {
|
|
1728
|
+
return CommonAnnotationTerms.IsFiscalYearPeriod;
|
|
1729
|
+
}
|
|
1730
|
+
if (property.annotations?.Common?.IsFiscalQuarter) {
|
|
1731
|
+
return CommonAnnotationTerms.IsFiscalQuarter;
|
|
1732
|
+
}
|
|
1733
|
+
if (property.annotations?.Common?.IsFiscalYearQuarter) {
|
|
1734
|
+
return CommonAnnotationTerms.IsFiscalYearQuarter;
|
|
1735
|
+
}
|
|
1736
|
+
if (property.annotations?.Common?.IsFiscalWeek) {
|
|
1737
|
+
return CommonAnnotationTerms.IsFiscalWeek;
|
|
1738
|
+
}
|
|
1739
|
+
if (property.annotations?.Common?.IsFiscalYearWeek) {
|
|
1740
|
+
return CommonAnnotationTerms.IsFiscalYearWeek;
|
|
1741
|
+
}
|
|
1742
|
+
if (property.annotations?.Common?.IsDayOfFiscalYear) {
|
|
1743
|
+
return CommonAnnotationTerms.IsDayOfFiscalYear;
|
|
1744
|
+
}
|
|
1745
|
+
};
|
|
1746
|
+
|
|
1747
|
+
/**
|
|
1748
|
+
* Calls a complex type to process the parameters.
|
|
1749
|
+
* If requireContext is set to true and no context is passed, a default context will be added automatically.
|
|
1750
|
+
*
|
|
1751
|
+
* @template T
|
|
1752
|
+
* @template U
|
|
1753
|
+
* @param parameters The list of parameters that should match the type for the complex type=
|
|
1754
|
+
* @param type The complex type to use
|
|
1755
|
+
* @param [contextEntityType] The context entity type to consider
|
|
1756
|
+
* @param oFormatOptions
|
|
1757
|
+
* @returns The corresponding expression
|
|
1758
|
+
*/
|
|
1759
|
+
export function addTypeInformation<T, U extends Fn<T>>(
|
|
1760
|
+
parameters: WrappedTuple<Parameters<U>>,
|
|
1761
|
+
type: string,
|
|
1762
|
+
contextEntityType?: EntityType,
|
|
1763
|
+
oFormatOptions?: ComplexTypeFormatOptions
|
|
1764
|
+
): UnresolvablePathExpression | ComplexTypeExpression<T> | ConstantExpression<T> {
|
|
1765
|
+
const parameterExpressions = (parameters as ExpressionOrPrimitive<PrimitiveType>[]).map(wrapPrimitive);
|
|
1766
|
+
if (hasUnresolvableExpression(...parameterExpressions)) {
|
|
1767
|
+
return unresolvableExpression;
|
|
1768
|
+
}
|
|
1769
|
+
// If there is only one parameter and it is a constant and we don't expect the context then return the constant
|
|
1770
|
+
if (parameterExpressions.length === 1 && isConstant(parameterExpressions[0]) && !contextEntityType) {
|
|
1771
|
+
return parameterExpressions[0] as ConstantExpression<T>;
|
|
1772
|
+
} else if (contextEntityType) {
|
|
1773
|
+
// Otherwise, if the context is required and no context is provided make sure to add the default binding
|
|
1774
|
+
if (!parameterExpressions.some(hasReferenceToDefaultContext)) {
|
|
1775
|
+
contextEntityType.keys.forEach((key) => parameterExpressions.push(pathInModel(key.name, "")));
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
oFormatOptions = _getComplexTypeFormatOptionsFromFirstParam(parameters[0], oFormatOptions);
|
|
1779
|
+
|
|
1780
|
+
if (type === "sap.ui.model.odata.type.Unit") {
|
|
1781
|
+
const uomPath = pathInModel("/##@@requestUnitsOfMeasure");
|
|
1782
|
+
uomPath.targetType = "any";
|
|
1783
|
+
uomPath.mode = "OneTime";
|
|
1784
|
+
parameterExpressions.push(uomPath);
|
|
1785
|
+
} else if (type === "sap.ui.model.odata.type.Currency") {
|
|
1786
|
+
const currencyPath = pathInModel("/##@@requestCurrencyCodes");
|
|
1787
|
+
currencyPath.targetType = "any";
|
|
1788
|
+
currencyPath.mode = "OneTime";
|
|
1789
|
+
parameterExpressions.push(currencyPath);
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
return {
|
|
1793
|
+
_type: "ComplexType",
|
|
1794
|
+
type: type,
|
|
1795
|
+
formatOptions: oFormatOptions || {},
|
|
1796
|
+
parameters: {},
|
|
1797
|
+
bindingParameters: parameterExpressions
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
type ComplexTypeFormatOptions = {
|
|
1802
|
+
showNumber?: boolean;
|
|
1803
|
+
showMeasure?: boolean;
|
|
1804
|
+
parseAsString?: boolean;
|
|
1805
|
+
parseKeepsEmptyString?: boolean;
|
|
1806
|
+
emptyString?: string | number | null;
|
|
1807
|
+
showTime?: boolean;
|
|
1808
|
+
showDate?: boolean;
|
|
1809
|
+
showTimezone?: boolean;
|
|
1810
|
+
};
|
|
1811
|
+
|
|
1812
|
+
/**
|
|
1813
|
+
* Process the formatOptions for a complexType based on the first parameter.
|
|
1814
|
+
*
|
|
1815
|
+
* @param param The first parameter of the complex type
|
|
1816
|
+
* @param formatOptions Initial formatOptions
|
|
1817
|
+
* @returns The modified formatOptions
|
|
1818
|
+
*/
|
|
1819
|
+
function _getComplexTypeFormatOptionsFromFirstParam<T, U extends Fn<T>>(
|
|
1820
|
+
param: Parameters<U>,
|
|
1821
|
+
formatOptions: undefined | ComplexTypeFormatOptions
|
|
1822
|
+
): ComplexTypeFormatOptions | undefined {
|
|
1823
|
+
// if showMeasure is set to false we want to not parse as string to see the 0
|
|
1824
|
+
// we do that also for all bindings because otherwise the mdc Field isn't editable
|
|
1825
|
+
if (
|
|
1826
|
+
!(formatOptions && formatOptions.showNumber === false) &&
|
|
1827
|
+
(param?.type?.indexOf("sap.ui.model.odata.type.Int") === 0 ||
|
|
1828
|
+
param?.type === "sap.ui.model.odata.type.Decimal" ||
|
|
1829
|
+
param?.type === "sap.ui.model.odata.type.Double")
|
|
1830
|
+
) {
|
|
1831
|
+
if (param?.type === "sap.ui.model.odata.type.Int64" || param?.type === "sap.ui.model.odata.type.Decimal") {
|
|
1832
|
+
//sap.ui.model.odata.type.Int64 do not support parseAsString false
|
|
1833
|
+
formatOptions = formatOptions?.showMeasure === false ? { showMeasure: false } : {};
|
|
1834
|
+
} else {
|
|
1835
|
+
formatOptions = formatOptions?.showMeasure === false ? { parseAsString: false, showMeasure: false } : { parseAsString: false };
|
|
1836
|
+
}
|
|
1837
|
+
formatOptions.emptyString = null;
|
|
1838
|
+
}
|
|
1839
|
+
return formatOptions;
|
|
1840
|
+
}
|
|
1841
|
+
/**
|
|
1842
|
+
* Function call, optionally with arguments.
|
|
1843
|
+
*
|
|
1844
|
+
* @param func Function name or reference to function
|
|
1845
|
+
* @param parameters Arguments
|
|
1846
|
+
* @param on Object to call the function on
|
|
1847
|
+
* @param isFormattingFn
|
|
1848
|
+
* @returns Expression representing the function call (not the result of the function call!)
|
|
1849
|
+
*/
|
|
1850
|
+
export function fn<T, U extends FunctionOrName<T>>(
|
|
1851
|
+
func: U,
|
|
1852
|
+
parameters: WrappedTuple<FunctionParameters<T, U>>,
|
|
1853
|
+
on?: ExpressionOrPrimitive<object>,
|
|
1854
|
+
isFormattingFn = false
|
|
1855
|
+
): FunctionExpression<T> {
|
|
1856
|
+
const functionName = typeof func === "string" ? func : func.__functionName;
|
|
1857
|
+
return {
|
|
1858
|
+
_type: "Function",
|
|
1859
|
+
obj: on !== undefined ? wrapPrimitive(on) : undefined,
|
|
1860
|
+
fn: functionName,
|
|
1861
|
+
isFormattingFn: isFormattingFn,
|
|
1862
|
+
parameters: (parameters as ExpressionOrPrimitive<PrimitiveType>[]).map(wrapPrimitive)
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
/**
|
|
1867
|
+
* Shortcut function to determine if a binding value is null, undefined or empty.
|
|
1868
|
+
*
|
|
1869
|
+
* @param expression
|
|
1870
|
+
* @returns A Boolean expression evaluating the fact that the current element is empty
|
|
1871
|
+
*/
|
|
1872
|
+
export function isEmpty(expression: BindingToolkitExpression<string>): BindingToolkitExpression<boolean> {
|
|
1873
|
+
const aBindings: ExpressionOrPrimitive<boolean>[] = [];
|
|
1874
|
+
transformRecursively(expression, "PathInModel", (expr) => {
|
|
1875
|
+
aBindings.push(or(equal(expr, ""), equal(expr, undefined), equal(expr, null)));
|
|
1876
|
+
return expr;
|
|
1877
|
+
});
|
|
1878
|
+
return and(...aBindings);
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
export function concat(...inExpressions: ExpressionOrPrimitive<string>[]): BindingToolkitExpression<string> {
|
|
1882
|
+
const expressions = inExpressions.map(wrapPrimitive);
|
|
1883
|
+
if (hasUnresolvableExpression(...expressions)) {
|
|
1884
|
+
return unresolvableExpression;
|
|
1885
|
+
}
|
|
1886
|
+
if (expressions.every(isConstant)) {
|
|
1887
|
+
return constant(
|
|
1888
|
+
expressions.reduce((concatenated: string, value) => {
|
|
1889
|
+
if (value.value !== undefined && value.value !== null) {
|
|
1890
|
+
return concatenated + value.value.toString();
|
|
1891
|
+
}
|
|
1892
|
+
return concatenated;
|
|
1893
|
+
}, "")
|
|
1894
|
+
);
|
|
1895
|
+
} else if (expressions.some(isComplexTypeExpression)) {
|
|
1896
|
+
let pathIdx = 0;
|
|
1897
|
+
const myConcatExpression = formatResult(expressions, "sap.fe.core.formatters.StandardFormatter#concat", undefined, true);
|
|
1898
|
+
const allParts = [];
|
|
1899
|
+
transformRecursively(myConcatExpression, "PathInModel", (constantPath: PathInModelExpression<unknown>) => {
|
|
1900
|
+
allParts.push(constantPath);
|
|
1901
|
+
return pathInModel(`$${pathIdx++}`, "$");
|
|
1902
|
+
});
|
|
1903
|
+
allParts.unshift(constant(JSON.stringify(myConcatExpression)));
|
|
1904
|
+
return formatResult(allParts, "sap.fe.core.formatters.StandardFormatter#evaluateComplexExpression", undefined, true);
|
|
1905
|
+
}
|
|
1906
|
+
return {
|
|
1907
|
+
_type: "Concat",
|
|
1908
|
+
expressions: expressions
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
type ExpressionTypeToExpression<T extends ExpressionType, V> = T extends "Constant"
|
|
1913
|
+
? ConstantExpression<V>
|
|
1914
|
+
: T extends "PathInModel"
|
|
1915
|
+
? PathInModelExpression<V>
|
|
1916
|
+
: T extends "Function"
|
|
1917
|
+
? FunctionExpression<V>
|
|
1918
|
+
: T extends "Unresolvable"
|
|
1919
|
+
? UnresolvablePathExpression
|
|
1920
|
+
: BindingToolkitExpression<V>;
|
|
1921
|
+
|
|
1922
|
+
export type TransformFunction<T, V> = (expressionPart: T) => BindingToolkitExpression<V>;
|
|
1923
|
+
export type ExpressionType = Pick<BindingToolkitExpression<unknown>, "_type">["_type"];
|
|
1924
|
+
export function transformRecursively<T extends PrimitiveType | unknown, ET extends ExpressionType>(
|
|
1925
|
+
inExpression: BindingToolkitExpression<T>,
|
|
1926
|
+
expressionType: ET,
|
|
1927
|
+
transformFunction: TransformFunction<ExpressionTypeToExpression<ET, T>, T>,
|
|
1928
|
+
includeAllExpression = false
|
|
1929
|
+
): BindingToolkitExpression<T> {
|
|
1930
|
+
let expression: BindingToolkitExpression<T> = inExpression;
|
|
1931
|
+
switch (expression._type) {
|
|
1932
|
+
case "Function":
|
|
1933
|
+
case "Formatter":
|
|
1934
|
+
expression.parameters = expression.parameters.map((parameter) =>
|
|
1935
|
+
transformRecursively(parameter as BindingToolkitExpression<T>, expressionType, transformFunction, includeAllExpression)
|
|
1936
|
+
);
|
|
1937
|
+
break;
|
|
1938
|
+
case "Concat":
|
|
1939
|
+
expression.expressions = expression.expressions.map((subExpression) =>
|
|
1940
|
+
transformRecursively(
|
|
1941
|
+
subExpression,
|
|
1942
|
+
expressionType,
|
|
1943
|
+
transformFunction as unknown as TransformFunction<ExpressionTypeToExpression<ET, string>, string>,
|
|
1944
|
+
includeAllExpression
|
|
1945
|
+
)
|
|
1946
|
+
);
|
|
1947
|
+
expression = concat(...expression.expressions) as BindingToolkitExpression<T>;
|
|
1948
|
+
break;
|
|
1949
|
+
case "ComplexType":
|
|
1950
|
+
expression.bindingParameters = expression.bindingParameters.map((bindingParameter) =>
|
|
1951
|
+
transformRecursively(
|
|
1952
|
+
bindingParameter as BindingToolkitExpression<T>,
|
|
1953
|
+
expressionType,
|
|
1954
|
+
transformFunction,
|
|
1955
|
+
includeAllExpression
|
|
1956
|
+
)
|
|
1957
|
+
);
|
|
1958
|
+
break;
|
|
1959
|
+
case "IfElse":
|
|
1960
|
+
const onTrue = transformRecursively(
|
|
1961
|
+
expression.onTrue,
|
|
1962
|
+
expressionType,
|
|
1963
|
+
transformFunction as unknown as TransformFunction<ExpressionTypeToExpression<ET, unknown>, unknown>,
|
|
1964
|
+
includeAllExpression
|
|
1965
|
+
);
|
|
1966
|
+
const onFalse = transformRecursively(
|
|
1967
|
+
expression.onFalse,
|
|
1968
|
+
expressionType,
|
|
1969
|
+
transformFunction as unknown as TransformFunction<ExpressionTypeToExpression<ET, unknown>, unknown>,
|
|
1970
|
+
includeAllExpression
|
|
1971
|
+
);
|
|
1972
|
+
let condition = expression.condition;
|
|
1973
|
+
if (includeAllExpression) {
|
|
1974
|
+
condition = transformRecursively(
|
|
1975
|
+
expression.condition,
|
|
1976
|
+
expressionType,
|
|
1977
|
+
transformFunction as unknown as TransformFunction<ExpressionTypeToExpression<ET, boolean>, boolean>,
|
|
1978
|
+
includeAllExpression
|
|
1979
|
+
);
|
|
1980
|
+
}
|
|
1981
|
+
expression = ifElse(condition, onTrue, onFalse) as BindingToolkitExpression<T>;
|
|
1982
|
+
break;
|
|
1983
|
+
case "Not":
|
|
1984
|
+
if (includeAllExpression) {
|
|
1985
|
+
const operand = transformRecursively(
|
|
1986
|
+
expression.operand,
|
|
1987
|
+
expressionType,
|
|
1988
|
+
transformFunction as unknown as TransformFunction<ExpressionTypeToExpression<ET, boolean>, boolean>,
|
|
1989
|
+
includeAllExpression
|
|
1990
|
+
);
|
|
1991
|
+
expression = not(operand) as BindingToolkitExpression<T>;
|
|
1992
|
+
}
|
|
1993
|
+
break;
|
|
1994
|
+
case "Truthy":
|
|
1995
|
+
break;
|
|
1996
|
+
case "Set":
|
|
1997
|
+
if (includeAllExpression) {
|
|
1998
|
+
const operands = expression.operands.map((operand) =>
|
|
1999
|
+
transformRecursively(
|
|
2000
|
+
operand,
|
|
2001
|
+
expressionType,
|
|
2002
|
+
transformFunction as unknown as TransformFunction<ExpressionTypeToExpression<ET, boolean>, boolean>,
|
|
2003
|
+
includeAllExpression
|
|
2004
|
+
)
|
|
2005
|
+
);
|
|
2006
|
+
expression =
|
|
2007
|
+
expression.operator === "||"
|
|
2008
|
+
? (or(...operands) as BindingToolkitExpression<T>)
|
|
2009
|
+
: (and(...operands) as BindingToolkitExpression<T>);
|
|
2010
|
+
}
|
|
2011
|
+
break;
|
|
2012
|
+
case "Comparison":
|
|
2013
|
+
if (includeAllExpression) {
|
|
2014
|
+
const operand1 = transformRecursively(
|
|
2015
|
+
expression.operand1 as BindingToolkitExpression<T>,
|
|
2016
|
+
expressionType,
|
|
2017
|
+
transformFunction,
|
|
2018
|
+
includeAllExpression
|
|
2019
|
+
);
|
|
2020
|
+
const operand2 = transformRecursively(
|
|
2021
|
+
expression.operand2 as BindingToolkitExpression<T>,
|
|
2022
|
+
expressionType,
|
|
2023
|
+
transformFunction,
|
|
2024
|
+
includeAllExpression
|
|
2025
|
+
);
|
|
2026
|
+
expression = comparison(expression.operator, operand1, operand2) as BindingToolkitExpression<T>;
|
|
2027
|
+
}
|
|
2028
|
+
break;
|
|
2029
|
+
case "Constant":
|
|
2030
|
+
const constantValue: Record<string, BindingToolkitExpression<unknown>> = expression.value as Record<
|
|
2031
|
+
string,
|
|
2032
|
+
BindingToolkitExpression<unknown>
|
|
2033
|
+
>;
|
|
2034
|
+
if (typeof constantValue === "object" && constantValue) {
|
|
2035
|
+
Object.keys(constantValue).forEach((key) => {
|
|
2036
|
+
constantValue[key] = transformRecursively(
|
|
2037
|
+
constantValue[key],
|
|
2038
|
+
expressionType,
|
|
2039
|
+
transformFunction as unknown as TransformFunction<ExpressionTypeToExpression<ET, unknown>, unknown>,
|
|
2040
|
+
includeAllExpression
|
|
2041
|
+
);
|
|
2042
|
+
});
|
|
2043
|
+
}
|
|
2044
|
+
break;
|
|
2045
|
+
case "Ref":
|
|
2046
|
+
case "Length":
|
|
2047
|
+
case "PathInModel":
|
|
2048
|
+
case "EmbeddedBinding":
|
|
2049
|
+
case "EmbeddedExpressionBinding":
|
|
2050
|
+
case "Unresolvable":
|
|
2051
|
+
// Do nothing
|
|
2052
|
+
break;
|
|
2053
|
+
}
|
|
2054
|
+
if (expressionType === expression._type) {
|
|
2055
|
+
expression = transformFunction(inExpression as ExpressionTypeToExpression<ET, T>);
|
|
2056
|
+
}
|
|
2057
|
+
return expression;
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
export type CompiledBindingToolkitExpression = string | undefined;
|
|
2061
|
+
|
|
2062
|
+
const needParenthesis = function <T extends PrimitiveType>(expr: ExpressionOrPrimitive<T>): boolean {
|
|
2063
|
+
return (
|
|
2064
|
+
!isConstant(expr) &&
|
|
2065
|
+
!isPathInModelExpression(expr) &&
|
|
2066
|
+
isBindingToolkitExpression(expr) &&
|
|
2067
|
+
expr._type !== "IfElse" &&
|
|
2068
|
+
expr._type !== "Function"
|
|
2069
|
+
);
|
|
2070
|
+
};
|
|
2071
|
+
|
|
2072
|
+
/**
|
|
2073
|
+
* Compiles a constant object to a string.
|
|
2074
|
+
*
|
|
2075
|
+
* @param expr
|
|
2076
|
+
* @param isNullable
|
|
2077
|
+
* @returns The compiled string
|
|
2078
|
+
*/
|
|
2079
|
+
function compileConstantObject(expr: ConstantExpression<object>, isNullable = false): string {
|
|
2080
|
+
if (isNullable && Object.keys(expr.value).length === 0) {
|
|
2081
|
+
return "";
|
|
2082
|
+
}
|
|
2083
|
+
const objects = expr.value as PlainExpressionObject;
|
|
2084
|
+
const properties: string[] = [];
|
|
2085
|
+
Object.keys(objects).forEach((key) => {
|
|
2086
|
+
const value = objects[key];
|
|
2087
|
+
const childResult = compileExpression(value, true, false, isNullable);
|
|
2088
|
+
if (childResult && childResult.length > 0) {
|
|
2089
|
+
properties.push(`${key}: ${childResult}`);
|
|
2090
|
+
}
|
|
2091
|
+
});
|
|
2092
|
+
return `{${properties.join(", ")}}`;
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
/**
|
|
2096
|
+
* Compiles a Constant Binding Expression.
|
|
2097
|
+
*
|
|
2098
|
+
* @param expr
|
|
2099
|
+
* @param embeddedInBinding
|
|
2100
|
+
* @param isNullable
|
|
2101
|
+
* @param doNotStringify
|
|
2102
|
+
* @returns The compiled string
|
|
2103
|
+
*/
|
|
2104
|
+
export function compileConstant<T extends PrimitiveType>(
|
|
2105
|
+
expr: ConstantExpression<T>,
|
|
2106
|
+
embeddedInBinding: boolean,
|
|
2107
|
+
isNullable?: boolean,
|
|
2108
|
+
doNotStringify?: false
|
|
2109
|
+
): CompiledBindingToolkitExpression;
|
|
2110
|
+
export function compileConstant<T extends PrimitiveType>(
|
|
2111
|
+
expr: ConstantExpression<T>,
|
|
2112
|
+
embeddedInBinding: boolean,
|
|
2113
|
+
isNullable?: boolean,
|
|
2114
|
+
doNotStringify?: true
|
|
2115
|
+
): PrimitiveType;
|
|
2116
|
+
export function compileConstant<T extends PrimitiveType>(
|
|
2117
|
+
expr: ConstantExpression<T>,
|
|
2118
|
+
embeddedInBinding: boolean,
|
|
2119
|
+
isNullable = false,
|
|
2120
|
+
doNotStringify = false
|
|
2121
|
+
): CompiledBindingToolkitExpression | PrimitiveType {
|
|
2122
|
+
if (expr.value === null) {
|
|
2123
|
+
return doNotStringify ? null : "null";
|
|
2124
|
+
}
|
|
2125
|
+
if (expr.value === undefined) {
|
|
2126
|
+
return doNotStringify ? undefined : "undefined";
|
|
2127
|
+
}
|
|
2128
|
+
if (typeof expr.value === "object") {
|
|
2129
|
+
if (Array.isArray(expr.value)) {
|
|
2130
|
+
const entries = expr.value.map((expression) => compileExpression(expression, true));
|
|
2131
|
+
return `[${entries.join(", ")}]`;
|
|
2132
|
+
} else {
|
|
2133
|
+
return compileConstantObject(expr as ConstantExpression<object>, isNullable);
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
if (embeddedInBinding) {
|
|
2138
|
+
switch (typeof expr.value) {
|
|
2139
|
+
case "number":
|
|
2140
|
+
case "bigint":
|
|
2141
|
+
case "boolean":
|
|
2142
|
+
return expr.value.toString();
|
|
2143
|
+
case "string":
|
|
2144
|
+
return `'${escapeXmlAttribute(expr.value.toString())}'`;
|
|
2145
|
+
default:
|
|
2146
|
+
return "";
|
|
2147
|
+
}
|
|
2148
|
+
} else {
|
|
2149
|
+
return doNotStringify ? expr.value : expr.value.toString();
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
/**
|
|
2154
|
+
* Generates the binding string for a Binding expression.
|
|
2155
|
+
*
|
|
2156
|
+
* @param expressionForBinding The expression to compile
|
|
2157
|
+
* @param embeddedInBinding Whether the expression to compile is embedded into another expression
|
|
2158
|
+
* @param embeddedSeparator The binding value evaluator ($ or % depending on whether we want to force the type or not)
|
|
2159
|
+
* @returns The corresponding expression binding
|
|
2160
|
+
*/
|
|
2161
|
+
function compilePathInModelExpression<T extends PrimitiveType>(
|
|
2162
|
+
expressionForBinding: PathInModelExpression<T>,
|
|
2163
|
+
embeddedInBinding: boolean,
|
|
2164
|
+
embeddedSeparator: string
|
|
2165
|
+
): CompiledBindingToolkitExpression {
|
|
2166
|
+
if (
|
|
2167
|
+
expressionForBinding.type ||
|
|
2168
|
+
expressionForBinding.parameters ||
|
|
2169
|
+
expressionForBinding.targetType ||
|
|
2170
|
+
expressionForBinding.formatOptions ||
|
|
2171
|
+
expressionForBinding.constraints
|
|
2172
|
+
) {
|
|
2173
|
+
// This is now a complex binding definition, let's prepare for it
|
|
2174
|
+
const complexBindingDefinition = {
|
|
2175
|
+
path: compilePathInModel(expressionForBinding),
|
|
2176
|
+
type: expressionForBinding.type,
|
|
2177
|
+
targetType: expressionForBinding.targetType,
|
|
2178
|
+
parameters: expressionForBinding.parameters,
|
|
2179
|
+
formatOptions: expressionForBinding.formatOptions,
|
|
2180
|
+
constraints: expressionForBinding.constraints
|
|
2181
|
+
};
|
|
2182
|
+
const outBinding = compileExpression(complexBindingDefinition, false, false, true);
|
|
2183
|
+
if (embeddedInBinding) {
|
|
2184
|
+
return `${embeddedSeparator}${outBinding}`;
|
|
2185
|
+
}
|
|
2186
|
+
return outBinding;
|
|
2187
|
+
} else if (embeddedInBinding) {
|
|
2188
|
+
return `${embeddedSeparator}{${compilePathInModel(expressionForBinding)}}`;
|
|
2189
|
+
} else {
|
|
2190
|
+
return `{${compilePathInModel(expressionForBinding)}}`;
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
function compileComplexTypeExpression<T extends PrimitiveType>(expression: ComplexTypeExpression<T>): string {
|
|
2195
|
+
if (expression.bindingParameters.length === 1) {
|
|
2196
|
+
return `{${compilePathParameter(expression.bindingParameters[0] as BindingToolkitExpression<PrimitiveType>, true)}, type: '${
|
|
2197
|
+
expression.type
|
|
2198
|
+
}'}`;
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
let outputEnd = `], type: '${expression.type}'`;
|
|
2202
|
+
if (hasElements(expression.formatOptions)) {
|
|
2203
|
+
outputEnd += `, formatOptions: ${compileExpression(expression.formatOptions)}`;
|
|
2204
|
+
}
|
|
2205
|
+
if (hasElements(expression.parameters)) {
|
|
2206
|
+
outputEnd += `, parameters: ${compileExpression(expression.parameters)}`;
|
|
2207
|
+
}
|
|
2208
|
+
outputEnd += "}";
|
|
2209
|
+
|
|
2210
|
+
return `{mode:'TwoWay', parts:[${expression.bindingParameters
|
|
2211
|
+
.map((param) => compilePathParameter(param as BindingToolkitExpression<PrimitiveType>))
|
|
2212
|
+
.join(",")}${outputEnd}`;
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
/**
|
|
2216
|
+
* Wrap the compiled binding string as required depending on its context.
|
|
2217
|
+
*
|
|
2218
|
+
* @param expression The compiled expression
|
|
2219
|
+
* @param embeddedInBinding True if the compiled expression is to be embedded in a binding
|
|
2220
|
+
* @param parenthesisRequired True if the embedded binding needs to be wrapped in parethesis so that it is evaluated as one
|
|
2221
|
+
* @returns Finalized compiled expression
|
|
2222
|
+
*/
|
|
2223
|
+
export function wrapBindingExpression(
|
|
2224
|
+
expression: string,
|
|
2225
|
+
embeddedInBinding: boolean,
|
|
2226
|
+
parenthesisRequired = false
|
|
2227
|
+
): CompiledBindingToolkitExpression {
|
|
2228
|
+
if (embeddedInBinding) {
|
|
2229
|
+
if (parenthesisRequired) {
|
|
2230
|
+
return `(${expression})`;
|
|
2231
|
+
} else {
|
|
2232
|
+
return expression;
|
|
2233
|
+
}
|
|
2234
|
+
} else {
|
|
2235
|
+
return `{= ${expression}}`;
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
/**
|
|
2240
|
+
* Compile an expression into an expression binding.
|
|
2241
|
+
*
|
|
2242
|
+
* @template T The target type
|
|
2243
|
+
* @param expression The expression to compile
|
|
2244
|
+
* @param embeddedInBinding Whether the expression to compile is embedded into another expression
|
|
2245
|
+
* @param keepTargetType Keep the target type of the embedded bindings instead of casting them to any
|
|
2246
|
+
* @param isNullable Whether binding expression can resolve to empty string or not
|
|
2247
|
+
* @returns The corresponding expression binding
|
|
2248
|
+
*/
|
|
2249
|
+
export function compileExpression<T extends PrimitiveType>(
|
|
2250
|
+
expression: ExpressionOrPrimitive<T>,
|
|
2251
|
+
embeddedInBinding = false,
|
|
2252
|
+
keepTargetType = false,
|
|
2253
|
+
isNullable = false
|
|
2254
|
+
): CompiledBindingToolkitExpression {
|
|
2255
|
+
const expr = wrapPrimitive(expression);
|
|
2256
|
+
const embeddedSeparator = keepTargetType ? "$" : "%";
|
|
2257
|
+
|
|
2258
|
+
switch (expr._type) {
|
|
2259
|
+
case "Unresolvable":
|
|
2260
|
+
return undefined;
|
|
2261
|
+
|
|
2262
|
+
case "Constant":
|
|
2263
|
+
return compileConstant(expr, embeddedInBinding, isNullable);
|
|
2264
|
+
|
|
2265
|
+
case "Ref":
|
|
2266
|
+
return expr.ref || "null";
|
|
2267
|
+
|
|
2268
|
+
case "Function":
|
|
2269
|
+
let hasEmbeddedFunctionCallOrBinding = false;
|
|
2270
|
+
if (expr.isFormattingFn) {
|
|
2271
|
+
transformRecursively(
|
|
2272
|
+
expr,
|
|
2273
|
+
"Function",
|
|
2274
|
+
(subFn) => {
|
|
2275
|
+
if (subFn !== expr && subFn.obj === undefined) {
|
|
2276
|
+
hasEmbeddedFunctionCallOrBinding = true;
|
|
2277
|
+
}
|
|
2278
|
+
return subFn;
|
|
2279
|
+
},
|
|
2280
|
+
true
|
|
2281
|
+
);
|
|
2282
|
+
transformRecursively(
|
|
2283
|
+
expr,
|
|
2284
|
+
"Constant",
|
|
2285
|
+
(subFn: ConstantExpression<T>) => {
|
|
2286
|
+
if ((subFn as unknown) !== expr && typeof subFn.value === "object") {
|
|
2287
|
+
transformRecursively(subFn, "PathInModel", (subSubFn) => {
|
|
2288
|
+
hasEmbeddedFunctionCallOrBinding = true;
|
|
2289
|
+
return subSubFn;
|
|
2290
|
+
});
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
return subFn;
|
|
2294
|
+
},
|
|
2295
|
+
true
|
|
2296
|
+
);
|
|
2297
|
+
}
|
|
2298
|
+
const argumentString = `${expr.parameters.map((arg) => compileExpression(arg, true)).join(", ")}`;
|
|
2299
|
+
let fnCall =
|
|
2300
|
+
expr.obj === undefined
|
|
2301
|
+
? `${expr.fn}(${argumentString})`
|
|
2302
|
+
: `${compileExpression(expr.obj, true)}.${expr.fn}(${argumentString})`;
|
|
2303
|
+
if (!embeddedInBinding && hasEmbeddedFunctionCallOrBinding) {
|
|
2304
|
+
fnCall = `{= ${fnCall}}`;
|
|
2305
|
+
}
|
|
2306
|
+
return fnCall;
|
|
2307
|
+
|
|
2308
|
+
case "EmbeddedExpressionBinding":
|
|
2309
|
+
return embeddedInBinding ? `(${expr.value.substring(2, expr.value.length - 1)})` : `${expr.value}`;
|
|
2310
|
+
|
|
2311
|
+
case "EmbeddedBinding":
|
|
2312
|
+
return embeddedInBinding ? `${embeddedSeparator}${expr.value}` : `${expr.value}`;
|
|
2313
|
+
|
|
2314
|
+
case "PathInModel":
|
|
2315
|
+
return compilePathInModelExpression(expr, embeddedInBinding, embeddedSeparator);
|
|
2316
|
+
|
|
2317
|
+
case "Comparison":
|
|
2318
|
+
const comparisonExpression = compileComparisonExpression(expr);
|
|
2319
|
+
return wrapBindingExpression(comparisonExpression, embeddedInBinding);
|
|
2320
|
+
|
|
2321
|
+
case "IfElse":
|
|
2322
|
+
const ifElseExpression = `${compileExpression(expr.condition, true)} ? ${compileExpression(
|
|
2323
|
+
expr.onTrue,
|
|
2324
|
+
true,
|
|
2325
|
+
keepTargetType
|
|
2326
|
+
)} : ${compileExpression(expr.onFalse, true, keepTargetType)}`;
|
|
2327
|
+
return wrapBindingExpression(ifElseExpression, embeddedInBinding, true);
|
|
2328
|
+
|
|
2329
|
+
case "Set":
|
|
2330
|
+
const setExpression = expr.operands.map((operand) => compileExpression(operand, true)).join(` ${expr.operator} `);
|
|
2331
|
+
return wrapBindingExpression(setExpression, embeddedInBinding, true);
|
|
2332
|
+
|
|
2333
|
+
case "Concat":
|
|
2334
|
+
const concatExpression = expr.expressions
|
|
2335
|
+
.map((nestedExpression) => compileExpression(nestedExpression, true, true))
|
|
2336
|
+
.join(" + ");
|
|
2337
|
+
return wrapBindingExpression(concatExpression, embeddedInBinding);
|
|
2338
|
+
|
|
2339
|
+
case "Length":
|
|
2340
|
+
const lengthExpression = `${compileExpression(expr.pathInModel, true)}.length`;
|
|
2341
|
+
return wrapBindingExpression(lengthExpression, embeddedInBinding);
|
|
2342
|
+
|
|
2343
|
+
case "Not":
|
|
2344
|
+
const notExpression = `!${compileExpression(expr.operand, true)}`;
|
|
2345
|
+
return wrapBindingExpression(notExpression, embeddedInBinding);
|
|
2346
|
+
|
|
2347
|
+
case "Truthy":
|
|
2348
|
+
const truthyExpression = `!!${compileExpression(expr.operand, true)}`;
|
|
2349
|
+
return wrapBindingExpression(truthyExpression, embeddedInBinding);
|
|
2350
|
+
|
|
2351
|
+
case "Formatter":
|
|
2352
|
+
const formatterExpression = compileFormatterExpression(expr);
|
|
2353
|
+
return embeddedInBinding ? `$${formatterExpression}` : formatterExpression;
|
|
2354
|
+
|
|
2355
|
+
case "ComplexType":
|
|
2356
|
+
const complexTypeExpression = compileComplexTypeExpression(expr);
|
|
2357
|
+
return embeddedInBinding ? `$${complexTypeExpression}` : complexTypeExpression;
|
|
2358
|
+
|
|
2359
|
+
default:
|
|
2360
|
+
return "";
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
/**
|
|
2365
|
+
* Compile a comparison expression.
|
|
2366
|
+
*
|
|
2367
|
+
* @param expression The comparison expression.
|
|
2368
|
+
* @returns The compiled expression. Needs wrapping before it can be used as an expression binding.
|
|
2369
|
+
*/
|
|
2370
|
+
function compileComparisonExpression(expression: ComparisonExpression): string {
|
|
2371
|
+
function compileOperand(operand: BindingToolkitExpression<unknown>): CompiledBindingToolkitExpression {
|
|
2372
|
+
const compiledOperand = compileExpression(operand, true) ?? "undefined";
|
|
2373
|
+
return wrapBindingExpression(compiledOperand, true, needParenthesis(operand));
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
return `${compileOperand(expression.operand1)} ${expression.operator} ${compileOperand(expression.operand2)}`;
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
/**
|
|
2380
|
+
* Compile a formatter expression.
|
|
2381
|
+
*
|
|
2382
|
+
* @param expression The formatter expression.
|
|
2383
|
+
* @returns The compiled expression.
|
|
2384
|
+
*/
|
|
2385
|
+
function compileFormatterExpression<T extends PrimitiveType>(expression: FormatterExpression<T>): string {
|
|
2386
|
+
if (expression.parameters.length === 1) {
|
|
2387
|
+
return `{${compilePathParameter(expression.parameters[0] as BindingToolkitExpression<PrimitiveType>, true)}, formatter: '${
|
|
2388
|
+
expression.fn
|
|
2389
|
+
}'}`;
|
|
2390
|
+
} else {
|
|
2391
|
+
const parts = expression.parameters.map((param) => {
|
|
2392
|
+
if (param._type === "ComplexType") {
|
|
2393
|
+
return compileComplexTypeExpression(param);
|
|
2394
|
+
} else {
|
|
2395
|
+
return compilePathParameter(param as BindingToolkitExpression<PrimitiveType>);
|
|
2396
|
+
}
|
|
2397
|
+
});
|
|
2398
|
+
return `{parts: [${parts.join(", ")}], formatter: '${expression.fn}'}`;
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
/**
|
|
2403
|
+
* Compile the path parameter of a formatter call.
|
|
2404
|
+
*
|
|
2405
|
+
* @param expression The binding part to evaluate
|
|
2406
|
+
* @param singlePath Whether there is one or multiple path to consider
|
|
2407
|
+
* @returns The string snippet to include in the overall binding definition
|
|
2408
|
+
*/
|
|
2409
|
+
function compilePathParameter(expression: BindingToolkitExpression<PrimitiveType>, singlePath = false): string {
|
|
2410
|
+
let outValue = "";
|
|
2411
|
+
if (expression._type === "Constant") {
|
|
2412
|
+
if (expression.value === undefined) {
|
|
2413
|
+
// Special case otherwise the JSTokenizer complains about incorrect content
|
|
2414
|
+
outValue = `value: 'undefined'`;
|
|
2415
|
+
} else {
|
|
2416
|
+
outValue = `value: ${compileConstant(expression, true)}`;
|
|
2417
|
+
}
|
|
2418
|
+
} else if (expression._type === "PathInModel") {
|
|
2419
|
+
outValue = `path: '${compilePathInModel(expression)}'`;
|
|
2420
|
+
|
|
2421
|
+
outValue += expression.type ? `, type: '${expression.type}'` : `, targetType: 'any'`;
|
|
2422
|
+
if (expression.mode) {
|
|
2423
|
+
outValue += `, mode: '${compileExpression(expression.mode)}'`;
|
|
2424
|
+
}
|
|
2425
|
+
if (hasElements(expression.constraints)) {
|
|
2426
|
+
outValue += `, constraints: ${compileExpression(expression.constraints)}`;
|
|
2427
|
+
}
|
|
2428
|
+
if (hasElements(expression.formatOptions)) {
|
|
2429
|
+
outValue += `, formatOptions: ${compileExpression(expression.formatOptions)}`;
|
|
2430
|
+
}
|
|
2431
|
+
if (hasElements(expression.parameters)) {
|
|
2432
|
+
outValue += `, parameters: ${compileExpression(expression.parameters)}`;
|
|
2433
|
+
}
|
|
2434
|
+
} else {
|
|
2435
|
+
return "";
|
|
2436
|
+
}
|
|
2437
|
+
return singlePath ? outValue : `{${outValue}}`;
|
|
2438
|
+
}
|
|
2439
|
+
|
|
2440
|
+
function hasElements(obj: object | undefined): boolean {
|
|
2441
|
+
return !!obj && Object.keys(obj).length > 0;
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
/**
|
|
2445
|
+
* Compile a binding expression path.
|
|
2446
|
+
*
|
|
2447
|
+
* @param expression The expression to compile.
|
|
2448
|
+
* @returns The compiled path.
|
|
2449
|
+
*/
|
|
2450
|
+
function compilePathInModel<T extends PrimitiveType>(expression: PathInModelExpression<T>): string {
|
|
2451
|
+
return `${expression.modelName ? expression.modelName + ">" : ""}${expression.path}`;
|
|
2452
|
+
}
|