ТекстовыйДокумент или ЧтениеТекста: чтение файлов, кодировки и импорт CSV в 1С
Содержание
Прочитать текстовый файл в 1С 8.3 можно двумя штатными объектами встроенного языка. ТекстовыйДокумент загружает файл целиком и даёт доступ к любой строке по номеру. ЧтениеТекста читает файл потоком, строку за строкой, и почти не тратит память. Для файлов до нескольких мегабайт хватит первого, для логов и выгрузок на сотни тысяч строк берите второй.
Остальные проблемы при чтении текстового документа — это кодировка, файл на компьютере пользователя, который не видит сервер, и кривой CSV. Всё это ниже, с рабочим кодом. Примеры подходят для любой конфигурации: это возможности платформы, а не БП или УТ.
Чтение файла целиком через ТекстовыйДокумент
ТекстовыйДокумент (в английском синтаксисе TextDocument) — это тип встроенного языка. Его же использует макет типа «Текстовый документ» в конфигураторе, поэтому иногда его называют объектом метаданных. Для чтения файла с диска макет не нужен: объект создаётся в коде.
Базовый вариант:
Текст = Новый ТекстовыйДокумент;
Текст.Прочитать("C:\Обмен\price.txt", КодировкаТекста.UTF8);
Для НомерСтроки = 1 По Текст.КоличествоСтрок() Цикл
Строка = Текст.ПолучитьСтроку(НомерСтроки);
// обработка строки
КонецЦикла;
Что здесь важно:
- 📌 Нумерация строк начинается с 1, а не с 0.
- 📌
КоличествоСтрок()считает и пустые строки, включая последнюю, если файл заканчивается переводом строки. Пустые строки пропускайте явно. - 📌 Весь текст одной строкой отдаёт
ПолучитьТекст(). Удобно, когда файл нужно передать дальше целиком, например вЧтениеJSON.
Синхронный и асинхронный вызов
Метод Прочитать() синхронный. На сервере это нормально. В клиентском коде веб-клиента и в конфигурациях с запретом модальности и синхронных вызовов платформа его не пропустит. Там используйте НачатьЧтение() с описанием оповещения или, в актуальных версиях платформы 8.3, асинхронный ПрочитатьАсинх() с Ждать.
&НаКлиенте
Асинх Процедура ЗагрузитьТекст(Команда)
Текст = Новый ТекстовыйДокумент;
Ждать Текст.ПрочитатьАсинх(ПутьКФайлу, КодировкаТекста.UTF8);
Сообщить("Строк в файле: " + Текст.КоличествоСтрок());
КонецПроцедуры
Если нужно показать файл пользователю, ТекстовыйДокумент открывается в окне просмотра методом Показать(). Для отладки выгрузок это быстрее, чем открывать файл в блокноте.
Построчное чтение большого файла классом ЧтениеТекста
ЧтениеТекста (TextReader) держит в памяти только текущую строку. Файл открывается в конструкторе, а не отдельным методом:
Чтение = Новый ЧтениеТекста("C:\Обмен\journal.log", КодировкаТекста.UTF8);
Строка = Чтение.ПрочитатьСтроку();
Пока Строка <> Неопределено Цикл
// обработка строки
Строка = Чтение.ПрочитатьСтроку();
КонецЦикла;
Чтение.Закрыть();
Конец файла определяется так: ПрочитатьСтроку() возвращает Неопределено. Пустая строка файла придёт как "", поэтому сравнивайте именно с Неопределено, а не проверяйте ПустаяСтрока(). Иначе цикл остановится на первой пустой строке в середине файла.
Другие методы, которые пригодятся:
| Метод | Что делает |
|---|---|
ПрочитатьСтроку() | Следующая строка без символа перевода строки |
Прочитать(Количество) | Указанное число символов; без параметра — весь остаток файла |
Закрыть() | Освобождает файл |
Открыть(...) | Переоткрывает объект на другой файл |
Всегда вызывайте Закрыть(), в том числе при ошибке. Пока объект не закрыт, файл может оставаться занятым процессом 1С. Внешняя программа, которая выгружает данные по расписанию, не сможет его перезаписать.
Чтение из потока
Начиная с платформы 8.3.9 конструктор ЧтениеТекста принимает не только путь, но и поток. Это удобно, когда файл пришёл двоичными данными из HTTP-запроса или временного хранилища:
Поток = ДвоичныеДанныеФайла.ОткрытьПотокДляЧтения();
Чтение = Новый ЧтениеТекста(Поток, КодировкаТекста.UTF8);
Временный файл на диск записывать не нужно.
Кодировка и разделители строк при чтении текстового файла
Кракозябры вместо кириллицы почти всегда означают неверную кодировку. Платформа умеет определить UTF-8 и UTF-16 по метке BOM в начале файла. Файл без BOM в Windows-1251 она угадывает не всегда, поэтому кодировку надо указывать явно.
| Источник файла | Что указать |
|---|---|
| Выгрузка из Excel «CSV (разделители — запятые)» | КодировкаТекста.ANSI или "windows-1251" |
| Excel «CSV UTF-8», большинство веб-сервисов | КодировкаТекста.UTF8 |
| Старые DOS-программы, банковские файлы старого формата | КодировкаТекста.OEM (cp866) |
| Файлы, сохранённые как «Юникод» в блокноте | КодировкаТекста.UTF16 |
Кодировку можно передать и строкой: "windows-1251", "utf-8", "cp866". Строковый вариант надёжнее ANSI: значение ANSI зависит от настроек операционной системы, на которой выполняется код. На сервере под Linux оно может оказаться не 1251.
Если в первой строке UTF-8 файла видите лишний символ перед текстом, значит, файл с BOM прочитан без указания UTF-8. Первое поле CSV в этом случае не совпадёт с ожидаемым заголовком, и поиск колонки по имени сломается.
Разделители строк
Файлы из Windows разделены парой символов ВК+ПС, из Linux и macOS — одним ПС. Оба объекта по умолчанию корректно разбирают оба варианта. Если источник использует нестандартный разделитель, например ; в конце каждой записи, передайте его третьим параметром:
Чтение = Новый ЧтениеТекста(ИмяФайла, "utf-8", ";");
Файл на компьютере пользователя: клиент, сервер и временное хранилище
Самая частая ошибка новичка — вызвать чтение на сервере с путём C:\Users\... с компьютера пользователя. В клиент-серверной базе сервер 1С работает на другой машине и такого файла не видит. В итоге ошибка «файл не найден», хотя файл точно лежит на месте.
Правильная схема:
- На клиенте пользователь выбирает файл в диалоге.
- Файл передаётся на сервер через временное хранилище.
- На сервере двоичные данные читаются через поток или временный файл.
&НаКлиенте
Асинх Процедура ВыбратьИЗагрузить(Команда)
Параметры = Новый ПараметрыДиалогаПомещенияФайлов;
Параметры.Фильтр = "CSV (*.csv)|*.csv|Текст (*.txt)|*.txt";
Описание = Ждать ПоместитьФайлНаСерверАсинх(, , , Параметры, УникальныйИдентификатор);
Если Описание = Неопределено Или Описание.ПомещениеФайлаОтменено Тогда
Возврат;
КонецЕсли;
ЗагрузитьНаСервере(Описание.Адрес);
КонецПроцедуры
&НаСервере
Процедура ЗагрузитьНаСервере(Адрес)
Данные = ПолучитьИзВременногоХранилища(Адрес);
Чтение = Новый ЧтениеТекста(Данные.ОткрытьПотокДляЧтения(), "windows-1251");
// разбор строк
Чтение.Закрыть();
УдалитьИзВременногоХранилища(Адрес);
КонецПроцедуры
ПоместитьФайлНаСерверАсинх есть в актуальных версиях платформы 8.3. На более старых используйте НачатьПомещениеФайла с описанием оповещения — смысл тот же.
Если файл лежит в общей сетевой папке, сервер может прочитать его напрямую по UNC-пути \\server\obmen\file.txt. Учтите, что читать будет учётная запись службы сервера 1С, а не пользователь. У неё должны быть права на эту папку.
Ошибки чтения и проверка, что файл существует
Перед чтением проверьте файл. Это даёт пользователю понятное сообщение вместо системного исключения:
Файл = Новый Файл(ИмяФайла);
Если Не Файл.Существует() Тогда
ВызватьИсключение "Не найден файл загрузки: " + ИмяФайла;
КонецЕсли;
На клиенте синхронный Существует() тоже может быть запрещён. Используйте СуществуетАсинх() с Ждать.
Даже если проверка прошла, файл может быть занят другой программой или недоступен по правам. Поэтому само чтение оборачивайте в Попытка, а Закрыть() выносите так, чтобы он вызывался в любом случае:
Чтение = Неопределено;
Попытка
Чтение = Новый ЧтениеТекста(ИмяФайла, "utf-8");
Строка = Чтение.ПрочитатьСтроку();
Пока Строка <> Неопределено Цикл
ОбработатьСтроку(Строка);
Строка = Чтение.ПрочитатьСтроку();
КонецЦикла;
Чтение.Закрыть();
Исключение
Если Чтение <> Неопределено Тогда
Чтение.Закрыть();
КонецЕсли;
ЗаписьЖурналаРегистрации("Загрузка из файла", УровеньЖурналаРегистрации.Ошибка, , ,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
ПодробноеПредставлениеОшибки даёт стек вызовов для журнала регистрации. Пользователю достаточно КраткоеПредставлениеОшибки.
Что быстрее: сравнение способов чтения текста
Оба объекта читают файл быстро. Разница появляется на больших объёмах и в том, что вы делаете со строками дальше.
| Критерий | ТекстовыйДокумент | ЧтениеТекста |
|---|---|---|
| Память | Весь файл в памяти, с запасом на служебные структуры | Только текущая строка |
| Доступ к строке | Любая строка по номеру | Только последовательно |
| Подходит для | Файлов до нескольких мегабайт, справочников, настроек | Логов, выгрузок, файлов в сотни мегабайт |
| Нужно ли закрывать | Нет | Да, Закрыть() |
| Чтение из потока | Нет | Да, с 8.3.9 |
Практические выводы:
- 🚀 Файл в 200 МБ через
ТекстовыйДокументна 32-битном клиенте может упасть с нехваткой памяти.ЧтениеТекстаобработает его без проблем. - 🚀 Узкое место почти никогда не само чтение, а запись в базу. Тысяча вызовов
Записать()по одному элементу займёт заметно больше, чем чтение файла. - 🚀 Если нужен весь текст строкой,
ЧтениеТекста.Прочитать()без параметра делает то же, чтоТекстовыйДокумент.ПолучитьТекст(), без промежуточного объекта. - 🚀 Для JSON и XML не разбирайте текст вручную: есть
ЧтениеJSONиЧтениеXML. Для двоичных форматов с полями фиксированной длины —ЧтениеДанных.
Пример: загрузка справочника из CSV-файла
Задача: загрузить номенклатуру из файла, который бухгалтер сохранил из Excel. Формат: первая строка — заголовки, разделитель ;, кодировка Windows-1251.
Артикул;Наименование;Цена
А-001;Бумага А4 80 г/м2;450,00
А-002;"Скрепки 28 мм; 100 шт";95,50
Во второй строке разделитель стоит внутри кавычек. Простой СтрРазделить(Строка, ";") разобьёт её на четыре части вместо трёх. Поэтому нужна функция разбора с учётом кавычек:
Функция РазобратьСтрокуCSV(Строка, Разделитель = ";")
Поля = Новый Массив;
Текущее = "";
ВКавычках = Ложь;
Номер = 1;
Длина = СтрДлина(Строка);
Пока Номер <= Длина Цикл
Символ = Сред(Строка, Номер, 1);
Если Символ = """" Тогда
Если ВКавычках И Сред(Строка, Номер + 1, 1) = """" Тогда
Текущее = Текущее + """";
Номер = Номер + 1;
Иначе
ВКавычках = Не ВКавычках;
КонецЕсли;
ИначеЕсли Символ = Разделитель И Не ВКавычках Тогда
Поля.Добавить(СокрЛП(Текущее));
Текущее = "";
Иначе
Текущее = Текущее + Символ;
КонецЕсли;
Номер = Номер + 1;
КонецЦикла;
Поля.Добавить(СокрЛП(Текущее));
Возврат Поля;
КонецФункции
Цены в файле с запятой и иногда с неразрывным пробелом между разрядами («1 250,00»). Приводим к числу отдельно:
Функция СтрокаВЧисло(Знач Текст)
Текст = СтрЗаменить(Текст, Символы.НПП, "");
Текст = СтрЗаменить(Текст, " ", "");
Текст = СтрЗаменить(Текст, ",", ".");
Попытка
Возврат Число(Текст);
Исключение
Возврат 0;
КонецПопытки;
КонецФункции
Основная процедура загрузки на сервере. Справочник и реквизиты приведены для примера: в вашей конфигурации артикул может храниться в другом реквизите, а цена — в регистре сведений, а не в справочнике.
&НаСервере
Процедура ЗагрузитьНоменклатуру(ДвоичныеДанные)
Чтение = Новый ЧтениеТекста(ДвоичныеДанные.ОткрытьПотокДляЧтения(), "windows-1251");
Заголовок = Чтение.ПрочитатьСтроку(); // пропускаем заголовки
Загружено = 0;
НомерСтроки = 1;
Строка = Чтение.ПрочитатьСтроку();
Пока Строка <> Неопределено Цикл
НомерСтроки = НомерСтроки + 1;
Если Не ПустаяСтрока(Строка) Тогда
Поля = РазобратьСтрокуCSV(Строка);
Если Поля.Количество() < 3 Тогда
Сообщить("Строка " + НомерСтроки + ": мало полей, пропущена");
Иначе
Ссылка = Справочники.Номенклатура.НайтиПоРеквизиту("Артикул", Поля[0]);
Если Ссылка.Пустая() Тогда
Объект = Справочники.Номенклатура.СоздатьЭлемент();
Объект.Артикул = Поля[0];
Иначе
Объект = Ссылка.ПолучитьОбъект();
КонецЕсли;
Объект.Наименование = Поля[1];
Объект.Записать();
Загружено = Загружено + 1;
КонецЕсли;
КонецЕсли;
Строка = Чтение.ПрочитатьСтроку();
КонецЦикла;
Чтение.Закрыть();
Сообщить("Загружено элементов: " + Загружено);
КонецПроцедуры
Что стоит добавить в боевой вариант:
- Сверять заголовки в первой строке с ожидаемыми. Если бухгалтер переставил колонки, загрузка должна остановиться, а не записать цены в наименования.
- Не обрывать загрузку на одной плохой строке. Запись элемента оберните в
Попыткаи копите ошибки с номерами строк, чтобы показать их списком в конце. - Для загрузки нескольких тысяч элементов группировать запись в транзакции порциями, например по 500 строк. Одна огромная транзакция надолго держит блокировки, а запись без транзакций идёт медленнее.
Первый прогон делайте на копии базы. Ошибка в сопоставлении колонок перезапишет наименования у существующей номенклатуры, и откатить это без резервной копии сложно.
Частые вопросы
Как прочитать текстовый макет конфигурации, а не файл с диска?
ПолучитьМакет("ИмяМакета") в модуле объекта или менеджера. Если макет типа «Текстовый документ», вернётся готовый ТекстовыйДокумент, и дальше работают КоличествоСтрок() и ПолучитьСтроку().Можно ли прочитать файл, который в этот момент дописывает другая программа?
ЧтениеТекста и метода Прочитать() отвечает за монопольный доступ; передайте Ложь. Строки, дописанные после открытия, могут не попасть в чтение.Как узнать кодировку файла, если её никто не сообщил?
Как прочитать файл из интернета без сохранения на диск?
ПолучитьТелоКакДвоичныеДанные(), затем откройте поток и передайте его в ЧтениеТекста. Если нужен просто текст, хватит ПолучитьТелоКакСтроку() с указанием кодировки.Чем ЧтениеТекста отличается от ЧтениеДанных?
ЧтениеТекста работает со строками и кодировками. ЧтениеДанных читает байты и числа заданной разрядности, им разбирают двоичные форматы. Для CSV и TXT нужен первый.