При выгрузке данных большого объема (от 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 МБ, происходят сразу несколько ресурсоемких операций:
readAll() объекта Scripting.FileSystemObject.ADODB.Stream и посимвольная конвертация кодировок.На таких объемах скрипт не успевает завершить работу за отведенные 10 секунд, и операционная система выдает диалоговое окно с вопросом о принудительном завершении сценария. Кроме того, чтение и запись через Scripting.FileSystemObject для формата UTF-8 не подходят архитектурно и могут портить специальные символы.
Если в конфигурации уже используется готовая процедура с 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().
Рассмотрим более производительный и надежный подход. Чтобы не формировать сначала файл в Windows-1251, а затем конвертировать его скриптами, мы можем писать данные сразу в кодировке UTF-8 непосредственно из 1С 7.7. Для этого не нужны сторонние внешние компоненты или MSScriptControl — достаточно стандартного системного объекта ADODB.Stream.
Разберем по шагам создание файла «на лету»:
ADODB.Stream через функцию СоздатьОбъект().Type = 2 (текстовый поток) и Mode = 3 (чтение и запись).Charset = "utf-8".Open().WriteText(ТекстСтроки, 1), где второй параметр 1 означает добавление стандартного символа переноса строки (аналог метода WriteLine).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: " + ИмяФайла);
КонецПроцедуры
Такой способ работает значительно быстрее, так как данные формируются в потоке за один проход и не расходуют память на хранение дубликатов гигантских строковых переменных.
Бывают ситуации, когда файл формируется сторонней типовой процедурой конфигурации, в которую нельзя внести изменения для прямой потоковой записи. В таком случае перекодировать готовый файл также можно силами двух потоков 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.