@sap/cds-compiler 2.13.8 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +155 -1594
- package/bin/cdsc.js +144 -66
- package/doc/CHANGELOG_ARCHIVE.md +1592 -0
- package/doc/CHANGELOG_BETA.md +3 -4
- package/doc/CHANGELOG_DEPRECATED.md +35 -1
- package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
- package/doc/Versioning.md +20 -1
- package/lib/api/.eslintrc.json +2 -2
- package/lib/api/main.js +237 -122
- package/lib/api/options.js +17 -88
- package/lib/api/validate.js +12 -16
- package/lib/base/keywords.js +216 -109
- package/lib/base/message-registry.js +152 -37
- package/lib/base/messages.js +145 -83
- package/lib/base/model.js +44 -2
- package/lib/base/optionProcessorHelper.js +19 -0
- package/lib/checks/actionsFunctions.js +7 -5
- package/lib/checks/annotationsOData.js +11 -32
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/cdsPersistence.js +1 -0
- package/lib/checks/elements.js +6 -6
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -1
- package/lib/checks/selectItems.js +5 -1
- package/lib/checks/types.js +4 -2
- package/lib/checks/utils.js +2 -2
- package/lib/checks/validator.js +4 -5
- package/lib/compiler/assert-consistency.js +16 -10
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +98 -9
- package/lib/compiler/checks.js +22 -70
- package/lib/compiler/define.js +61 -13
- package/lib/compiler/extend.js +79 -14
- package/lib/compiler/finalize-parse-cdl.js +46 -29
- package/lib/compiler/index.js +100 -37
- package/lib/compiler/moduleLayers.js +7 -0
- package/lib/compiler/populate.js +19 -18
- package/lib/compiler/propagator.js +7 -4
- package/lib/compiler/resolve.js +297 -234
- package/lib/compiler/shared.js +107 -102
- package/lib/compiler/tweak-assocs.js +16 -11
- package/lib/compiler/utils.js +5 -0
- package/lib/edm/annotations/genericTranslation.js +93 -21
- package/lib/edm/csn2edm.js +230 -115
- package/lib/edm/edm.js +305 -226
- package/lib/edm/edmPreprocessor.js +509 -438
- package/lib/edm/edmUtils.js +31 -45
- package/lib/gen/Dictionary.json +98 -22
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +10 -30
- package/lib/gen/language.tokens +105 -114
- package/lib/gen/languageLexer.interp +1 -34
- package/lib/gen/languageLexer.js +889 -1007
- package/lib/gen/languageLexer.tokens +95 -106
- package/lib/gen/languageParser.js +20786 -22199
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +59 -51
- package/lib/json/to-csn.js +10 -10
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/docCommentParser.js +62 -39
- package/lib/language/errorStrategy.js +52 -40
- package/lib/language/genericAntlrParser.js +348 -229
- package/lib/language/language.g4 +629 -653
- package/lib/language/multiLineStringParser.js +14 -42
- package/lib/language/textUtils.js +44 -0
- package/lib/main.d.ts +46 -43
- package/lib/main.js +108 -79
- package/lib/model/csnRefs.js +34 -7
- package/lib/model/csnUtils.js +337 -332
- package/lib/model/enrichCsn.js +1 -0
- package/lib/model/revealInternalProperties.js +30 -10
- package/lib/model/sortViews.js +32 -31
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +73 -46
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/DuplicateChecker.js +4 -7
- package/lib/render/manageConstraints.js +70 -2
- package/lib/render/toCdl.js +1042 -882
- package/lib/render/toHdbcds.js +195 -245
- package/lib/render/toRename.js +44 -22
- package/lib/render/toSql.js +225 -241
- package/lib/render/utils/common.js +145 -15
- package/lib/render/utils/sql.js +20 -19
- package/lib/sql-identifier.js +6 -0
- package/lib/transform/db/.eslintrc.json +4 -3
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +5 -15
- package/lib/transform/db/constraints.js +4 -2
- package/lib/transform/db/expansion.js +22 -16
- package/lib/transform/db/flattening.js +109 -80
- package/lib/transform/db/transformExists.js +7 -7
- package/lib/transform/db/views.js +9 -6
- package/lib/transform/draft/.eslintrc.json +2 -2
- package/lib/transform/draft/db.js +6 -6
- package/lib/transform/draft/odata.js +6 -7
- package/lib/transform/forHanaNew.js +62 -48
- package/lib/transform/forOdataNew.js +49 -50
- package/lib/transform/localized.js +31 -20
- package/lib/transform/odata/toFinalBaseType.js +16 -14
- package/lib/transform/odata/typesExposure.js +146 -198
- package/lib/transform/odata/utils.js +1 -38
- package/lib/transform/transformUtilsNew.js +67 -84
- package/lib/transform/translateAssocsToJoins.js +7 -3
- package/lib/transform/universalCsn/.eslintrc.json +2 -2
- package/lib/transform/universalCsn/coreComputed.js +16 -9
- package/lib/transform/universalCsn/universalCsnEnricher.js +60 -10
- package/lib/utils/file.js +3 -3
- package/lib/utils/moduleResolve.js +13 -6
- package/lib/utils/timetrace.js +20 -21
- package/package.json +35 -4
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- package/doc/ApiMigration.md +0 -237
- package/doc/CommandLineMigration.md +0 -58
- package/doc/ErrorMessages.md +0 -175
- package/doc/FioriAnnotations.md +0 -94
- package/doc/ODataTransformation.md +0 -273
- package/lib/backends.js +0 -529
- package/lib/fix_antlr4-8_warning.js +0 -56
- package/lib/transform/odata/attachPath.js +0 -96
- package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
- package/lib/transform/odata/generateForeignKeyElements.js +0 -261
- package/lib/transform/odata/referenceFlattener.js +0 -296
- package/lib/transform/odata/sortByAssociationDependency.js +0 -105
- package/lib/transform/odata/structuralPath.js +0 -72
- package/lib/transform/odata/structureFlattener.js +0 -171
package/lib/edm/csn2edm.js
CHANGED
|
@@ -7,12 +7,13 @@ const NAVPROP_TRENNER = '_';
|
|
|
7
7
|
const VALUELIST_NAVPROP_PREFIX = '';
|
|
8
8
|
|
|
9
9
|
const edmUtils = require('./edmUtils.js')
|
|
10
|
-
const { initializeModel } = require('./edmPreprocessor.js');
|
|
10
|
+
const { initializeModel, assignAnnotation } = require('./edmPreprocessor.js');
|
|
11
11
|
const translate = require('./annotations/genericTranslation.js');
|
|
12
12
|
const { setProp } = require('../base/model');
|
|
13
|
-
const {
|
|
13
|
+
const { cloneCsnNonDict, isEdmPropertyRendered, isBuiltinType } = require('../model/csnUtils');
|
|
14
14
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
15
15
|
const { makeMessageFunction } = require('../base/messages');
|
|
16
|
+
const { EdmTypeFacetMap, EdmTypeFacetNames, EdmPrimitiveTypeMap, getEdm } = require('./edm.js');
|
|
16
17
|
|
|
17
18
|
/*
|
|
18
19
|
OData V2 spec 06/01/2017 PDF version is available from here:
|
|
@@ -25,11 +26,11 @@ function csn2edm(_csn, serviceName, _options) {
|
|
|
25
26
|
|
|
26
27
|
function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
27
28
|
// get us a fresh model copy that we can work with
|
|
28
|
-
const csn =
|
|
29
|
+
const csn = cloneCsnNonDict(_csn, _options);
|
|
29
30
|
|
|
30
31
|
// use original options for messages; cloned CSN for semantic location
|
|
31
32
|
const messageFunctions = makeMessageFunction(csn, _options, 'to.edmx');
|
|
32
|
-
const { info, warning, error, message,
|
|
33
|
+
const { info, warning, error, message, throwWithAnyError } = messageFunctions;
|
|
33
34
|
checkCSNVersion(csn, _options);
|
|
34
35
|
|
|
35
36
|
let rc = Object.create(null);
|
|
@@ -42,13 +43,14 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
42
43
|
setProp(csn.meta, 'options', _csn.meta.options);
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
const [ allServices,
|
|
46
47
|
allSchemas,
|
|
48
|
+
reqDefs,
|
|
47
49
|
whatsMyServiceRootName,
|
|
48
|
-
|
|
49
|
-
options ] = initializeModel(csn, _options, messageFunctions);
|
|
50
|
+
fallBackSchemaName,
|
|
51
|
+
options ] = initializeModel(csn, _options, messageFunctions, serviceNames);
|
|
50
52
|
|
|
51
|
-
const Edm =
|
|
53
|
+
const Edm = getEdm(options, messageFunctions);
|
|
52
54
|
|
|
53
55
|
const v = options.v;
|
|
54
56
|
if(Object.keys(allServices).length === 0) {
|
|
@@ -74,7 +76,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
74
76
|
services[serviceCsn.name] = createEdm(serviceCsn);
|
|
75
77
|
return services; }, rc);
|
|
76
78
|
}
|
|
77
|
-
|
|
79
|
+
throwWithAnyError();
|
|
78
80
|
return rc;
|
|
79
81
|
|
|
80
82
|
//--------------------------------------------------------------------------------
|
|
@@ -189,9 +191,9 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
189
191
|
fqSchemaXRef.sort((a,b) => b.length-a.length);
|
|
190
192
|
|
|
191
193
|
// Bring the schemas in alphabetical order, service first, root last
|
|
192
|
-
const sortedSchemaNames = Object.keys(subSchemaDictionary).filter(n => n !==
|
|
193
|
-
if(subSchemaDictionary[
|
|
194
|
-
sortedSchemaNames.push(
|
|
194
|
+
const sortedSchemaNames = Object.keys(subSchemaDictionary).filter(n => n !== fallBackSchemaName && n !== serviceCsn.name).sort();
|
|
195
|
+
if(subSchemaDictionary[fallBackSchemaName])
|
|
196
|
+
sortedSchemaNames.push(fallBackSchemaName);
|
|
195
197
|
|
|
196
198
|
// Finally create the schemas and register them in the service.
|
|
197
199
|
LeadSchema = createSchema(subSchemaDictionary[serviceCsn.name]);
|
|
@@ -221,7 +223,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
221
223
|
service._children.forEach(c => {
|
|
222
224
|
c._ec && Object.entries(c._ec._registry).forEach((([setName, arr]) => {
|
|
223
225
|
if(arr.length > 1) {
|
|
224
|
-
error(null, null, { name: c.Namespace, id: setName },
|
|
226
|
+
error(null, null, { name: c._edmAttributes.Namespace, id: setName },
|
|
225
227
|
`Namespace $(NAME): Duplicate entries in EntityContainer with Name=$(ID) for ${arr.map(a =>a.getDuplicateMessage()).join(', ')} `);
|
|
226
228
|
}
|
|
227
229
|
}));
|
|
@@ -232,7 +234,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
232
234
|
|
|
233
235
|
// Sort definitions into their schema container
|
|
234
236
|
function populateSchemas(schemas) {
|
|
235
|
-
Object.entries(
|
|
237
|
+
Object.entries(reqDefs.definitions).forEach(([fqName, art]) => {
|
|
236
238
|
// Identify service members by their definition name only, this allows
|
|
237
239
|
// to let the internal object.name have the sub-schema name.
|
|
238
240
|
// With nested services we must do a longest path match and check whether
|
|
@@ -301,10 +303,18 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
301
303
|
|
|
302
304
|
// Same check for alias (if supported by us)
|
|
303
305
|
const reservedNames = ['Edm', 'odata', 'System', 'Transient'];
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
306
|
+
const loc = ['definitions', schema.name];
|
|
307
|
+
if(reservedNames.includes(schema.name))
|
|
308
|
+
warning('odata-spec-violation-namespace', loc, { names: reservedNames });
|
|
309
|
+
if (schema.name.length > 511)
|
|
310
|
+
warning('odata-spec-violation-namespace', loc, { '#': 'length' });
|
|
311
|
+
else {
|
|
312
|
+
schema.name.split('.').forEach(id => {
|
|
313
|
+
if (!edmUtils.isODataSimpleIdentifier(id))
|
|
314
|
+
message('odata-spec-violation-id', loc, { id });
|
|
315
|
+
});
|
|
307
316
|
}
|
|
317
|
+
|
|
308
318
|
/** @type {any} */
|
|
309
319
|
const Schema = new Edm.Schema(v, schema.name, undefined /* unset alias */, schema._csn, /* annotations */ [], schema.container);
|
|
310
320
|
const EntityContainer = Schema._ec || (LeadSchema && LeadSchema._ec);
|
|
@@ -343,10 +353,11 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
343
353
|
|
|
344
354
|
// fetch all exising children names in a map
|
|
345
355
|
const NamesInSchemaXRef = Schema._children.reduce((acc, cur) => {
|
|
346
|
-
|
|
347
|
-
|
|
356
|
+
const name = cur._edmAttributes.Name;
|
|
357
|
+
if(acc[name] === undefined) {
|
|
358
|
+
acc[name] = [ cur ];
|
|
348
359
|
} else {
|
|
349
|
-
acc[
|
|
360
|
+
acc[name].push(cur);
|
|
350
361
|
}
|
|
351
362
|
return acc;
|
|
352
363
|
}, Object.create(null) );
|
|
@@ -368,12 +379,12 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
368
379
|
}
|
|
369
380
|
if(Schema._children.length === 0) {
|
|
370
381
|
// FIXME: Location for sub schemas?
|
|
371
|
-
warning(null, ['definitions', Schema.Namespace], { name: Schema.Namespace }, 'Schema $(NAME) is empty');
|
|
382
|
+
warning(null, ['definitions', Schema._edmAttributes.Namespace], { name: Schema._edmAttributes.Namespace }, 'Schema $(NAME) is empty');
|
|
372
383
|
}
|
|
373
384
|
|
|
374
385
|
Object.entries(NamesInSchemaXRef).forEach(([name, refs]) => {
|
|
375
386
|
if(refs.length > 1) {
|
|
376
|
-
error(null, ['definitions', `${Schema.Namespace}.${name}`], { name: Schema.Namespace },
|
|
387
|
+
error(null, ['definitions', `${Schema._edmAttributes.Namespace}.${name}`], { name: Schema._edmAttributes.Namespace },
|
|
377
388
|
'Duplicate name in Schema $(NAME)');
|
|
378
389
|
}
|
|
379
390
|
});
|
|
@@ -384,26 +395,34 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
384
395
|
{
|
|
385
396
|
const EntityTypeName = entityCsn.name.replace(schemaNamePrefix, '');
|
|
386
397
|
const EntitySetName = edmUtils.getBaseName(entityCsn.$entitySetName || entityCsn.name);
|
|
387
|
-
|
|
398
|
+
const isSingleton = edmUtils.isSingleton(entityCsn) && options.isV4();
|
|
388
399
|
const [ properties, hasStream ] = createProperties(entityCsn);
|
|
389
400
|
|
|
390
401
|
const loc = ['definitions', entityCsn.name];
|
|
391
402
|
const type = `${schema.name}.${EntityTypeName}`;
|
|
392
|
-
if(properties.length === 0)
|
|
403
|
+
if(properties.length === 0)
|
|
393
404
|
warning(null, loc, { type }, 'EDM EntityType $(TYPE) has no properties');
|
|
394
|
-
|
|
405
|
+
else if(entityCsn.$edmKeyPaths.length === 0 && !isSingleton)
|
|
395
406
|
message('odata-spec-violation-no-key', loc);
|
|
396
|
-
|
|
407
|
+
|
|
408
|
+
if(!edmUtils.isODataSimpleIdentifier(EntityTypeName))
|
|
409
|
+
message('odata-spec-violation-id', loc, { id: EntityTypeName });
|
|
410
|
+
|
|
397
411
|
properties.forEach(p => {
|
|
398
|
-
const pLoc = [...loc, 'elements', p.Name];
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
if(p.Name === EntityTypeName) {
|
|
412
|
+
const pLoc = [...loc, 'elements', p._edmAttributes.Name];
|
|
413
|
+
edmTypeCompatibilityCheck(p, pLoc);
|
|
414
|
+
if(p._edmAttributes.Name === EntityTypeName)
|
|
403
415
|
warning('odata-spec-violation-property-name', pLoc, { kind: entityCsn.kind });
|
|
404
|
-
|
|
405
|
-
if(options.isV2() && p._isCollection && !edmUtils.isAssociationOrComposition(p._csn))
|
|
406
|
-
warning('odata-spec-violation-array', pLoc, {
|
|
416
|
+
|
|
417
|
+
if(options.isV2() && p._isCollection && !edmUtils.isAssociationOrComposition(p._csn))
|
|
418
|
+
warning('odata-spec-violation-array', pLoc, { version: '2.0' });
|
|
419
|
+
|
|
420
|
+
if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
|
|
421
|
+
message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
|
|
422
|
+
else if (options.isV2() && /^(_|[0-9])/.test(p._edmAttributes.Name)) {
|
|
423
|
+
// FIXME: Rewrite signalIllegalIdentifier function to be more flexible
|
|
424
|
+
message('odata-spec-violation-id', pLoc,
|
|
425
|
+
{ prop: p._edmAttributes.Name[0], id: p._edmAttributes.Name, version: '2.0', '#': 'v2firstchar' });
|
|
407
426
|
}
|
|
408
427
|
});
|
|
409
428
|
|
|
@@ -411,8 +430,10 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
411
430
|
const attributes = { Name : EntityTypeName };
|
|
412
431
|
|
|
413
432
|
// CDXCORE-CDXCORE-173
|
|
414
|
-
if(options.isV2() && hasStream)
|
|
415
|
-
attributes['m:HasStream'] =
|
|
433
|
+
if(options.isV2() && hasStream) {
|
|
434
|
+
attributes['m:HasStream'] = true;
|
|
435
|
+
assignAnnotation(entityCsn, '@Core.MediaType', hasStream);
|
|
436
|
+
}
|
|
416
437
|
|
|
417
438
|
Schema.append(new Edm.EntityType(v, attributes, properties, entityCsn));
|
|
418
439
|
|
|
@@ -424,7 +445,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
424
445
|
if(edmUtils.isSingleton(entityCsn) && options.isV4()) {
|
|
425
446
|
containerEntry = new Edm.Singleton(v, { Name: EntitySetName, Type: fullQualified(EntityTypeName) }, entityCsn);
|
|
426
447
|
if(entityCsn['@odata.singleton.nullable'])
|
|
427
|
-
containerEntry.Nullable= true;
|
|
448
|
+
containerEntry._edmAttributes.Nullable= true;
|
|
428
449
|
}
|
|
429
450
|
else {
|
|
430
451
|
containerEntry = new Edm.EntitySet(v, { Name: EntitySetName, EntityType: fullQualified(EntityTypeName) }, entityCsn);
|
|
@@ -447,14 +468,19 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
447
468
|
}
|
|
448
469
|
|
|
449
470
|
// add bound/unbound actions/functions for V4
|
|
450
|
-
function createActionV4(actionCsn,
|
|
471
|
+
function createActionV4(actionCsn, _name, entityCsn=undefined)
|
|
451
472
|
{
|
|
452
473
|
const iAmAnAction = actionCsn.kind === 'action';
|
|
453
|
-
|
|
454
474
|
const actionName = edmUtils.getBaseName(actionCsn.name);
|
|
455
|
-
|
|
456
475
|
const attributes = { Name: actionName, IsBound : false };
|
|
457
476
|
|
|
477
|
+
const loc = entityCsn
|
|
478
|
+
? [ 'definitions', entityCsn.name, 'actions', actionCsn.name ]
|
|
479
|
+
: [ 'definitions', actionCsn.name ];
|
|
480
|
+
|
|
481
|
+
if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
|
|
482
|
+
message('odata-spec-violation-id', loc, { id: attributes.Name });
|
|
483
|
+
|
|
458
484
|
if(!iAmAnAction)
|
|
459
485
|
attributes.IsComposable = false;
|
|
460
486
|
|
|
@@ -468,7 +494,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
468
494
|
|
|
469
495
|
if(entityCsn != undefined)
|
|
470
496
|
{
|
|
471
|
-
actionNode.IsBound
|
|
497
|
+
actionNode.setEdmAttribute('IsBound', true);
|
|
472
498
|
const bpType = fullQualified(entityCsn.name);
|
|
473
499
|
// Binding Parameter: 'in' at first position in sequence, this is decisive!
|
|
474
500
|
if(actionCsn['@cds.odata.bindingparameter.collection'])
|
|
@@ -489,7 +515,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
489
515
|
const definition = schemaCsn.definitions[rt];
|
|
490
516
|
if(definition && definition.kind === 'entity' && !definition.abstract)
|
|
491
517
|
{
|
|
492
|
-
actionImport.EntitySet
|
|
518
|
+
actionImport.setEdmAttribute('EntitySet', edmUtils.getBaseName(rt));
|
|
493
519
|
}
|
|
494
520
|
}
|
|
495
521
|
EntityContainer.register(actionImport);
|
|
@@ -497,15 +523,22 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
497
523
|
|
|
498
524
|
// Parameter Nodes
|
|
499
525
|
edmUtils.forAll(actionCsn.params, (parameterCsn, parameterName) => {
|
|
500
|
-
|
|
526
|
+
const p = new Edm.Parameter(v, { Name: parameterName }, parameterCsn );
|
|
527
|
+
const pLoc = [ ...loc, 'params', p._edmAttributes.Name ];
|
|
528
|
+
if(!edmUtils.isODataSimpleIdentifier(parameterName))
|
|
529
|
+
message('odata-spec-violation-id', pLoc, { id: parameterName });
|
|
530
|
+
|
|
531
|
+
edmTypeCompatibilityCheck(p, pLoc);
|
|
532
|
+
actionNode.append(p);
|
|
501
533
|
});
|
|
502
534
|
|
|
503
535
|
// return type if any
|
|
504
536
|
if(actionCsn.returns) {
|
|
505
537
|
actionNode._returnType = new Edm.ReturnType(v, actionCsn.returns);
|
|
538
|
+
edmTypeCompatibilityCheck(actionNode._returnType, [ ...loc, 'returns' ]);
|
|
506
539
|
// if binding type matches return type add attribute EntitySetPath
|
|
507
540
|
if(entityCsn != undefined && fullQualified(entityCsn.name) === actionNode._returnType._type) {
|
|
508
|
-
actionNode.EntitySetPath
|
|
541
|
+
actionNode.setEdmAttribute('EntitySetPath', bpName);
|
|
509
542
|
}
|
|
510
543
|
}
|
|
511
544
|
Schema.addAction(actionNode);
|
|
@@ -515,7 +548,8 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
515
548
|
function createActionV2(actionCsn, name, entityCsn=undefined)
|
|
516
549
|
{
|
|
517
550
|
/** @type {object} */
|
|
518
|
-
const
|
|
551
|
+
const attributes = { Name: name.replace(schemaNamePrefix, '') };
|
|
552
|
+
const functionImport = new Edm.FunctionImport(v, attributes );
|
|
519
553
|
|
|
520
554
|
// inserted now to maintain attribute order with old odata generator...
|
|
521
555
|
/*
|
|
@@ -528,19 +562,25 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
528
562
|
in the spec and advised mention it as in V4
|
|
529
563
|
*/
|
|
530
564
|
|
|
531
|
-
const
|
|
565
|
+
const loc = entityCsn
|
|
566
|
+
? [ 'definitions', entityCsn.name, 'actions', actionCsn.name ]
|
|
567
|
+
: [ 'definitions', actionCsn.name ];
|
|
568
|
+
|
|
569
|
+
if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
|
|
570
|
+
message('odata-spec-violation-id', loc, { id: attributes.Name });
|
|
571
|
+
|
|
532
572
|
const rt = actionCsn.returns && ((actionCsn.returns.items && actionCsn.returns.items.type) || actionCsn.returns.type);
|
|
533
573
|
if(rt) // add EntitySet attribute only if return type is an entity
|
|
534
574
|
{
|
|
535
575
|
const defintion = schemaCsn.definitions[rt];
|
|
536
576
|
if(defintion && edmUtils.isEntity(defintion))
|
|
537
577
|
{
|
|
538
|
-
functionImport.EntitySet
|
|
578
|
+
functionImport.setEdmAttribute('EntitySet', rt.replace(schemaNamePrefix, ''));
|
|
539
579
|
}
|
|
540
580
|
}
|
|
541
581
|
|
|
542
582
|
if(actionCsn.returns)
|
|
543
|
-
functionImport.ReturnType
|
|
583
|
+
functionImport.setEdmAttribute('ReturnType', getReturnType(actionCsn));
|
|
544
584
|
|
|
545
585
|
if(actionCsn.kind === 'function')
|
|
546
586
|
functionImport.setXml( {'m:HttpMethod': 'GET' });
|
|
@@ -551,7 +591,8 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
551
591
|
{
|
|
552
592
|
// Make bound function names always unique as per Ralf's recommendation
|
|
553
593
|
functionImport.setXml( {'sap:action-for': fullQualified(entityCsn.name) } );
|
|
554
|
-
|
|
594
|
+
const name = entityCsn.name.replace(schemaNamePrefix, '') + '_' + functionImport._edmAttributes.Name;
|
|
595
|
+
functionImport.setEdmAttribute('Name', name);
|
|
555
596
|
|
|
556
597
|
// Binding Parameter: Primary Keys at first position in sequence, this is decisive!
|
|
557
598
|
// V2 XML: Nullable=false is set because we reuse the primary key property for the parameter
|
|
@@ -573,14 +614,22 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
573
614
|
// V2 XML spec does only mention default Nullable=true for Properties not for Parameters so omitting Nullable=true let
|
|
574
615
|
// the client assume that Nullable is false.... Correct Nullable Handling is done inside Parameter constructor
|
|
575
616
|
edmUtils.forAll(actionCsn.params, (parameterCsn, parameterName) => {
|
|
576
|
-
const
|
|
617
|
+
const pLoc = [...loc, 'params', parameterName];
|
|
577
618
|
const param = new Edm.Parameter(v, { Name: parameterName }, parameterCsn, 'In' );
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
619
|
+
edmTypeCompatibilityCheck(param, pLoc);
|
|
620
|
+
if(!edmUtils.isODataSimpleIdentifier(parameterName))
|
|
621
|
+
message('odata-spec-violation-id', pLoc, { id: parameterName });
|
|
622
|
+
|
|
623
|
+
// only scalar or structured type in V2 (not entity)
|
|
624
|
+
if(param._type &&
|
|
625
|
+
!param._type.startsWith('Edm.') &&
|
|
626
|
+
csn.definitions[param._type] &&
|
|
627
|
+
!edmUtils.isStructuredType(csn.definitions[param._type]))
|
|
628
|
+
warning('odata-spec-violation-param', pLoc, { version: '2.0' });
|
|
629
|
+
|
|
630
|
+
if(param._isCollection)
|
|
631
|
+
warning('odata-spec-violation-array', pLoc, { version: '2.0' });
|
|
632
|
+
|
|
584
633
|
functionImport.append(param);
|
|
585
634
|
});
|
|
586
635
|
|
|
@@ -590,19 +639,33 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
590
639
|
function getReturnType(action)
|
|
591
640
|
{
|
|
592
641
|
// it is safe to assume that either type or items.type are set
|
|
642
|
+
const returnsLoc = [ ...loc, 'returns'];
|
|
593
643
|
const returns = action.returns.items || action.returns;
|
|
594
644
|
let type = returns.type;
|
|
595
|
-
if (type){
|
|
645
|
+
if (type) {
|
|
596
646
|
if (!isBuiltinType(type) && csn.definitions[type].kind !== 'entity' && csn.definitions[type].kind !== 'type') {
|
|
597
|
-
|
|
598
|
-
warning('odata-spec-violation-returns', returnsLoc, { kind: action.kind, api: 'OData V2' });
|
|
647
|
+
warning('odata-spec-violation-returns', returnsLoc, { kind: action.kind, version: '2.0' });
|
|
599
648
|
}
|
|
600
|
-
|
|
649
|
+
else if(isBuiltinType(type)) {
|
|
650
|
+
type = edmUtils.mapCdsToEdmType(returns, messageFunctions, true);
|
|
651
|
+
if(type) {
|
|
652
|
+
const td = EdmPrimitiveTypeMap[type];
|
|
653
|
+
if(td && !td.v2) {
|
|
654
|
+
message('odata-spec-violation-type', returnsLoc,
|
|
655
|
+
{ type, version: '2.0', '#': 'incompatible' });
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
message('odata-spec-violation-type-unknown', returnsLoc, { type });
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if(action.returns._isCollection)
|
|
663
|
+
type = `Collection(${type})`
|
|
664
|
+
}
|
|
665
|
+
else {
|
|
666
|
+
// type is missing
|
|
667
|
+
message('odata-spec-violation-type', returnsLoc);
|
|
601
668
|
}
|
|
602
|
-
|
|
603
|
-
if(action.returns._isCollection)
|
|
604
|
-
type = `Collection(${type})`
|
|
605
|
-
|
|
606
669
|
return type;
|
|
607
670
|
}
|
|
608
671
|
}
|
|
@@ -610,22 +673,22 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
610
673
|
/**
|
|
611
674
|
* @param {object} elementsCsn
|
|
612
675
|
* @param {object} edmParentCsn
|
|
613
|
-
* @returns {[object[],
|
|
676
|
+
* @returns {[object[], any]} Returns a [ [ Edm Properties ], boolean hasStream ]:
|
|
614
677
|
* array of Edm Properties
|
|
615
|
-
*
|
|
678
|
+
* hasStream : value of @Core.MediaType assignment
|
|
616
679
|
*/
|
|
617
680
|
function createProperties(elementsCsn, edmParentCsn=elementsCsn)
|
|
618
681
|
{
|
|
619
682
|
const props = [];
|
|
620
683
|
let hasStream = false;
|
|
684
|
+
const streamProps = [];
|
|
685
|
+
|
|
621
686
|
edmUtils.forAll(elementsCsn.elements, (elementCsn, elementName) =>
|
|
622
687
|
{
|
|
623
688
|
if(elementCsn._edmParentCsn == undefined)
|
|
624
689
|
setProp(elementCsn, '_edmParentCsn', edmParentCsn);
|
|
625
690
|
|
|
626
|
-
if(
|
|
627
|
-
if(edmUtils.isAssociationOrComposition(elementCsn))
|
|
628
|
-
{
|
|
691
|
+
if(edmUtils.isAssociationOrComposition(elementCsn)) {
|
|
629
692
|
// Foreign keys are part of the generic elementCsn.elements property creation
|
|
630
693
|
|
|
631
694
|
// This is the V4 edmx:NavigationProperty
|
|
@@ -633,40 +696,49 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
633
696
|
|
|
634
697
|
// suppress navprop creation only if @odata.navigable:false is not annotated.
|
|
635
698
|
// (undefined !== false) still evaluates to true
|
|
636
|
-
|
|
699
|
+
if (!elementCsn._target.abstract && elementCsn['@odata.navigable'] !== false)
|
|
637
700
|
{
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
701
|
+
const navProp = new Edm.NavigationProperty(v, {
|
|
702
|
+
Name: elementName,
|
|
703
|
+
Type: elementCsn._target.name
|
|
704
|
+
}, elementCsn);
|
|
705
|
+
props.push(navProp);
|
|
643
706
|
// save the navProp in the global array for late constraint building
|
|
644
|
-
|
|
645
|
-
}
|
|
707
|
+
navigationProperties.push(navProp);
|
|
646
708
|
}
|
|
709
|
+
}
|
|
647
710
|
// render ordinary property if element is NOT ...
|
|
648
711
|
// 1) ... annotated @cds.api.ignore
|
|
649
712
|
// 2) ... annotated @odata.foreignKey4 and odataFormat: structured
|
|
650
713
|
|
|
651
|
-
|
|
714
|
+
else if(isEdmPropertyRendered(elementCsn, options))
|
|
652
715
|
{
|
|
653
716
|
// CDXCORE-CDXCORE-173
|
|
654
717
|
// V2: filter @Core.MediaType
|
|
655
|
-
|
|
718
|
+
if ( options.isV2() && elementCsn['@Core.MediaType']) {
|
|
719
|
+
hasStream = elementCsn['@Core.MediaType'];
|
|
720
|
+
delete elementCsn['@Core.MediaType'];
|
|
656
721
|
// CDXCORE-CDXCORE-177:
|
|
657
722
|
// V2: don't render element but add attribute 'm:HasStream="true' to EntityType
|
|
658
723
|
// V4: render property type 'Edm.Stream'
|
|
659
|
-
|
|
660
|
-
info(null, ['definitions', elementsCsn.name], { name: elementsCsn.name, id: elementName, anno: '@Core.MediaType' },
|
|
661
|
-
'$(NAME): Property $(ID) annotated with $(ANNO) is removed from EDM in OData V2');
|
|
724
|
+
streamProps.push(elementName);
|
|
662
725
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
}
|
|
726
|
+
} else {
|
|
727
|
+
props.push(new Edm.Property(v, { Name: elementName }, elementCsn));
|
|
666
728
|
}
|
|
667
729
|
}
|
|
668
730
|
|
|
669
731
|
});
|
|
732
|
+
if(options.isV2()) {
|
|
733
|
+
if(streamProps.length > 1) {
|
|
734
|
+
error(null, ['definitions', elementsCsn.name], { names: streamProps, version: '2.0', anno: '@Core.MediaType' },
|
|
735
|
+
`Expected only one element to be annotated with $(ANNO) for OData $(VERSION) but found $(NAMES)`);
|
|
736
|
+
}
|
|
737
|
+
else if(streamProps.length === 1) {
|
|
738
|
+
info(null, ['definitions', elementsCsn.name], { id: streamProps[0], version: '2.0', anno: '@Core.MediaType' },
|
|
739
|
+
'Property $(ID) annotated with $(ANNO) is removed from EDM for OData $(VERSION)');
|
|
740
|
+
}
|
|
741
|
+
}
|
|
670
742
|
return [ props, hasStream ];
|
|
671
743
|
}
|
|
672
744
|
|
|
@@ -681,24 +753,27 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
681
753
|
const loc = ['definitions', structuredTypeCsn.name];
|
|
682
754
|
|
|
683
755
|
if(properties.length === 0) {
|
|
684
|
-
warning(null,
|
|
756
|
+
warning(null, loc, { name: structuredTypeCsn.name },
|
|
685
757
|
'EDM ComplexType $(NAME) has no properties');
|
|
686
758
|
}
|
|
759
|
+
if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
|
|
760
|
+
message('odata-spec-violation-id', loc, { id: attributes.Name });
|
|
761
|
+
|
|
687
762
|
properties.forEach(p => {
|
|
688
|
-
const pLoc = [ ...loc, ...(structuredTypeCsn.items ? ['items', 'elements'] : [ 'elements' ]), p.Name ];
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
}
|
|
692
|
-
if(p.Name === complexType.Name) {
|
|
763
|
+
const pLoc = [ ...loc, ...(structuredTypeCsn.items ? ['items', 'elements'] : [ 'elements' ]), p._edmAttributes.Name ];
|
|
764
|
+
edmTypeCompatibilityCheck(p, pLoc);
|
|
765
|
+
if(p._edmAttributes.Name === complexType._edmAttributes.Name)
|
|
693
766
|
warning('odata-spec-violation-property-name', pLoc, { kind: structuredTypeCsn.kind });
|
|
694
|
-
|
|
767
|
+
|
|
768
|
+
if(!edmUtils.isODataSimpleIdentifier(p._edmAttributes.Name))
|
|
769
|
+
message('odata-spec-violation-id', pLoc, { id: p._edmAttributes.Name });
|
|
770
|
+
|
|
695
771
|
if(options.isV2()) {
|
|
696
|
-
if(p._isCollection && !edmUtils.isAssociationOrComposition(p._csn))
|
|
697
|
-
warning('odata-spec-violation-array', pLoc, {
|
|
698
|
-
|
|
699
|
-
if(edmUtils.isAssociationOrComposition(p._csn))
|
|
700
|
-
warning('odata-spec-violation-assoc', pLoc, {
|
|
701
|
-
}
|
|
772
|
+
if(p._isCollection && !edmUtils.isAssociationOrComposition(p._csn))
|
|
773
|
+
warning('odata-spec-violation-array', pLoc, { version: '2.0' });
|
|
774
|
+
|
|
775
|
+
if(edmUtils.isAssociationOrComposition(p._csn))
|
|
776
|
+
warning('odata-spec-violation-assoc', pLoc, { version: '2.0' });
|
|
702
777
|
}
|
|
703
778
|
});
|
|
704
779
|
|
|
@@ -712,8 +787,12 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
712
787
|
function createTypeDefinition(typeCsn)
|
|
713
788
|
{
|
|
714
789
|
// derived types are already resolved to base types
|
|
715
|
-
const
|
|
716
|
-
|
|
790
|
+
const attributes = { Name: typeCsn.name.replace(schemaNamePrefix, '') };
|
|
791
|
+
if(!edmUtils.isODataSimpleIdentifier(attributes.Name))
|
|
792
|
+
message('odata-spec-violation-id', ['definitions', typeCsn.name], { id: attributes.Name });
|
|
793
|
+
|
|
794
|
+
const typeDef = new Edm.TypeDefinition(v, attributes, typeCsn );
|
|
795
|
+
edmTypeCompatibilityCheck(typeDef, [ 'definitions', typeCsn.name ]);
|
|
717
796
|
Schema.append(typeDef);
|
|
718
797
|
}
|
|
719
798
|
|
|
@@ -735,7 +814,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
735
814
|
{
|
|
736
815
|
let constraints = navigationProperty._csn._constraints;
|
|
737
816
|
let parentName = navigationProperty._csn._edmParentCsn.name.replace(schemaNamePrefix, '');
|
|
738
|
-
let plainAssocName = parentName + NAVPROP_TRENNER + navigationProperty.Name.replace(VALUELIST_NAVPROP_PREFIX, '');
|
|
817
|
+
let plainAssocName = parentName + NAVPROP_TRENNER + navigationProperty._edmAttributes.Name.replace(VALUELIST_NAVPROP_PREFIX, '');
|
|
739
818
|
let assocName = plainAssocName;
|
|
740
819
|
let i = 1;
|
|
741
820
|
while(NamesInSchemaXRef[assocName] !== undefined) {
|
|
@@ -743,7 +822,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
743
822
|
}
|
|
744
823
|
|
|
745
824
|
let fromRole = parentName;
|
|
746
|
-
let toRole = navigationProperty.Type.replace(schemaAliasPrefix, ''); // <= navprops type should be prefixed with alias
|
|
825
|
+
let toRole = navigationProperty._edmAttributes.Type.replace(schemaAliasPrefix, ''); // <= navprops type should be prefixed with alias
|
|
747
826
|
|
|
748
827
|
let fromEntityType = fromRole;
|
|
749
828
|
let toEntityType = toRole;
|
|
@@ -763,17 +842,14 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
763
842
|
}
|
|
764
843
|
|
|
765
844
|
// add V2 attributes to navigationProperty
|
|
766
|
-
navigationProperty.Relationship
|
|
767
|
-
navigationProperty.FromRole
|
|
768
|
-
navigationProperty.ToRole
|
|
845
|
+
navigationProperty.setEdmAttribute('Relationship', fullQualified(assocName));
|
|
846
|
+
navigationProperty.setEdmAttribute('FromRole', fromRole);
|
|
847
|
+
navigationProperty.setEdmAttribute('ToRole', toRole);
|
|
769
848
|
|
|
770
849
|
// remove V4 attributes
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
delete navigationProperty.Partner;
|
|
775
|
-
if(navigationProperty.ContainsTarget != undefined)
|
|
776
|
-
delete navigationProperty.ContainsTarget;
|
|
850
|
+
navigationProperty.removeEdmAttribute('Type');
|
|
851
|
+
navigationProperty.removeEdmAttribute('Partner');
|
|
852
|
+
navigationProperty.removeEdmAttribute('ContainsTarget');
|
|
777
853
|
|
|
778
854
|
/*
|
|
779
855
|
If NavigationProperty is a backlink association (constraints._originAssocCsn is set), then there are two options:
|
|
@@ -799,7 +875,7 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
799
875
|
assocName = plainAssocName + '_' + i++;
|
|
800
876
|
}
|
|
801
877
|
|
|
802
|
-
navigationProperty.Relationship
|
|
878
|
+
navigationProperty.setEdmAttribute('Relationship', fullQualified(assocName));
|
|
803
879
|
|
|
804
880
|
reuseAssoc = !!forwardAssocCsn._NavigationProperty;
|
|
805
881
|
constraints = forwardAssocCsn._constraints;
|
|
@@ -854,17 +930,18 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
854
930
|
|
|
855
931
|
// generate the Edm.Annotations tree and append it to the corresponding schema
|
|
856
932
|
function addAnnotations() {
|
|
857
|
-
let { annos, usedVocabularies } = translate.csn2annotationEdm(
|
|
933
|
+
let { annos, usedVocabularies } = translate.csn2annotationEdm(reqDefs, serviceCsn.name, Edm, options, messageFunctions);
|
|
858
934
|
// distribute edm:Annotations into the schemas
|
|
859
935
|
// Distribute each anno into Schema
|
|
860
936
|
annos.forEach(anno => {
|
|
861
|
-
let targetSchema = whatsMySchemaName(anno.Target);
|
|
937
|
+
let targetSchema = whatsMySchemaName(anno._edmAttributes.Target);
|
|
862
938
|
// if no target schema has been found, it's a service annotation that applies to the service schema
|
|
863
939
|
if(targetSchema === undefined)
|
|
864
940
|
targetSchema = serviceCsn.name;
|
|
865
941
|
if(targetSchema) {
|
|
866
942
|
if(targetSchema !== serviceCsn.name) {
|
|
867
|
-
|
|
943
|
+
const newTarget = anno._edmAttributes.Target.replace(serviceCsn.name + '.', '');
|
|
944
|
+
anno.setEdmAttribute('Target', newTarget);
|
|
868
945
|
}
|
|
869
946
|
edm._service._schemas[targetSchema]._annotations.push(anno);
|
|
870
947
|
}
|
|
@@ -877,6 +954,44 @@ function csn2edmAll(_csn, _options, serviceNames=undefined) {
|
|
|
877
954
|
edm._defaultRefs.push(r);
|
|
878
955
|
})
|
|
879
956
|
}
|
|
957
|
+
|
|
958
|
+
function edmTypeCompatibilityCheck(p, pLoc) {
|
|
959
|
+
const edmType = p._type;
|
|
960
|
+
if(!edmType) {
|
|
961
|
+
message('odata-spec-violation-type', pLoc);
|
|
962
|
+
}
|
|
963
|
+
else if(p._scalarType) {
|
|
964
|
+
const td = EdmPrimitiveTypeMap[edmType];
|
|
965
|
+
if(td) {
|
|
966
|
+
// The renderer/type mapper doesn't/shouldn't produce incompatible types and facets.
|
|
967
|
+
// Only the unknown type warning may be triggered by an unknown @odata.Type override.
|
|
968
|
+
if(td.v2 !== p.v2 && td.v4 !== p.v4)
|
|
969
|
+
message('odata-spec-violation-type', pLoc,
|
|
970
|
+
{ type:edmType, version: (p.v4 ? '4.0' : '2.0'), '#': 'incompatible' });
|
|
971
|
+
EdmTypeFacetNames.forEach(name => {
|
|
972
|
+
const facet = EdmTypeFacetMap[name];
|
|
973
|
+
const optional =
|
|
974
|
+
(facet.optional !== undefined)
|
|
975
|
+
? (Array.isArray(facet.optional)
|
|
976
|
+
? facet.optional.includes(edmType)
|
|
977
|
+
: facet.optional)
|
|
978
|
+
: false;
|
|
979
|
+
|
|
980
|
+
// facet is not in attributes
|
|
981
|
+
// facet is member of type definition and mandatory
|
|
982
|
+
// node and facet version match
|
|
983
|
+
if(!p._edmAttributes[name] && td[name] && !optional && (p.v2 === facet.v2 || p.v4 === facet.v4)) {
|
|
984
|
+
message('odata-spec-violation-type', pLoc,
|
|
985
|
+
{ type:edmType, name, version: (p.v4 ? '4.0' : '2.0'), '#': 'facet' });
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
else {
|
|
990
|
+
message('odata-spec-violation-type-unknown', pLoc,
|
|
991
|
+
{ type:edmType });
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
880
995
|
}
|
|
881
996
|
}
|
|
882
997
|
module.exports = { csn2edm, csn2edmAll };
|