Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions backend/xls/appMaker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
const fs = require('fs');
const uuid = require('uuid');
const path = require('path');
const metadataStructureUtil = require('./metadataStructure/index');
const objectStructureUtil = require('./objectStructure/index');
const archiver = require('archiver');
const miscUtil = require('./util/misc');

class appMaker {

constructor(appName) {
this.name = appName;
}

async prepareAppStructure() {
const includedFolders = [
'data',
'meta',
'navigation',
'themes',
'views',
'workflows'
];

this.folders = {};
this.folders['app'] = await prepareAppStructure(includedFolders);
for (const folderName of includedFolders)
this.folders[folderName] = path.join(this.folders.app, folderName);

return this.folders;
}

async writePackageJson(moduleDependencies, appDependencies) {
this.moduleDependencies = moduleDependencies;
this.appDependencies = appDependencies;
this.packageJson = await formPackageJson(this.name, this.moduleDependencies, this.appDependencies);

const packageJsonPath = path.join(this.folders.app, 'package.json');
fs.writeFileSync(packageJsonPath, JSON.stringify(this.packageJson, null, 2));
return packageJsonPath;
}

async writeDeploy() {
this.deploy = await metadataStructureUtil.formDeploy(this.packageJson);

const deployPath = path.join(this.folders.app, 'deploy.json');
fs.writeFileSync(deployPath, JSON.stringify(this.deploy, null, 2));
return deployPath;
}

async writeMetadataStructure(metadataStructure) {
return await metadataStructureUtil.writeMetadataStructure(metadataStructure, path.join(this.folders.app, 'meta'));
}

async fromXls(xls) {
await this.buildApp(await objectStructureUtil.getObjects(xls));
return await this.zip();
}

async buildApp(source) {
await this.prepareAppStructure();
this.objectStructure = objectStructureUtil.prepareObjectStructure(source);
this.metadataStructure = await metadataStructureUtil.deduceMetadata(this.objectStructure);
await metadataStructureUtil.writeMetadataStructure(this.metadataStructure, this.name, this.folders.meta);
await this.writePackageJson({'registry': '*'}, {});
await this.writeDeploy();
this.objectStructure = await objectStructureUtil.expandObjectStructure(this.objectStructure, this.metadataStructure);
await objectStructureUtil.writeObjectStructure(this.objectStructure, this.name, this.folders.data);
await metadataStructureUtil.writeNavigation(this.metadataStructure, this.folders.navigation);
await metadataStructureUtil.writeViewforms(this.metadataStructure, this.folders.views)
}

zip() {
return new Promise((resolve, reject) => {
const archive = archiver('zip', {
zlib: {level: 9}
});
archive.directory(this.folders.app, this.name, {name: this.name});
let chunks = [];
let data;
archive.on('data', (chunk) => {
chunks = chunks.concat(chunk);
});
archive.on('end', () => {
data = Buffer.concat(chunks);
resolve(data);
});
archive.finalize();
});
}

async clean() {
return await miscUtil.rmRecursively(this.folders.app);
}
}

async function prepareAppStructure(includedFolders) {
const tempFolder = path.join(__dirname, 'temp');
if (!fs.existsSync(tempFolder))
await fs.mkdirSync(tempFolder);
const appFolder = path.join(tempFolder, uuid.v4());
await fs.mkdirSync(appFolder);

const fsPromises = [];
for (const folder of includedFolders) {
fsPromises.push(new Promise((resolve, reject) => {
fs.mkdir(path.join(appFolder, folder), null, (err) => {
if (err)
return reject(err);
return resolve();
});
}));
}
await Promise.all(fsPromises);
return appFolder;
}

async function formPackageJson(appName, moduleDependencies, appDependencies) {
const packageJson = {
name: appName,
description: '',
version: '1.0.0',
ionModulesDependencies: {},
ionMetaDependencies: {},
};
packageJson.ionModulesDependencies = moduleDependencies || {};
packageJson.ionMetaDependencies = appDependencies || {};
return packageJson;
}

module.exports = appMaker;
99 changes: 99 additions & 0 deletions backend/xls/dbtypes/xls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
const xlsx = require('xlsx');

/**
* Делает из xls или xlsx массив объектов
* @param {String} path - путь к файлу
* @param {boolean} compact - пропускать объекты, в которых все поля null
* @returns {{headers: [], data: []}}
*/
module.exports = (file, compact = true) => {
const xbook = xlsx.read(file);

const out = {};

for (const sheetName of Object.keys(xbook.Sheets)) {
const sheet = xbook.Sheets[sheetName];
out[sheetName] = {'headers': [], 'data': []};
const topLeftmostCellAddress = sheet['!ref'].split(':')[0];
const bottomRightmostCellAddress = sheet['!ref'].split(':')[1];
const addressPattern = /^(\D+)(\d+)$/;
const leftmostColumn = addressPattern.exec(topLeftmostCellAddress)[1];
const rightmostColumn = addressPattern.exec(bottomRightmostCellAddress)[1];
const firstRow = parseInt(addressPattern.exec(topLeftmostCellAddress)[2]);
const lastRow = parseInt(addressPattern.exec(bottomRightmostCellAddress)[2]);

if (!rightmostColumn)
continue;

const headers = [];
const lastHeaderNum = lettersToIndex(rightmostColumn);
const firstHeaderNum = lettersToIndex(leftmostColumn);
for (let i = firstHeaderNum; i <= lastHeaderNum; i += 1) {
const column = indexToLetters(i);
if (sheet[column + firstRow])
headers.push(sheet[column + firstRow].v);
else
headers.push(null);
}
const data = [];
for (let row = firstRow + 1; row <= lastRow; row += 1) {
const object = {};
for (let column = firstHeaderNum; column <= lastHeaderNum; column += 1) {
const header = headers[column - firstHeaderNum];
if (header == null)
continue;
const columnAddress = indexToLetters(column);
if (sheet[columnAddress + row])
object[header] = sheet[columnAddress + row].v;
else
object[header] = null;
}
if (compact) {
let nullCheck = false;
for (const key of Object.keys(object))
if (object[key] != null) {
nullCheck = true
break;
}
if (!nullCheck)
continue;
}
data.push(object);
}
out[sheetName].headers = headers;
out[sheetName].data = data;
}

return out;

function lettersToIndex(letters) {
const ALPHABET_POWER = 27;
let total = 0;
for (let i = letters.length - 1; i >= 0; i -= 1) {
const value = Math.pow(ALPHABET_POWER, letters.length - 1 - i) * (letters.charCodeAt(i) - 64);
total += value;
}
return total - 1 - Math.floor(total / ALPHABET_POWER);
}

function indexToLetters(index) {
const ALPHABET_POWER = 27;
let indexValue = index + 1 + Math.floor((index) / (ALPHABET_POWER - 1));
let out = '';
let power = -1;
let multiplier;
do {
power += 1;
multiplier = Math.pow(ALPHABET_POWER, power + 1);
} while (indexValue / multiplier >= 1);

while (power >= 0) {
multiplier = Math.pow(ALPHABET_POWER, power);
const charIndex = Math.floor(indexValue / multiplier);
indexValue %= multiplier;
out += String.fromCharCode(charIndex + 64);
power -= 1;
}
return out;
}
}
75 changes: 75 additions & 0 deletions backend/xls/metadataStructure/applyFilter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
module.exports = {
all,
one
}

const fs = require('fs');
const path = require('path');

function all(objects, metadataStructure) {
const filters = fs.readdirSync(path.join(__dirname, 'filters'));
for (const filterFile of filters) {
const filterPath = path.join(__dirname, 'filters', filterFile);
const filter = require(filterPath);
one(filter, objects, metadataStructure);
}
}

function one(filter, objects, metadataStructure) {
for (const object of objects) {

if (
(typeof object === 'object')
&& (!Array.isArray(object))
) {
for (const key of Object.keys(object)) {
const value = object[key];
const template = makeTemplate(value);
if (template) {
const filterTypes = Object.keys(filter);
for (const filterType of filterTypes) {
if ((RegExp(filterType).test(template))) {
metadataStructure[key] = filter[filterType].structure;
metadataStructure[key]['_deducedClass'] = filter[filterType].name;
metadataStructure[key]['_dataType'] = 'object';
}
}
}
if (!metadataStructure[key]) {
metadataStructure[key] = {};
metadataStructure[key]['_dataType'] = typeof value;
}
}
if (!metadataStructure['_dataType'])
metadataStructure['_dataType'] = 'object';

} else if (
(typeof object === 'object')
&& (Array.isArray(object))
) {
if (!metadataStructure['_dataType'])
metadataStructure['_dataType'] = 'collection';

} else {
if (!metadataStructure['_dataType'])
metadataStructure['_dataType'] = typeof object;
}
}
}

function makeTemplate(value) {
let template = null;
switch(typeof value) {
case 'string':
template = value;
template = template.replace(/\d/g, '9');
template = template.replace(/[a-zA-Z]/g, 'z');
break;
case 'number':
template = String(value);
template = template.replace(/\d/g, '9');
break;
}

return template
}
7 changes: 7 additions & 0 deletions backend/xls/metadataStructure/constants.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"PROPERTY_NAMES_TO_IGNORE": [
"_value",
"_deducedClass",
"_dataType"
]
}
12 changes: 12 additions & 0 deletions backend/xls/metadataStructure/deduceMetadata.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const applyFilter = require('./applyFilter');

module.exports = async function deduceMetadata(objectStructure) {
const metadataStructure = {};

for (const objectClass of Object.keys(objectStructure)) {
metadataStructure[objectClass] = {};
applyFilter.all(objectStructure[objectClass], metadataStructure[objectClass]);
}

return metadataStructure;
}
11 changes: 11 additions & 0 deletions backend/xls/metadataStructure/filters/email.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"\\w+@\\w+?.\\w+": {
"name": "email",
"structure": {
"address": {
"_dataType": "string"
},
"_value": "address"
}
}
}
38 changes: 38 additions & 0 deletions backend/xls/metadataStructure/filters/phone.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"\\+99999999999": {
"name": "phone",
"structure": {
"number": {
"_dataType": "string"
}
},
"_value": "number"
},
"\\+999999999999": {
"name": "phone",
"structure": {
"number": {
"_dataType": "string"
}
},
"_value": "number"
},
"\\+9999999999999": {
"name": "phone",
"structure": {
"number": {
"_dataType": "string"
}
},
"_value": "number"
},
"\\+\\d+?-999-999-9999": {
"name": "phone",
"structure": {
"number": {
"_dataType": "string"
}
},
"_value": "number"
}
}
16 changes: 16 additions & 0 deletions backend/xls/metadataStructure/formDeploy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module.exports = formDeploy;

const deployTarget = require('../target/deploy.json');

async function formDeploy(packageJson) {
const deploy = Object.assign({}, deployTarget);
for (const moduleName of Object.keys(packageJson.ionModulesDependencies)) {
deploy.modules[moduleName] = {
'globals': null,
'import': null,
'statics': null
};
}
deploy['namespace'] = packageJson.name;
return deploy;
}
Loading