pir-model-preparer
v2.2.3
Published
Model Preparer (converter from PIR-XSD to Sencha ExtJS Model Classes)
Downloads
5
Readme
Конвертация моделей данных из формата XSD ПИР в формат Sencha Ext JS Model
Инсталяция для пользователя
Установить SWI-Prolog 7.4.2 for Microsoft Windows (64 bit)
http://www.swi-prolog.org/download/stable
Добавить в PATH
строку C:\Program Files\swipl\bin
.
После этого в командной строке должна быть доступна команда swipl
.
Установить зависимостиnpm install
Создать пустой пакет (здесь он называется pir-model
), где будут размещаться сгененированные модели.
В файле package.json
вашего проекта прописать скрипт (здесь он называется pir-model
):
"scripts": {
"pir-model": "node_modules/.bin/pir-model-preparer -c packages/local/pir-model/preparer.config.js"
}
В корне пакета pir-model создать файл preparer.config.js со следующим содержимым:
const extend = require('./preparer/extend');
const prepareClasses = require('./preparer/prepareClasses');
module.exports = {
dirs: {
input: '<путь до WSDL-файлов>',
output: {
result: 'src'
}
},
namespace: 'Pir.model',
extend: extend,
prepareClasses: prepareClasses
};
Здесь namespace укажите для своего проекта.
Создайте файл \packages\local\pir-model\preparer\extend.js со следующим содержимым:
/**
* Вычисление опции extend для каждой модели.
*/
module.exports = function(model) {
const m = model, bt = model.baseType;
return model.baseType ? `${m.namespace}.${bt.path.join('.')}.${bt.className}` : `${m.namespace}.Base`;
};
Создайте файл \packages\local\pir-model\preparer\prepareClasses.js со следующим содержимым:
const fs = require('fs');
const path = require('path');
const _ = require('lodash');
const senchaExtjsGenerator = require('sencha-extjs-generator');
const ModelClass = senchaExtjsGenerator.ModelClass;
const Config = senchaExtjsGenerator.Config;
/**
* Изменение классов перед их сохранением в пакете pir-model.
*/
module.exports = function(modelClassManager) {
const common = require('./common')(modelClassManager);
/**
* Создание дополнительных классов.
*/
const creators = fs.readdirSync(path.join(__dirname, 'create'));
creators.forEach(creatorFilename => {
const creatorFilepath = path.join(__dirname, 'create', creatorFilename);
// Криэтор класса может создавать как один класс, так и массив классов.
const cls = require(creatorFilepath)(common);
modelClassManager.push(cls);
// Любой класс может содержать функцию _onAfterModelClassManagerPush.
// Если он есть, то считаем, что класс предполагает обработку после его добавления в менеджер классов.
(_.isArray(cls) ? cls : [cls]).forEach(cls => {
if (cls._onAfterModelClassManagerPush) cls._onAfterModelClassManagerPush();
});
});
/**
* Переопределение имеющихся классов Override.
* Этим добавляется дополнительный функционал в имеющиеся модели данных.
*/
const overrides = fs.readdirSync(path.join(__dirname, 'override'));
overrides.forEach(overrideFilename => {
const overrideFilepath = path.join(__dirname, 'override', overrideFilename);
require(overrideFilepath)(common);
});
};
Создайте файл \packages\local\pir-model\preparer\common.js со следующим содержимым:
const camelcase = require('camelcase');
const pascalcase = require('pascalcase');
const { Config: ExtjsConfig, ModelClass: ExtjsModelClass } = require('sencha-extjs-generator');
/**
* Общие функции.
*/
module.exports = function(modelClassManager) {
function getModelClassManager() {
return modelClassManager;
}
/**
* Функция для удобной обработки одного класса.
* В качестве контекста this для функции prepareFn выступает сам класс.
*/
function prepareClass(className, prepareFn) {
const cls = modelClassManager.find(className);
if (!cls) console.error('[ERR] prepareClass: Не найден класс "' + className + '".');
if (cls) prepareFn.bind(cls)(cls);
}
/**
* Функция для создания классов, предназначенных для создания хранилищ
* под результаты разных методов АПИ сервера.
* Добавляет название метода сервиса и имя поля, где хранятся массив записей.
* @return {ExtjsModelClass}
*/
function createInfoClass({ baseClassName, name, serviceMethod, rootProperty }) {
const baseClass = modelClassManager.find(baseClassName);
const infoClass = baseClass.clone();
infoClass.name.value = name;
infoClass.extend.value = baseClass.name.localName;
infoClass.proxy.value = new ExtjsConfig([{
name: 'serviceMethod',
value: serviceMethod
}, {
name: 'reader',
value: new ExtjsConfig([{
name: 'rootProperty',
value: `result.${rootProperty}`
}])
}]);
// Внимание, после добавления класса в менеджер классов добавьте очистку полей:
// infoClass.fields.clear();
// Очистку полей можно делать ТОЛЬКО после добавления класса в менеджер классов.
return infoClass;
}
/**
* Вычисление baseClassName и rootProperty для классов информационных моделей.
* На выходе готовый конфиг для метода createInfoClass.
* @param {Object} params
* @param {String} params.name Имя класса без неймспейса, например info.DebtorInfoSearchTemplate.
* @param {String} params.serviceMethod Имя метода в формате camelCase, например, getManagementCompanySearchTemplates.
* @param {String} params.serviceName Имя сервиса = administration | operations | information.
* @return {Object}
*/
function createInfoClassConfig({ name, serviceMethod, serviceName }) {
let rootProperty, baseClassName;
// Имя сервиса в WSDL (точнее имена файлов *.wsdl) отличаются от именования сервисов в клиенте.
const serviceFolderName = {
information: 'debtorInformation',
operations: 'debtorOperations',
administration: 'debtorAdministration'
}[serviceName];
if (!serviceFolderName) throw new Error(`Неизвестный сервис '${serviceName}'.`);
// Ищем класс ответа для данного метода сервера.
const responseClassName = `${serviceFolderName}.${pascalcase(serviceMethod)}Response`;
const responseClass = modelClassManager.find(responseClassName);
if (!responseClass) throw new Error(`Не найден класс '${responseClassName}'.`);
if (!responseClass.has('hasOne')) throw new Error(`В классе ответа сервера '${responseClassName}' нет hasOne с role==result.`);
// В классе ответа ищем поле result в разделе hasOne.
const resultAssociation = responseClass.hasOne.value.find(item => item.find('role').value == 'result');
// В поле result ищем type. Это будет класс результатов в ответе сервера.
const resultClassName = resultAssociation.find('type').value;
const resultClass = modelClassManager.find(resultClassName);
if (!resultClass) throw new Error(`Не найден класс '${resultClassName}'.`);
// В классе результата ищем hasMany.
// Для проверки надо убедится что он есть и в нем только одна запись, иначе ошибка.
if (!resultClass.has('hasMany')) throw new Error(`В классе результата '${resultClassName}' нет hasMany.`);
if (resultClass.hasMany.value.length > 1) throw new Error(`В hasMany класса результата '${resultClassName}' больше одной связи.`);
// Также можно проверить наличие totalRecordCount в fields, иначе ошибка.
if (!resultClass.has('fields')) throw new Error(`В классе '${resultClassName}' нет полей модели.`);
if (!resultClass.fields.value.find(field => field.find('name').value == 'totalRecordCount')) {
throw new Error(`В классе '${resultClassName}' не найдено поле totalRecordCount.`);
}
// Из hasMany следует вытащить role и сделать его PascalCase.
rootProperty = pascalcase(resultClass.hasMany.value[0].find('role').value);
baseClassName = resultClass.hasMany.value[0].find('type').value;
return { baseClassName, name, serviceMethod, rootProperty, serviceName };
}
/**
* Создание класса модели для словарей.
* Все классы наследуются от класса результата ответа сервера на метод getDictionaryDetailsBySearchParams
* На данный момент этим классом является Pir.model.baseModel.type.TBaseDictionary.
* Также в extraParams.DictionaryCode включен код словаря dictionaryCode.
*/
function createDictionaryDetailClass(dictionaryCode) {
const serviceMethod = 'getDictionaryDetailsBySearchParams';
const { baseClassName, rootProperty } = createInfoClassConfig({
serviceName: 'information', serviceMethod
});
const dictionaryDetailClass = new ExtjsModelClass(`dictionary.${dictionaryCode}`, [{
name: 'proxy',
value: new ExtjsConfig([{
name: 'serviceMethod',
value: serviceMethod
}, {
name: 'reader',
value: {
name: 'rootProperty',
value: rootProperty
}
}, {
name: 'extraParams',
value: {
name: 'DictionaryCode',
value: dictionaryCode
}
}])
}]);
dictionaryDetailClass.extend.value = baseClassName;
return dictionaryDetailClass;
}
return {
prepareClass,
createInfoClass,
createInfoClassConfig,
createDictionaryDetailClass
};
}
Создайте каталоги:\packages\local\pir-model\preparer\create
\packages\local\pir-model\preparer\override
с содержимым аналогичным как в проекте w_pir_client_v2
.
Собственно кодогенерацию следует настраивать путем создания скриптов в этих двух директориях.
В директории create создаются скрипты для создания новых моделей.
В директории override создаются оверрайды для существующих.
Запуск кодогенерации
npm run pir-model
Если все было сделано верно, сгенерированные модели будут размещены в директории \packages\local\pir-model\src
.