pir-model-preparer
TypeScript icon, indicating that this package has built-in type declarations

2.2.3 • Public • Published

Конвертация моделей данных из формата 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.

/pir-model-preparer/

    Package Sidebar

    Install

    npm i pir-model-preparer

    Weekly Downloads

    2

    Version

    2.2.3

    License

    ISC

    Unpacked Size

    38 kB

    Total Files

    12

    Last publish

    Collaborators

    • khusamov