Как перекодировать файл из ANSI в UTF-8 с помощью ADODB.Stream и исправить ошибку «Операция не допускается, если объект закрыт»

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

При работе с внешними файлами в среде 1С (особенно в версии 7.7) или при написании скриптов автоматизации на JScript/VBScript часто возникает необходимость изменить кодировку текстового документа. Одной из самых популярных технологий для этого остается ADODB.Stream. Однако при ее использовании разработчики часто сталкиваются с неочевидными ошибками состояния объекта или некорректным отображением символов («кракозябрами») после сохранения. В этой статье мы подробно разберем, как правильно инициализировать потоки, почему возникает ошибка закрытого объекта и как гарантированно преобразовать ANSI в UTF-8.

Разбор ошибки «Операция не допускается, если объект закрыт»

Рассмотрим типичную ситуацию: разработчик создает объект ActiveXObject('ADODB.Stream'), настраивает его свойства (кодировку, тип данных) и сразу пытается вызвать метод LoadFromFile. В этот момент выполнение прерывается с ошибкой: «Операция не допускается, если объект закрыт». Проанализируем причину.

Дело в том, что объект ADODB.Stream после создания находится в состоянии «Closed». Хотя мы уже определили его параметры (например, Charset или Type), сам канал передачи данных еще не инициализирован в оперативной памяти. Прежде чем выполнять любые операции чтения или записи (LoadFromFile, WriteText, ReadText), необходимо явно вызвать метод Open().

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


Stream = new ActiveXObject('ADODB.Stream');
Stream.Type = 2; // adTypeText
Stream.Charset = 'windows-1251'; // Указываем исходную кодировку
Stream.Open(); // ОБЯЗАТЕЛЬНЫЙ ВЫЗОВ
Stream.LoadFromFile('C:\\file.txt');

Почему простая смена Charset не меняет кодировку файла?

Часто программисты допускают ошибку, полагая, что достаточно загрузить файл, изменить свойство Charset у того же объекта и сохранить его. Однако ADODB.Stream работает иначе: свойство Charset определяет, как объект интерпретирует байты в момент чтения или записи. Если вы уже загрузили данные в поток как ANSI, смена свойства Charset на utf-8 перед сохранением просто заставит объект «думать», что данные уже находятся в UTF-8, что приведет к записи испорченных данных.

Для правильной перекодировки мы должны использовать два раздельных потока: один для чтения исходных данных, другой — для записи в целевой кодировке. Разберем этот алгоритм подробнее.

Решение №1: Использование двух потоков и метода ReadText/WriteText

Этот способ является классическим для 1С 7.7 и скриптовых языков. Мы создаем «входящий» поток с исходной кодировкой (ANSI/Windows-1251) и «исходящий» поток с нужной нам кодировкой (UTF-8). Рассмотрим программную реализацию на языке 1С:


// Пути к файлам
ПутьКФайлуИн = "C:\temp\ansi.txt";
ПутьКФайлуАут = "C:\temp\utf8.txt";

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

// Создаем и настраиваем исходящий поток
outStream = СоздатьОбъект("ADODB.Stream");
outStream.Type = 2;
outStream.Mode = 3;
outStream.Charset = "utf-8";
outStream.Open();

// Переносим данные: читаем текст из одного и пишем в другой
// В этот момент ADODB.Stream автоматически выполняет конвертацию
outStream.WriteText(inStream.ReadText());

// Сохраняем результат
outStream.SaveToFile(ПутьКФайлуАут, 2); // 2 = adSaveCreateOverWrite

// Закрываем ресурсы
outStream.Close();
inStream.Close();

В данном примере метод ReadText() извлекает данные из первого потока, преобразуя байты в строку (Unicode внутри COM-объекта), а метод WriteText() принимает эту строку и записывает ее во второй поток, используя кодировку utf-8.

Решение №2: Оптимизированное копирование через CopyTo

Рассмотрим более изящный способ, который упоминался в обсуждении. Метод CopyTo позволяет передать все содержимое одного потока в другой. Это может работать быстрее при обработке очень больших файлов, так как данные передаются более эффективно на уровне внутренних буферов.

Проанализируем пример функции на JScript:


function ConvertToUTF8(fileFrom, fileTo) {
    var StreamIn = new ActiveXObject('ADODB.Stream');
    var StreamOut = new ActiveXObject('ADODB.Stream');

    StreamIn.Type = 2;
    StreamIn.Charset = 'windows-1251';
    StreamIn.Open();
    StreamIn.LoadFromFile(fileFrom);

    StreamOut.Type = 2;
    StreamOut.Charset = 'utf-8';
    StreamOut.Open();

    // Копируем данные из входного потока в выходной
    StreamIn.CopyTo(StreamOut);

    StreamOut.SaveToFile(fileTo, 2);

    StreamIn.Close();
    StreamOut.Close();
}

Продвинутые нюансы: BOM и разделители строк

При использовании ADODB.Stream для формирования UTF-8 файлов важно помнить о нескольких технических деталях, которые могут повлиять на дальнейшую работу с файлом:

  1. Byte Order Mark (BOM): По умолчанию ADODB.Stream при сохранении в UTF-8 добавляет в начало файла сигнатуру EF BB BF. Если принимающая сторона (например, веб-сервер на Linux или специфический парсер) не ожидает BOM, это может вызвать ошибку «Invalid character». Чтобы сохранить UTF-8 без BOM, обычно требуется выполнить «трюк» с переключением потока в бинарный режим (Type = 1) после записи текста, установкой Position = 3 и копированием оставшихся байтов в новый поток.
  2. LineSeparator: Свойство LineSeparator (по умолчанию -1, что соответствует CRLF) определяет, какие символы будут считаться концом строки. Если ваш исходный файл пришел из системы Unix (где используется только LF), убедитесь, что чтение проходит корректно.
  3. Очистка ресурсов: Всегда вызывайте Close(). В 1С 7.7 объекты COM могут удерживать блокировку файла в файловой системе до тех пор, пока переменная не будет уничтожена или пока не завершится процесс. После вызова Close() рекомендуется также обнулять переменную: inStream = 0; (в 1С 7.7) или inStream = null; (в JS).

Альтернатива для современных версий 1С (8.3+)

Хотя вышеописанные методы идеально подходят для «динозавриков» (как выразились на форуме), таких как 1С 7.7, при работе на платформе 1С 8.3 использование ADODB считается излишним. Платформа предоставляет встроенные кроссплатформенные средства.

Рассмотрим, как это решается в современных конфигурациях:


// Чтение файла в кодировке ANSI
Чтение = Новый ЧтениеТекста(ПутьКФайлу, КодировкаТекста.ANSI);
Текст = Чтение.Прочитать();
Чтение.Закрыть();

// Запись файла в UTF-8 (второй параметр Ложь отключает BOM)
Запись = Новый ЗаписьТекста(ПутьКФайлуUTF, КодировкаТекста.UTF8, , Ложь);
Запись.Записать(Текст);
Запись.Закрыть();

Этот код работает значительно быстрее, не требует наличия установленных компонентов ADO в системе и корректно обрабатывает кодировки на уровне ядра платформы 1С.

Резюме: Если вы столкнулись с ошибкой закрытого объекта — добавьте Open(). Если файл не перекодируется — используйте два объекта Stream: один для чтения оригинальных байтов, второй для записи в новой кодировке.

← На главную