@sap/cds-dk
Version:
Command line client and development toolkit for the SAP Cloud Application Programming Model
154 lines (138 loc) • 5.94 kB
JavaScript
;
let edmxv2CSN = require("./v2parser");
let edmxv4CSN = require("./v4parser");
const messages = require("../message").getMessages();
let common = require("../common");
const { path } = require('../../cds').utils;
let namespaceFileObject = {};
function getNamespaces(context) {
if (context.include_namespaces === "*") context.include_all_namespaces = true;
else if (typeof context.include_namespaces === 'string' && !context.include_namespaces.startsWith('-')) context.namespaces = context.include_namespaces.replace(/ /g, "").split(',');
else throw new Error(messages.INCORRECT_INPUT_NAMESPACES);
}
function _convertEDMXWithMultipleSchemas(edmx, multipleSchemas, namespace) {
/*
This is a quick hack only handling the following case we saw from SFSF:
<edmx:DataServices>
<Schema Namespace="SFODataSet">
<EntityContainer>
<EntitySet Name="Foos" EntityType="SFOData.Foo"> ... </EntitySet>
</EntityContainer>
</Schema>
<Schema Namespace="SFOData">
<EntityType Name="Foo"> ... </EntityType>
</Schema>
</edmx:DataServices>
which is turned into that:
<edmx:DataServices>
<Schema Namespace="SFOData">
<EntityContainer>
<EntitySet Name="Foos" EntityType="SFOData.Foo"> ... </EntitySet>
</EntityContainer>
<EntityType Name="Foo"> ... </EntityType>
</Schema>
</edmx:DataServices>
*/
// Remove the ending and beginning of the first and second schema tag
edmx = edmx.replace(multipleSchemas, '');
// Now capture the schema tag details
let schemaDetails = edmx.match(/(<Schema [^>]*>)/);
// Replace the Namespace value with the correct one
schemaDetails = schemaDetails[0].replace(/Namespace="[^"]+"/, `Namespace="${namespace}"`);
// Place the above computed schema details in the edmx content
edmx = edmx.replace(/(<Schema [^>]*>)/, `${schemaDetails}`);
return edmx;
}
async function edmx2csn(edmx, filename, context) {
let namespace;
// we might get aliased namespace value here
const result = edmx.match(/EntityType="([^"]+)/);
const multipleSchemas = /(\s*<\/Schema>\s*<Schema [^>]*>)\s*/m;
if (result) {
const index = result[1].lastIndexOf(".");
namespace = (index > 0) ? result[1].substring(0, index) : result[1];
}
else {
// to support edmx without entities - unbound actions and functions only
console.warn('There are no entities in the OData model');
let schema = edmx.match(/(<Schema [^>]*>)/);
if (!schema) throw new Error(messages.INVALID_EDMX_METADATA);
schema = schema[0].match(/Namespace="[^"]+"/);
namespace = schema[0].slice(schema[0].lastIndexOf('Namespace') + 11, schema[0].lastIndexOf('"'));
}
// this hack is only used for OData V2, for V4 we support multiple schema scenario
if (context.odataVersion === 'V2' && multipleSchemas.test(edmx)) {
edmx = _convertEDMXWithMultipleSchemas(edmx, multipleSchemas, namespace);
}
let csn = await _generateCSN(edmx, false, true, context);
if (filename) {
// Regex checks for this condition “A-B” must start with a letter or underscore, followed by at most 127 letters, underscores or digits in the fileName
const regex = /^[\p{Letter}\p{Nl}_]{1}[_\p{Letter}\p{Nl}\p{Nd}\p{Mn}\p{Mc}\p{Pc}\p{Cf}]{0,127}$/gu
/**
* V4: <Schema Namespace="Full.Schema.Name" xmlns="http://docs.oasis-open.org/odata/ns/edm" Alias="aliasedValue">
* V2: <Schema Namespace="Full.Schema.Name" xmlns="http://schemas.microsoft.com/ado/2008/09/edm" Alias="aliasedValue">
*
* context.schemaNamespace is set to correct namespace value by the respective parsers
*/
if (context.schemaNamespace) {
namespaceFileObject[context.schemaNamespace] = filename;
} else {
namespaceFileObject[namespace] = filename;
}
for (const [namespace, filename] of Object.entries(namespaceFileObject)) {
// Replace namespace by given filename in the csn file instead of edmx file
const re = RegExp(`([("/])${namespace}([/").])`, 'g');
let name = path.parse(filename).name;
if (!(name.match(regex))) { // check if the filename is in the right format with the regex if not replace unaccepted characters with underscore (_)
name = name.replace(/^[^a-z\p{Nl}_]/gui, '_');
name = name.replace(/[^a-z0-9_\p{Nl}\p{Nd}\p{Mn}\p{Mc}\p{Pc}\p{Cf}]/gui, '_');
}
const fn = `$1${name}$2`;
csn = csn.replace(re, fn);
}
}
return JSON.parse(csn);
}
async function _generateCSN(edmx, ignorePersistenceSkip, mockServerUc, context) {
let csn;
let edmx2jsonModel;
let defaultNamespaces;
let namespaces = [];
if (context.namespaces) namespaces.push(...(context.namespaces));
if (context.odataVersion === "V2") {
// default namespaces to be present
if (!context.include_all_namespaces) {
defaultNamespaces = ['edmx', 'sap', 'm'];
namespaces = namespaces.concat(defaultNamespaces);
namespaces = namespaces.filter((item, pos) => namespaces.indexOf(item) === pos); //removing duplicate values
}
context.namespaces = namespaces;
edmx2jsonModel = await common.generateEDMX2JSON(edmx);
return new Promise(function getCsn(resolve, reject) {
csn = edmxv2CSN.getEdmxv2CSN(edmx2jsonModel, ignorePersistenceSkip, mockServerUc, context);
if (csn) resolve(csn);
else reject("Error Occurred");
});
}
else if (context.odataVersion === 'V4') {
// default namespaces to be present
if (!context.include_all_namespaces) {
defaultNamespaces = ['edmx'];
namespaces = namespaces.concat(defaultNamespaces);
namespaces = namespaces.filter((item, pos) => namespaces.indexOf(item) === pos); //removing duplicate values
}
context.namespaces = namespaces;
return new Promise(function(resolve, reject) {
csn = edmxv4CSN.getEdmxv4CSN(edmx, ignorePersistenceSkip, mockServerUc, context);
if (csn) resolve(csn);
else reject("Error Occurred");
});
}
return new Promise(function invalidFile(resolve, reject) {
reject(new Error(messages.INVALID_EDMX_METADATA));
});
}
module.exports = {
getNamespaces,
edmx2csn
};