Как в 1С 7.7 избежать ошибки длительного выполнения скрипта при перекодировке больших файлов в UTF-8?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

При выгрузке данных большого объема (от 10–20 МБ и выше) разработчики в платформе 1С 7.7 нередко сталкиваются с ситуацией, когда стандартных средств платформы оказывается недостаточно. Встроенный объект Текст поддерживает только кодировки Windows (ANSI / Windows-1251) и DOS (OEM / 866), а популярный объект Scripting.FileSystemObject вовсе не умеет работать с форматом UTF-8. В связи с этим для смены кодировки часто используют скриптовые обертки через MSScriptControl.ScriptControl и ADODB.Stream. Однако при обработке тяжелых файлов выполнение прерывается системным предупреждением о том, что скрипт выполняется слишком долго.

Разберем природу этой ошибки, выясним причину ее появления и рассмотрим практические способы решения — от простой корректировки свойств скрипт-контрола до правильной прямой записи в UTF-8 без посредников.

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

Проанализируем ситуацию: по умолчанию у объекта MSScriptControl.ScriptControl установлено внутреннее ограничение по времени выполнения команд — таймаут составляет около 10 000 миллисекунд (10 секунд). Когда в VBScript передается процедура обработки текстового файла размером 15–20 МБ, происходят сразу несколько ресурсоемких операций:

  1. Чтение всего файла целиком в память с помощью метода readAll() объекта Scripting.FileSystemObject.
  2. Создание COM-объекта ADODB.Stream и посимвольная конвертация кодировок.
  3. Запись результата обратно на диск.

На таких объемах скрипт не успевает завершить работу за отведенные 10 секунд, и операционная система выдает диалоговое окно с вопросом о принудительном завершении сценария. Кроме того, чтение и запись через Scripting.FileSystemObject для формата UTF-8 не подходят архитектурно и могут портить специальные символы.

Решение 1. Отключение таймаута в MSScriptControl

Если в конфигурации уже используется готовая процедура с MSScriptControl.ScriptControl и требуется быстро устранить появление назойливого окна, разберем по шагам настройку времени ожидания.

У объекта управления сценариями существует свойство TimeOut. Если передать в него значение -1, контроль времени выполнения полностью отключается, и скрипт будет работать до тех пор, пока полностью не завершит обработку данных.

Посмотрим на пример правильной инициализации скрипта:


Процедура ИзменитьКодовуюСтраницуФайла(ИмяФайла, ИсходнКодировка, НужнаяКодировка)
    Если ФС.СуществуетФайл(ИмяФайла) = 0 Тогда
        Сообщить("Файл " + ИмяФайла + " не найден!", "!");
        Возврат;
    КонецЕсли;

    ScrptCtrl = СоздатьОбъект("MSScriptControl.ScriptControl");
    ScrptCtrl.Language = "VBScript";
    
    // Отключаем ограничение по времени выполнения скрипта
    ScrptCtrl.TimeOut = -1;
    
    ScrptCtrl.AddCode("
        |Function StrConv(Text, SourceCharset, DestCharset)
        |    Set Stream = CreateObject(""ADODB.Stream"")
        |    Stream.Type = 2
        |    Stream.Mode = 3
        |    Stream.Open
        |    Stream.Charset = DestCharset
        |    Stream.WriteText Text
        |    Stream.Position = 0
        |    Stream.Charset = SourceCharset
        |    StrConv = Stream.ReadText
        |End Function
        |
        |Sub ConvertCodepage(fileName, SourceCharset, DestCharset)
        |    Set fs = CreateObject(""Scripting.FileSystemObject"")
        |    originalText = fs.OpenTextFile(fileName, 1).ReadAll()
        |    convertedText = StrConv(originalText, SourceCharset, DestCharset)
        |    fs.OpenTextFile(fileName, 2, True).Write(convertedText)
        |End Sub
        |");

    Module = ScrptCtrl.Modules("Global");
    CodeObject = Module.CodeObject;
    CodeObject.ConvertCodepage(ИмяФайла, ИсходнКодировка, НужнаяКодировка);
КонецПроцедуры

После добавления строки ScrptCtrl.TimeOut = -1 скрипт перестанет прерываться ошибкой системы. Однако этот способ все еще нагружает оперативную память из-за полного считывания файла в переменную через ReadAll().

Решение 2. Запись напрямую в UTF-8 через ADODB.Stream без промежуточных скриптов

Рассмотрим более производительный и надежный подход. Чтобы не формировать сначала файл в Windows-1251, а затем конвертировать его скриптами, мы можем писать данные сразу в кодировке UTF-8 непосредственно из 1С 7.7. Для этого не нужны сторонние внешние компоненты или MSScriptControl — достаточно стандартного системного объекта ADODB.Stream.

Разберем по шагам создание файла «на лету»:

  1. Инициализируем объект ADODB.Stream через функцию СоздатьОбъект().
  2. Устанавливаем режим потока: Type = 2 (текстовый поток) и Mode = 3 (чтение и запись).
  3. Задаем свойство Charset = "utf-8".
  4. Открываем поток методом Open().
  5. Записываем строки по мере выборки данных с помощью метода WriteText(ТекстСтроки, 1), где второй параметр 1 означает добавление стандартного символа переноса строки (аналог метода WriteLine).
  6. Сохраняем готовый файл методом SaveToFile(ИмяФайла, 2) (параметр 2 разрешает перезапись файла, если он уже существует) и освобождаем ресурсы методом Close().

Посмотрим на практический пример кода:


Процедура ВыгрузитьДанныеВUTF8(ИмяФайла)
    Перем Поток, Заголовок, СтрокаДанных;
    
    Поток = СоздатьОбъект("ADODB.Stream");
    Поток.Type = 2; // Текстовые данные
    Поток.Mode = 3; // Чтение и запись
    Поток.Charset = "utf-8";
    Поток.Open();
    
    // Записываем строку заголовка
    Заголовок = "1.Код товара;2.артикул;3.название товара;4.склад;5.дата;6.кол-во продажи";
    Поток.WriteText(Заголовок, 1);
    
    // Пример записи строк в цикле выборки
    // ВыборкаДанных.ВыбратьСтроки();
    // Пока ВыборкаДанных.ПолучитьСтроку() = 1 Цикл
    //     СтрокаДанных = ... формируем строку ...;
    //     Поток.WriteText(СтрокаДанных, 1);
    // КонецЦикла;

    // Сохраняем поток в файл (2 - перезапись существующего файла)
    Поток.SaveToFile(ИмяФайла, 2);
    Поток.Close();
    
    Сообщить("Файл успешно сформирован в UTF-8: " + ИмяФайла);
КонецПроцедуры

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

Решение 3. Быстрая перекодировка существующего файла через два потока ADODB.Stream

Бывают ситуации, когда файл формируется сторонней типовой процедурой конфигурации, в которую нельзя внести изменения для прямой потоковой записи. В таком случае перекодировать готовый файл также можно силами двух потоков ADODB.Stream прямо из встроенного языка 1С 7.7, минуя VBScript:


Процедура ПерекодироватьФайлЧерезПотоки(ИмяФайла, ИсходнаяКодировка, ЦелеваяКодировка)
    Перем ВходнойПоток, ВыходнойПоток, Содержимое;

    Если ФС.СуществуетФайл(ИмяФайла) = 0 Тогда
        Сообщить("Файл не найден: " + ИмяФайла, "!");
        Возврат;
    КонецЕсли;

    // Читаем исходный файл
    ВходнойПоток = СоздатьОбъект("ADODB.Stream");
    ВходнойПоток.Type = 2;
    ВходнойПоток.Mode = 3;
    ВходнойПоток.Charset = ИсходнаяКодировка; // например, "windows-1251"
    ВходнойПоток.Open();
    ВходнойПоток.LoadFromFile(ИмяФайла);
    Содержимое = ВходнойПоток.ReadText();
    ВходнойПоток.Close();

    // Пишем в целевой кодировке
    ВыходнойПоток = СоздатьОбъект("ADODB.Stream");
    ВыходнойПоток.Type = 2;
    ВыходнойПоток.Mode = 3;
    ВыходнойПоток.Charset = ЦелеваяКодировка; // например, "utf-8"
    ВыходнойПоток.Open();
    ВыходнойПоток.WriteText(Содержимое);
    ВыходнойПоток.SaveToFile(ИмяФайла, 2);
    ВыходнойПоток.Close();
КонецПроцедуры

Альтернатива: посимвольная программная конвертация

В практике программирования 1С 7.7 иногда встречается алгоритм посимвольного преобразования строки в UTF-8 на уровне кода встроенного языка с помощью сопоставления байтов через функции КодСимв() и Симв():


Функция СтрКонвертироватьВUTF8(Ст) Экспорт
    Перем НоваяСт, i, Символ, КодСимвола;
    НоваяСт = "";
    Для i = 1 По СтрДлина(Ст) Цикл
        Символ = Сред(Ст, i, 1);
        КодСимвола = КодСимв(Символ);
        Если (КодСимвола > 191) И (КодСимвола < 224) Тогда // А - Я
            НоваяСт = НоваяСт + Симв(208) + Симв(КодСимвола - 48);
        ИначеЕсли (КодСимвола > 223) И (КодСимвола < 240) Тогда // а - п
            НоваяСт = НоваяСт + Симв(208) + Симв(КодСимвола - 48);
        ИначеЕсли (КодСимвола > 239) И (КодСимвола < 256) Тогда // р - я
            НоваяСт = НоваяСт + Симв(209) + Симв(КодСимвола - 112);
        ИначеЕсли Символ = "ё" Тогда
            НоваяСт = НоваяСт + Симв(209) + Симв(145);
        ИначеЕсли Символ = "Ё" Тогда
            НоваяСт = НоваяСт + Симв(208) + Симв(129);
        ИначеЕсли Символ = "№" Тогда
            НоваяСт = НоваяСт + Симв(226) + Симв(132) + Симв(150);
        Иначе
            НоваяСт = НоваяСт + Символ;
        КонецЕсли;
    КонецЦикла;
    Возврат НоваяСт;
КонецФункции

Важное предупреждение: такой метод допустим исключительно для коротких строковых значений (например, наименование одной номенклатуры или строковый параметр в HTTP-запросе). На файлах объемом более 1 МБ встроенный интерпретатор 1С 7.7 будет выполнять конкатенацию строк крайне медленно, и операция может растянуться на часы.

Для регулярных выгрузок средних и больших объемов всегда отдавайте предпочтение прямой потоковой записи через ADODB.Stream либо отключайте таймаут скрипта через TimeOut = -1.

← На главную