Cоздание скриптов в Enfocus Switch на примере XLSXtoCSV

Cоздание скриптов в Enfocus Switch на примере XLSXtoCSV


В данном тексте будет описано пошаговое создание срипта для Switch, но здесь не будет подробного описания базового javascript. Подразумевается, что вы уже более менее знаете, что такое функции и переменные, циклы, а также более продвинутые темы вроде async / await.

Для создания скрипта нам понадобятся:

1) Сам Switch и устанавливаемые с ним в комплекте программы:

SwitchScriptTool - для создания самого скрипта и всех сопутствующих необходимых файлов, а после написания скрипта - для упаковки множества файлов в единый *.sscript. Для удобства работы с программой рекомендую добавить её в системную переменную PATH (Control panel - System settings - Advanced - Environment Variables - PATH - добавляем туда путь к программе C:\Program Files\Enfocus\Enfocus Switch\SwitchScriptTool), тогда из консольной строки приложение можно будет вызывать просто написав SwitchScriptTool

Switch Scripter - для редактирования настроек скрипта.

2) Node.js - Switch его поддерживает, и это сильно облегчает нам жизнь, потому что Node.js содержит огромное количество уже написанных другими людьми модулей. Например, фактически все полезные действия нашего будущего скрипта будут выполняться модулем "xlsx", но об этом дальше.

3) редактор кода. Можно обойтись даже блокнотом, но это неудобно, поэтому я использую Visual Studio code + для удобства форматирования кода - плагин Prettier.


I. Создание скрипта

Запускаем командную строку cmd.exe, далее с помощью режима create SwitchScriptTool создаем файл будущего скрипта (путь указывайте свой):

SwitchScriptTool --create XLSXtoCSV D:\!!!enfocus_switch_scripts --JavaScript

Если SwitchScriptTool не добавлен в PATH, надо указать полный путь к программе:

"C:\Program Files\Enfocus\Enfocus Switch\SwitchScriptTool\SwitchScriptTool" --create XLSXtoCSV D:\!!!enfocus_switch_scripts --JavaScript

Если не указана опция --JavaScript, создается файл main.ts, но я не достаточно знаком с Typescript, чтобы о нем рассказывать. Хотя, кажется, Typescript облегчает разработку скриптов для Switch, потому что при создании скрипта в редактор кода добавляется поддержка внутренних функций Switch, таким образом часть ошибок кода вы увидите еще в редакторе, до запуска отладки. Рекомендую изучить.

Итак, в указанной нами папке (в моем случае D:\!!!enfocus_switch_scripts) появилась папка с именем скрипта XLSXtoCSV. Внутри нее много файлов, но нас интересуют только 2:

XLSXtoCSV.sscript - файл с настройками скрипта для Switch

main.js - здесь в будущем будет код скрипта.

II. Настройки файла скрипта

Запускаем Switch Scripter и открываем в нем XLSXtoCSV.sscript. Здесь нам надо прописать общие настройки скрипта: сколько будет входящих и исходящих подключений, добавить описание и иконку.

Основные настройки скрипта

После этого, на уровне Flow element properties добавляем изменяемые пользователем опции:

Настройка разделителя. Строковое значение. По умолчанию ","
Пропускать пустые строки? Выбор "Да-нет", по умолчанию "Да".
Возможность выбрать номер листа, который будет конвертирован в csv. По умолчанию "0" - это значит, что будут конвертированы все листы. Если выбрать конкретное число - будет конвертирован только выбранный лист
Эта настройка позволяет выбрать, как будет называться конвертированный файл. Она появляется только если в предыдущей настройке был выбран номер листа отличный от 0. Есть 2 варианта - по имени файла или по имени листа. Если же в предыдущей настройке выбрано 0 (все листы) - конвертированные файлы будут называться по имени листа.

III. Создание прототипа скрипта

Теперь, когда мы определились с пользовательскими настройками, можно приступить к созданию самого скрипта.

В данном случае скрипт будет практически полностью состоять из кода на node.js, а задача Switch будет лишь в том, чтобы взять пользовательские настройки, вместе со входящим файлом передать их скрипту, а после успешной конвертации в csv отправить файл дальше во flow. А это значит, что мы можем воспользоваться недавно появившимся помощником для начинающих программистов: ChatGPT.

Если вы уверенный программист, пишите код самостоятельно ипомощь ИИ вам не нужна, тогда переходите к следующему этапу.

Инструкций по регистрации в ChatGPT для россиян в интернете довольно много. Понадобится арендовать зарубежный номер для получения СМС (примерно 20 рублей) и воспользоваться ВПН (рекомендую бесплатный и опенсорсный psiphon).

После регистрации пишем запрос для ИИ, в котором подробно рассказываем, что нам нужно: скрипт для node.js, который получит файл, проверит его расширение, и если это xls или xlsx конвертирует его в csv. Также у нас будут переменные, которые будут управлять разделителем, пропуском пустых строк, выбором листа и названием полученного файла. Переменные описываем подробно, говорим как они будут называться (см. предыдущий пункт), какие типы данных могут иметь: separator - строковое значение, blankrows - может иметь значение Yes или No и так далее.

Запросы для ChatGPT я пишу по-английски, но он, кажется, и русский неплохо понимает.

Итак, после некоторого общения, уточняющих запросов и ручного исправления ряда мелких ошибок искусственного интеллекта (да, ChatGPT не панацея, он тоже ошибается, полной автоматизации пока нет) был получен такой код:

const XLSX = require('xlsx');
const fs = require('fs-extra');
const path = require('path');

const separator = ',' // CSV field separator (default: ',')
const blankrows = 'No' // Skip empty rows (default: No)
const blankrowsBool = blankrows.toLowerCase() === "no"; // translates Yes/No to boolean true/false
// Specify the path to the Excel file
const excelFilePath = 'in/file.xls';

// Check if the file extension is supported
const supportedExtensions = ['.xls', '.xlsx'];
const fileExtension = path.extname(excelFilePath);
if (!supportedExtensions.includes(fileExtension)) {
  console.error('Unsupported file format. Only .xls and .xlsx files are supported.');
  process.exit(1);
}

// Load the workbook
const workbook = XLSX.readFile(excelFilePath);

// Specify options
const csvOptions = {
  FS: separator,
  blankrows: blankrowsBool,
};

// Specify worksheets selection
const worksheetsSelection = 0; // 0 to create CSV files for all sheets, otherwise specify the sheet number (1 - worksheet[0])

// Check if the specified sheet number is valid
if (worksheetsSelection !== 0 && (worksheetsSelection < 1 || worksheetsSelection > workbook.SheetNames.length)) {
  console.error('Invalid sheet number. Please enter a valid sheet number.');
  process.exit(1);
}

// Specify the CSV filename behavior
const useSheetName = 'Yes'; // Set to true to use the original filename, false to use the sheet name
const useSheetNameBool = useSheetName.toLowerCase() === "no"; // translates Yes/No to boolean true/false

// Iterate over each sheet in the workbook
for (const sheetName of workbook.SheetNames) {
  const index = workbook.SheetNames.indexOf(sheetName) + 1;
 
  // Check if the current sheet should be processed based on worksheetsSelection
  if (worksheetsSelection === 0 || worksheetsSelection === index) {
    const worksheet = workbook.Sheets[sheetName];

    // Convert the worksheet to CSV
    const csvData = XLSX.utils.sheet_to_csv(worksheet, csvOptions);

    // Specify the path to save the CSV file
    let csvFilePath;
    if (worksheetsSelection === 0) {
      csvFilePath = `out/${sheetName}.csv`;
    } else {
      const baseFilename = useSheetNameBool ? path.basename(excelFilePath, fileExtension) : sheetName;
      csvFilePath = `out/${baseFilename}.csv`;
    }

    // Write the CSV data to a file
    fs.writeFileSync(csvFilePath, csvData, 'utf8');
  }
}

console.log('Excel to CSV conversion complete!');


Давайте его опробуем: переходим в Visual Studio Code, создаем новый файл ctrl+N, вставляем в него скопированный код и сразу же сохраняем в ту же папку XLSXtoCSV, где лежит пока пустой скрипт main.js. Но под другим именем, например, назовем его test.js.

Если обратите внимание, то код начинается со строк вроде

const XLSX = require("xlsx");

Это как раз и есть импорт уже готовых модулей node.js. Но, чтобы скрипту было что импортировать, модули надо установить. Прямо в VSCode открываем консоль ctrl+` (но можно воспрользоваться и стандартным терминалом windows cmd.exe).

Переходим в консоли в папку, где лежит скрипт

PS C:\Users\user> D:
PS D:\> cd .\!!!enfocus_switch_scripts\
PS D:\!!!enfocus_switch_scripts> cd .\XLSXtoCSV\

И устанавливаем необходимые модули node.js:

PS D:\!!!enfocus_switch_scripts\XLSXtoCSV> npm install xlsx fs-extra path --save

Правда сразу же видим в консоли сообщение:

1 high severity vulnerability
Run `npm audit` for details.

Запускаем команду npm audit, узнаем что есть проблема с модулем xlsх. Идем в гугл и там выясняем, что по какой-то причине разработчики перестали обновлять свой репозиторий на node.js, его надо удалить и поставить с сайта разработчиков. Бывает и такое... Делаем:

PS D:\!!!enfocus_switch_scripts\XLSXtoCSV> npm remove xlsx
PS D:\!!!enfocus_switch_scripts\XLSXtoCSV> npm i --save https://cdn.sheetjs.com/xlsx-0.20.0/xlsx-0.20.0.tgz

Почти всё готово. В скрипте прописаны переменные excelFilePath и csvFilePath, в которых указан путь к входному файлу и куда его надо сохранить после конвертации. Значит в папке со скриптом должна быть создана папка in (D:\!!!enfocus_switch_scripts\XLSXtoCSV\in), внутри которой должен лежать файл file.xlsx. После обработки скрипт конвертирует этот файл в csv и положит в папку out (D:\!!!enfocus_switch_scripts\XLSXtoCSV\out). Создаем отсутствующие папки, кладем в in файл file.xlsx.

В VS Code нажимаем F5 - запуск режима отладки. Появится небольшое меню, в котором выбираем node.js в качестве отладчика. В консоли отладки видим сообщение:

Excel to CSV conversion complete!

Проверяем папку out - там лежат конвертированные файлы с расширением csv. Ура, скрипт сработал.

IV. Адаптация скрипта для Switch

Хотя у нас уже есть работающий скрипт, его еще надо связать со Switch.

Для начала создадим новое тестовое флоу в Switch Designer. Добавим в него 2 папки (входящую и исходящую) и script element, в настройках которого укажем путь к XLSXtoCSV.sscript.

При разработке с нуля в настройках скрипта можно включить Debug mode (Enable Debug mode - Yes), остальные настройки оставить по умолчанию.
Debug mode - это режим отладки. Когда он включен, Switch и редактор кода (VSCode), связываются друг с другом напрямую через указанный в настройках порт (менять его не надо). И при запуске отладки пользователь может видеть в какой момент и почему происходит сбой кода, потому что видны не только ошибки, но и значения переменных в момент выполнения. В данном туториале мы ничего отлаживать не будем, но при реальной разработке данный режим вам пригодится.

Теперь код надо подготовить к работе в Switch:

Открываем основной файл скрипта main.js, созданный в п. I, и видим, что внутри уже есть строка

async function jobArrived(s, flowElement, job) { }

Это - точка входа для скрипта в Switch. На самом деле таких точек несколько, но jobArrived безусловно наиболее часто используемая. Она работает с хотфолдерами и фактически означает "как только файл прибыл по флоу ко скрипту, выполни код внутри фигурных скобок".

Скопируем весь код из test.js внутрь фигурных скобок.


Теперь его надо немного подправить. Так, все начальные элементы импорта модулей node.js надо вынести из фигурных скобок в начало скрипта:

const XLSX = require("xlsx");
const fs = require("fs-extra");
const path = require("path");
async function jobArrived(s, flowElement, job) {

Далее, имеющиеся внутри скрипта характерные для node.js куски кода, говорящие о том, что процесс надо завершить

 process.exit(1);

заменим на

return

Нам не надо завершать процесс сервера node.js, встроенного в Switch, да он и не поймет эту команду и выдаст ошибку.


Кроме того, мы хотим пользоваться указанными пользователем переменными. Для этого переменные

const separator = ',' // CSV field separator (default: ',')
const blankrows = false // Skip empty rows (default: false)
const worksheetsSelection = 0; // 0 to create CSV files for all sheets, otherwise specify the sheet number (1 - worksheet[0])
const useOriginalFilename = true; // Set to true to use the original filename, false to use the sheet name

заменим на подгружаемые из настроек скрипта выбранные пользователем значения:

const separator = await flowElement.getPropertyStringValue("separator");
const blankrows = await flowElement.getPropertyStringValue("blankrows");
const worksheetsSelection = Number(await flowElement.getPropertyStringValue("worksheetsSelection") );
const useSheetName = await flowElement.getPropertyStringValue( "useSheetName");

Обратите внимание, что для worksheetsSelection значение завернуто в Number. Хотя в настройках скрипта мы выставили тип данных для данного поля как "число", скрипт все равно получает данные из Switch как строковое значение, которое надо принудительно превратить в число заново.

Также обратите внимание на await.


Переменную, в которой указан путь к входящему файлу

const excelFilePath = 'in/file.xls';

заменим на входящий файл из Switch

  const excelFilePath = await job.get(AccessLevel.ReadWrite);

В скрипте есть несколько сообщений, которые скрипт выдает в консоль, вроде:

console.error('Unsupported file format. Only .xls and .xlsx files are supported.');

Нам надо, чтобы сообщения выводились в окно Messages самого Switch, поэтому заменим их на подобную конструкцию:

job.log(LogLevel.Error, "Unsupported file format. Only .xls and .xlsx files are supported.");

Надо добавить для Switch логику того, что делать с файлом после его обработки.

Так, если файл не *.xls или *.xlsx, вместо остановки скрипта, отправим файлы дальше по флоу:

await job.sendToSingle();
return;

А если файл *.xls или *.xlsx, надо указать путь, по которому будут сохранены конвертированные файлы.

Но на самом деле логика в данном случае будет немного сложнее, менее прямолинейная. Дело в том, что Switch оперирует понятием job, а не file. И может быть такая ситуация, когда на входе подается 1 файл *.xls, являющийся тем самым job'ом, а внутри него несколько листов, которые должны превратиться, например, в 3 файла csv. Чтобы все было корректно, при конвертации в csv каждый из из этих файлов должен быть создан во временной папке, затем как новый job добавлен в switch и отправлен дальше по flow, а оригинальный файл в конце просто удален.

Плюс, так как у нас асинхронный код, надо всё завернуть в конструкцию

try{

}catch(e){

}


В итоге финальный код скрипта выглядит так:

const XLSX = require("xlsx");
const fs = require("fs-extra");
const path = require("path");

async function jobArrived(s, flowElement, job) {
  const separator = await flowElement.getPropertyStringValue("separator");
  const blankrows = await flowElement.getPropertyStringValue("blankrows");
  const blankrowsBool = blankrows.toLowerCase() === "no";
  const worksheetsSelection = Number(
    await flowElement.getPropertyStringValue("worksheetsSelection")
  );

  try {
    // Specify the path to the Excel file
    const excelFilePath = await job.get(AccessLevel.ReadWrite);

    // Check if the file extension is supported
    const supportedExtensions = [".xls", ".xlsx"];
    const fileExtension = path.extname(excelFilePath);
    if (!supportedExtensions.includes(fileExtension)) {
      job.log(
        LogLevel.Error,
        "Unsupported file format. Only .xls and .xlsx files are supported."
      );
      await job.sendToSingle();
      return;
    }

    // Load the workbook
    const workbook = await XLSX.readFile(excelFilePath);

    // Specify options
    const csvOptions = {
      FS: separator, // CSV field separator
      blankrows: blankrowsBool, // Skip empty rows
    };

    // Check if the specified sheet number is valid
    if (
      worksheetsSelection !== 0 &&
      (worksheetsSelection < 1 ||
        worksheetsSelection > workbook.SheetNames.length)
    ) {
      await job.fail(
        "Invalid sheet number. Please enter a valid sheet number."
      );
      return;
    }

    const csvPath = path.dirname(excelFilePath);

    // Iterate over each sheet in the workbook
    for (const sheetName of workbook.SheetNames) {
      const index = workbook.SheetNames.indexOf(sheetName) + 1;

      // Check if the current sheet should be processed based on worksheetsSelection
      if (worksheetsSelection == 0 || worksheetsSelection === index) {
        const worksheet = workbook.Sheets[sheetName];

        // Convert the worksheet to CSV
        const csvData = XLSX.utils.sheet_to_csv(worksheet, csvOptions);

        // Specify the path to save the CSV file
        let csvFilePath;
        if (worksheetsSelection === 0) {
          csvFilePath = `${csvPath}\\${sheetName}.csv`;
        } else {
          const useSheetName = await flowElement.getPropertyStringValue(
            "useSheetName"
          );
          const useSheetNameBool = useSheetName.toLowerCase() === "no";
          const baseFilename = useSheetNameBool
            ? path.basename(excelFilePath, fileExtension)
            : sheetName;
          csvFilePath = `${csvPath}\\${baseFilename}.csv`;
        }

        // Write the CSV data to a file
        await fs.writeFileSync(csvFilePath, csvData, "utf8");

        let newJob = await flowElement.createJob(csvFilePath);
        await newJob.sendToSingle();
      }
    }

    job.log(LogLevel.Info, "Excel to CSV conversion complete!");
    await job.sendToNull();
  } catch (error) {
    await job.fail("An error occurred: %1", [error]);
  }
}

Cохраним его в main.js.

Теперь можно активировать флоу в Switch и убедиться, что всё работает.

V. Запаковка скрипта в единый файл

Всё готово и скриптом можно пользоваться в том виде как он есть - в виде так называемого script folder, когда мы имеем папку скрипта с кучей файлов и пустым XLSXtoCSV.sscript. Но чтобы script folder превратить в один файл можно воспользоваться командой Pack SwitchScriptTool:

SwitchScriptTool --pack d:\!!!enfocus_switch_scripts\XLSXtoCSV\ d:\!!!enfocus_switch_scripts\

А если вам интересно взглянуть на код чужих скриптов, их можно распаковать командой unpack и получить такой же script folder, но только если автор не защитил sscript паролем.

Кстати, у нас на меге все скрипты для Switch открыты для изучения.

Report Page