При разработке интеграционных механизмов и выполнении практических задач часто возникает требование выгрузить документ произвольного вида в текстовый файл формата TXT и корректно загрузить его обратно, не прибегая к встроенным механизмам сериализации XML или JSON. Основная сложность заключается в сохранении информации о типах данных, структуре табличных частей и правильном восстановлении ссылочных реквизитов (справочников, документов, перечислений). Рассмотрим подробно, как спроектировать собственный текстовый формат и написать универсальные процедуры выгрузки и загрузки на платформе 1С:Предприятие 8.3.
Для того чтобы парсер мог однозначно разобрать структуру документа любой конфигурации, текстовый файл должен быть четко структурирован. Разделим файл на логические блоки:
РеализацияТоваровУслуг).Каждую строку со значением реквизита представим в виде структурированной записи с разделителем (например, символ вертикальной черты |):
ИмяРеквизита|ТипДанных|Значение
Посмотрим на пример структуры готового текстового файла:
[Документ:РеализацияТоваровУслуг]
[Шапка]
Номер|Строка|0000-000001
Дата|Дата|20260215143000
Организация|Справочник.Организации|f7a1b2c3-d4e5-6789-0123-abcdef456789
Контрагент|Справочник.Контрагенты|a1b2c3d4-e5f6-7890-abcd-ef1234567890
Проведен|Булево|1
[ТабличнаяЧасть:Товары]
[НоваяСтрока]
Номенклатура|Справочник.Номенклатура|b2c3d4e5-f6a7-8901-bcde-f12345678901
Количество|Число|10
Цена|Число|150.50
Сумма|Число|1505.00
[КонецДокумента]
Разберем по шагам, как выполнять взаимное преобразование типов между платформой 1С и строковым представлением:
Числа выгружаются без разделителей групп (Формат(Значение, "ЧГ=0; ЧДР=.")), восстанавливаются вызовом Число(ЗначениеСтрокой). Булево значение представляется в виде 1 или 0. Для строковых значений с многострочным текстом рекомендуется заменять переносы строк на служебную последовательность \n, чтобы не нарушать структуру файла.
Для однозначного чтения даты выгружаем ее в каноническом формате yyyyMMddHHmmss с помощью функции Формат(Значение, "ДФ=yyyyMMddHHmmss"). При загрузке значение легко преобразуется обратно через конструктор Дата(ЗначениеСтрокой).
Для ссылок сохраняем два параметра: полное имя метаданных (например, Справочник.Контрагенты) и уникальный идентификатор (GUID) в виде строки: Строка(Значение.УникальныйИдентификатор()). При загрузке получаем менеджер соответствующего объекта и находим ссылку по Новый УникальныйИдентификатор(СтрокаGUID).
У перечислений нет GUID. Выгружаем имя перечисления и строковое имя конкретного значения: Значение.Метаданные().Имя и Строка(Значение). При загрузке получаем значение через коллекцию Перечисления[ИмяПеречисления][ИмяЗначения].
Рассмотрим серверный код выгрузки документа в текстовый документ. Обратите внимание: для полной выгрузки необходимо обходить не только пользовательские реквизиты, но и стандартные реквизиты шапки (Номер, Дата, Проведен):
&НаСервере
Процедура ВыгрузитьДокументВТекст(ДокументСсылка, ПутьКФайлу)
ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
МетаданныеДок = ДокументСсылка.Метаданные();
ТекстовыйФайл = Новый ТекстовыйДокумент;
ТекстовыйФайл.ДобавитьСтроку("[Документ:" + МетаданныеДок.Имя + "]");
// --- Выгрузка шапки документа ---
ТекстовыйФайл.ДобавитьСтроку("[Шапка]");
// Выгрузка стандартных реквизитов шапки
МассивСтандартных = Новый Массив;
МассивСтандартных.Добавить("Номер");
МассивСтандартных.Добавить("Дата");
МассивСтандартных.Добавить("ПометкаУдаления");
МассивСтандартных.Добавить("Проведен");
Для Каждого ИмяРеквизита Из МассивСтандартных Цикл
Попытка
Значение = ДокументОбъект[ИмяРеквизита];
СтрокаРеквизита = СформироватьСтрокуРеквизита(ИмяРеквизита, Значение);
Если Не ПустаяСтрока(СтрокаРеквизита) Тогда
ТекстовыйФайл.ДобавитьСтроку(СтрокаРеквизита);
КонецЕсли;
Исключение
// Реквизит может отсутствовать у некоторых типов документов
КонецПопытки;
КонецЦикла;
// Выгрузка дополнительных реквизитов шапки
Для Каждого Реквизит Из МетаданныеДок.Реквизиты Цикл
Значение = ДокументОбъект[Реквизит.Имя];
СтрокаРеквизита = СформироватьСтрокуРеквизита(Реквизит.Имя, Значение);
Если Не ПустаяСтрока(СтрокаРеквизита) Тогда
ТекстовыйФайл.ДобавитьСтроку(СтрокаРеквизита);
КонецЕсли;
КонецЦикла;
// --- Выгрузка табличных частей ---
Для Каждого ТЧ Из МетаданныеДок.ТабличныеЧасти Цикл
ТекстовыйФайл.ДобавитьСтроку("[ТабличнаяЧасть:" + ТЧ.Имя + "]");
ТаблицаДанных = ДокументОбъект[ТЧ.Имя];
Для Каждого СтрокаТЧ Из ТаблицаДанных Цикл
ТекстовыйФайл.ДобавитьСтроку("[НоваяСтрока]");
Для Каждого РеквизитТЧ Из ТЧ.Реквизиты Цикл
Значение = СтрокаТЧ[РеквизитТЧ.Имя];
СтрокаРеквизита = СформироватьСтрокуРеквизита(РеквизитТЧ.Имя, Значение);
Если Не ПустаяСтрока(СтрокаРеквизита) Тогда
ТекстовыйФайл.ДобавитьСтроку(СтрокаРеквизита);
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецЦикла;
ТекстовыйФайл.ДобавитьСтроку("[КонецДокумента]");
ТекстовыйФайл.Записать(ПутьКФайлу, КодировкаТекста.UTF8);
КонецПроцедуры
&НаСервере
Функция СформироватьСтрокуРеквизита(ИмяРеквизита, Значение)
Тип = ТипЗнч(Значение);
Если Значение = Неопределено Тогда
Возврат ИмяРеквизита + "|Неопределено|";
ИначеЕсли Тип = Тип("Строка") Тогда
ЗначениеТекст = СтрЗаменить(Значение, Символы.ПС, "\n");
ЗначениеТекст = СтрЗаменить(ЗначениеТекст, Символы.ВК, "");
Возврат ИмяРеквизита + "|Строка|" + ЗначениеТекст;
ИначеЕсли Тип = Тип("Число") Тогда
Возврат ИмяРеквизита + "|Число|" + Формат(Значение, "ЧГ=0; ЧДР=.");
ИначеЕсли Тип = Тип("Булево") Тогда
Возврат ИмяРеквизита + "|Булево|" + ?(Значение, "1", "0");
ИначеЕсли Тип = Тип("Дата") Тогда
Возврат ИмяРеквизита + "|Дата|" + Формат(Значение, "ДФ=yyyyMMddHHmmss");
Иначе
// Проверяем ссылочные типы и перечисления
МетаданныеОбъекта = Метаданные.НайтиПоТипу(Тип);
Если МетаданныеОбъекта <> Неопределено Тогда
ПолноеИмя = МетаданныеОбъекта.ПолноеИмя();
Если Перечисления.ТипВсеСсылки().СодержитТип(Тип) Тогда
Возврат ИмяРеквизита + "|" + ПолноеИмя + "|" + XMLСтрока(Значение);
Иначе
// Справочники, Документы и др.
Если Значение.Пустая() Тогда
Возврат ИмяРеквизита + "|" + ПолноеИмя + "|ПустаяСсылка";
Иначе
Возврат ИмяРеквизита + "|" + ПолноеИмя + "|" + Строка(Значение.УникальныйИдентификатор());
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЕсли;
Возврат "";
КонецФункции
Проанализируем алгоритм загрузки. Мы читаем файл построчно, отслеживаем текущую секцию (шапка или конкретная табличная часть) и выполняем восстановление типов:
&НаСервере
Функция ЗагрузитьДокументИзТекста(ПутьКФайлу)
ТекстовыйФайл = Новый ТекстовыйДокумент;
ТекстовыйФайл.Прочитать(ПутьКФайлу, КодировкаТекста.UTF8);
НовыйДокумент = Неопределено;
ТекущаяСекция = "";
ТекущаяТЧ = "";
ТекущаяСтрокаТЧ = Неопределено;
ВсегоСтрок = ТекстовыйФайл.КоличествоСтрок();
Для НомерСтроки = 1 По ВсегоСтрок Цикл
СтрокаФайла = СокрЛП(ТекстовыйФайл.ПолучитьСтроку(НомерСтроки));
Если ПустаяСтрока(СтрокаФайла) Тогда
Продолжить;
КонецЕсли;
// Определение начала документа
Если Лев(СтрокаФайла, 10) = "[Документ:" Тогда
ИмяДокумента = Сред(СтрокаФайла, 11, СтрДлина(СтрокаФайла) - 11);
НовыйДокумент = Документы[ИмяДокумента].СоздатьДокумент();
Продолжить;
КонецЕсли;
Если СтрокаФайла = "[Шапка]" Тогда
ТекущаяСекция = "Шапка";
Продолжить;
ИначеЕсли Лев(СтрокаФайла, 16) = "[ТабличнаяЧасть:" Тогда
ТекущаяСекция = "ТЧ";
ТекущаяТЧ = Сред(СтрокаФайла, 17, СтрДлина(СтрокаФайла) - 17);
Продолжить;
ИначеЕсли СтрокаФайла = "[НоваяСтрока]" Тогда
Если НовыйДокумент <> Неопределено И Не ПустаяСтрока(ТекущаяТЧ) Тогда
ТекущаяСтрокаТЧ = НовыйДокумент[ТекущаяТЧ].Добавить();
КонецЕсли;
Продолжить;
ИначеЕсли СтрокаФайла = "[КонецДокумента]" Тогда
Прервать;
КонецЕсли;
// Разбор данных реквизита
ЧастиСтроки = СтрРазделить(СтрокаФайла, "|", Ложь);
Если ЧастиСтроки.Количество() < 3 Тогда
Продолжить;
КонецЕсли;
ИмяРеквизита = ЧастиСтроки[0];
ТипРеквизита = ЧастиСтроки[1];
ЗначениеСтрока = ЧастиСтроки[2];
Значение = ВосстановитьЗначениеИзСтроки(ТипРеквизита, ЗначениеСтрока);
Если ТекущаяСекция = "Шапка" Тогда
Попытка
НовыйДокумент[ИмяРеквизита] = Значение;
Исключение
// Пропуск недоступных для записи реквизитов
КонецПопытки;
ИначеЕсли ТекущаяСекция = "ТЧ" И ТекущаяСтрокаТЧ <> Неопределено Тогда
Попытка
ТекущаяСтрокаТЧ[ИмяРеквизита] = Значение;
Исключение
КонецПопытки;
КонецЕсли;
КонецЦикла;
Если НовыйДокумент <> Неопределено Тогда
НовыйДокумент.Записать(РежимЗаписиДокумента.Запись);
Возврат НовыйДокумент.Ссылка;
КонецЕсли;
Возврат Неопределено;
КонецФункции
&НаСервере
Функция ВосстановитьЗначениеИзСтроки(ТипРеквизита, ЗначениеСтрока)
Если ТипРеквизита = "Неопределено" Тогда
Возврат Неопределено;
ИначеЕсли ТипРеквизита = "Строка" Тогда
Возврат СтрЗаменить(ЗначениеСтрока, "\n", Символы.ПС);
ИначеЕсли ТипРеквизита = "Число" Тогда
Возврат Число(ЗначениеСтрока);
ИначеЕсли ТипРеквизита = "Булево" Тогда
Возврат (ЗначениеСтрока = "1" Или ВРег(ЗначениеСтрока) = "ИСТИНА");
ИначеЕсли ТипРеквизита = "Дата" Тогда
Если СтрДлина(ЗначениеСтрока) = 14 Тогда
Возврат Дата(Лев(ЗначениеСтрока, 4), Сред(ЗначениеСтрока, 5, 2), Сред(ЗначениеСтрока, 7, 2),
Сред(ЗначениеСтрока, 9, 2), Сред(ЗначениеСтрока, 11, 2), Сред(ЗначениеСтрока, 13, 2));
КонецЕсли;
Возврат '00010101';
Иначе
// Разбор ссылочных типов
ЧастиТипа = СтрРазделить(ТипРеквизита, ".");
Если ЧастиТипа.Количество() = 2 Тогда
ВидМетаданных = ЧастиТипа[0]; // Например, "Справочник", "Перечисление", "Документ"
ИмяМетаданных = ЧастиТипа[1];
Если ВидМетаданных = "Перечисление" Тогда
Попытка
Возврат Перечисления[ИмяМетаданных][ЗначениеСтрока];
Исключение
Возврат Неопределено;
КонецПопытки;
ИначеЕсли ЗначениеСтрока = "ПустаяСсылка" Тогда
Возврат Неопределено;
Иначе
// Получение ссылки по Уникальному Идентификатору
ГУИД = Новый УникальныйИдентификатор(ЗначениеСтрока);
Если ВидМетаданных = "Справочник" Тогда
Возврат Справочники[ИмяМетаданных].ПолучитьСсылку(ГУИД);
ИначеЕсли ВидМетаданных = "Документ" Тогда
Возврат Документы[ИмяМетаданных].ПолучитьСсылку(ГУИД);
ИначеЕсли ВидМетаданных = "ПланВидовХарактеристик" Тогда
Возврат ПланыВидовХарактеристик[ИмяМетаданных].ПолучитьСсылку(ГУИД);
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецЕсли;
Возврат Неопределено;
КонецФункции
При решении задач подобного класса обратим внимание на следующие особенности работы платформы:
ПолучитьСсылку(Новый УникальныйИдентификатор(...)) создает объект ссылки даже в том случае, если физически элемента в базе данных нет («битая ссылка»). Если обмен предполагается между разными базами данных, вместо восстановления по GUID необходимо организовать поиск объекта по ключевым полям (код, наименование, ИНН) через методы НайтиПоРеквизиту() или НайтиПоНаименованию().КодировкаТекста.UTF8) при записи и чтении текстовых файлов, чтобы избежать искажения кириллических символов на разных операционных системах.РежимЗаписиДокумента.Запись, так как при проведении могут не выполниться проверки проведения (например, контроль остатков), если связанные регистры или справочники еще не актуализированы.