При интеграции устаревших систем на платформе 1С:Предприятие 7.7 с современными веб-сервисами, CRM или другими базами данных часто возникает задача выгрузки информации в текстовые форматы, чаще всего в CSV (Comma-Separated Values). На первый взгляд задача кажется тривиальной: достаточно обойти выборку данных, склеить строки и записать их в файл. Однако разработчики часто сталкиваются с неприятной проблемой — в полученном файле после каждой заполненной строки появляется пустая строка. Это нарушает структуру файла и приводит к ошибкам при его чтении сторонними программами.
В данной статье мы подробно разберем, почему возникает этот эффект, как работает встроенный объект 1С 7.7 Текст, почему крайне важно очищать строки от концевых пробелов и какие альтернативные (и более современные) инструменты можно использовать для полного контроля над кодировкой и символами перевода строк.
Рассмотрим типичный код, который часто пишут начинающие разработчики (именно такой пример приводился в начале обсуждения):
ФайлCSV = СоздатьОбъект("Текст");
Выборка.ВыбратьСтроки();
Пока Выборка.ПолучитьСтроку() = 1 Цикл
// Формируем строку и принудительно добавляем разделитель в конец
СтрокаФайла = Выборка.Код + ";" + Выборка.Наименование + РазделительСтрок;
ФайлCSV.ДобавитьСтроку(СтрокаФайла);
КонецЦикла;
ФайлCSV.Записать("C:\export.csv");
При просмотре полученного файла в обычном Блокноте мы увидим следующую картину:
00001;Товар 1
00002;Товар 2
00003;Товар 3
Кажется, что система добавила лишний пустой абзац. Почему так происходит? Дело в архитектуре работы встроенного объекта Текст в 1С 7.7.
Метод ДобавитьСтроку() не записывает данные в файл мгновенно. Он работает с оперативной памятью, добавляя переданное значение как новый элемент в виртуальный список (массив) строк. На этом этапе строка хранится внутри объекта "как есть".
Когда мы вызываем метод Записать(), платформа берет этот список строк и последовательно выгружает их на диск. При этом 1С 7.7 самостоятельно разделяет элементы списка стандартным для операционной системы Windows символом перевода строки — комбинацией CRLF (в шестнадцатеричном виде это байты 0x0D 0x0A, что соответствует функциям Симв(13) + Симв(10)).
Если в коде мы вручную прибавили к строке константу РазделительСтрок (которая в 1С 7.7 как раз и возвращает Симв(13) + Симв(10)), то при записи файла платформа добавит еще один системный перевод строки. В итоге в файле образуется последовательность CRLF CRLF. Любая программа-обработчик или текстовый редактор интерпретирует эту двойную последовательность как переход на новую строку и создание следующей пустой строки.
Решение проблемы: При использовании объекта Текст никогда не добавляйте в конец строки константу РазделительСтрок или символы Симв(13)+Симв(10) вручную. Метод ДобавитьСтроку() сделает все переводы автоматически при записи файла.
Правильный вариант кода выглядит так:
ФайлCSV = СоздатьОбъект("Текст");
Выборка.ВыбратьСтроки();
Пока Выборка.ПолучитьСтроку() = 1 Цикл
// Собираем строку БЕЗ РазделительСтрок в конце
СтрокаФайла = Выборка.Код + ";" + Выборка.Наименование;
ФайлCSV.ДобавитьСтроку(СтрокаФайла);
КонецЦикла;
ФайлCSV.Записать("C:\export.csv");
Если вы откроете полученный таким образом файл в HEX-редакторе (редакторе шестнадцатеричных кодов), вы увидите, что в конце каждой строки гарантированно присутствуют байты 0D 0A, хотя в коде мы их явно не указывали. Платформа всё сделала за нас.
---
В сообщении №12 обсуждения прозвучал важный совет: использовать функцию СокрЛП() при выгрузке текстовых переменных. Давайте разберем подробнее, почему в среде 1С 7.7 пренебрежение этим правилом может испортить структуру CSV-файла.
В базах данных 1С 7.7 (особенно DBF-версиях) текстовые реквизиты справочников и документов имеют жестко заданную фиксированную длину. Например, длина наименования номенклатуры в конфигурации может составлять 100 символов. Если реальное название товара занимает всего 15 символов (например, "Кабель силовой"), то оставшиеся 85 символов физически заполняются пробелами в базе данных.
Если мы попытаемся выгрузить этот реквизит напрямую без обработки:
СтрокаФайла = Выборка.Код + ";" + Выборка.Наименование + ";" + Выборка.Цена;
То на выходе мы получим строку огромной длины, где между названием товара и его ценой будет стоять длинный ряд пробелов:
00015;Кабель силовой ;1500.00
Это приводит к следующим негативным последствиям:
Поэтому всегда очищайте текстовые поля с помощью функции СокрЛП(), которая удаляет все незначащие пробелы слева и справа от полезного текста:
СтрокаФайла = СокрЛП(Выборка.Код) + ";" + СокрЛП(Выборка.Наименование) + ";" + СокрЛП(Выборка.Цена);
---
Простое объединение строк через точку с запятой (;) таит в себе еще одну опасность. Если в самом наименовании товара встретится символ-разделитель (например, точка с запятой в названии: "Комплект подключения; тип А") или символ кавычки, структура файла CSV безвозвратно нарушится при чтении.
По правилам формата CSV (RFC 4180):
ООО "Рога и Копыта"), то эти внутренние кавычки должны удваиваться, а само поле также должно быть обернуто в кавычки.Рассмотрим, как написать вспомогательную функцию для подготовки текста перед записью в CSV:
Функция ПодготовитьДляCSV(Знач ТекстПоля)
Перем Рез;
Рез = СокрЛП(Строка(ТекстПоля));
// Если в тексте есть кавычки, удваиваем их
Если Найти(Рез, """") > 0 Тогда
Рез = СтрЗаменить(Рез, """", """""");
КонецЕсли;
// Если есть разделитель или кавычки, оборачиваем все поле в кавычки
Если (Найти(Рез, ";") > 0) Или (Найти(Рез, """") > 0) Или (Найти(Рез, Симв(10)) > 0) Тогда
Рез = """" + Рез + """";
КонецЕсли;
Возврат Рез;
КонецФункции
Теперь процесс формирования строки выгрузки станет абсолютно безопасным и надежным:
СтрокаФайла = ПодготовитьДляCSV(Выборка.Код) + ";" + ПодготовитьДляCSV(Выборка.Наименование) + ";" + ПодготовитьДляCSV(Выборка.Цена);
ФайлCSV.ДобавитьСтроку(СтрокаФайла);
---
Хотя стандартный объект 1С 7.7 Текст прост в использовании, он имеет два серьезных ограничения:
Записать() всегда автоматически ставит перевод строки (CRLF) в самый конец файла после последней строки. Для некоторых строгих систем интеграции наличие пустого перевода в конце файла недопустимо.Для решения этих проблем мы можем использовать системные COM-объекты операционной системы Windows.
Этот способ предоставляет низкоуровневый доступ к записи файлов. Он позволяет вручную управлять каждым байтом перевода строк и не добавлять ничего лишнего в конец файла. Мы используем метод Write(), который записывает текст строго "как есть" (без автоматического добавления перевода строки в отличие от метода WriteLine()).
Рассмотрим пример реализации:
// Создаем объект для работы с файловой системой Windows
ФС_Объект = СоздатьОбъект("Scripting.FileSystemObject");
// Открываем файл для записи (Путь, Режим: 2 - запись, ФлагСоздания: Истина)
// Последний параметр: 0 - ASCII (ANSI), -1 - Unicode (UTF-16), -2 - Системная кодировка по умолчанию
НовыйФайл = ФС_Объект.CreateTextFile("C:\temp\export_fso.csv", 1, 0);
Выборка.ВыбратьСтроки();
ПерваяСтрока = 1;
Пока Выборка.ПолучитьСтроку() = 1 Цикл
// Формируем строку данных
ТекущаяСтрока = ПодготовитьДляCSV(Выборка.Код) + ";" + ПодготовитьДляCSV(Выборка.Наименование);
// Перевод строки добавляем только ПЕРЕД следующей строкой,
// чтобы избежать лишнего символа перевода строки в самом конце файла
Если ПерваяСтрока = 1 Тогда
ПерваяСтрока = 0;
Иначе
НовыйФайл.Write(РазделительСтрок);
КонецЕсли;
НовыйФайл.Write(ТекущаяСтрока);
КонецЦикла;
НовыйФайл.Close();
Если веб-сайт или внешняя база данных требует от нас выгрузку исключительно в кодировке UTF-8, то использование ActiveX-объекта ADODB.Stream — это лучшее и самое быстрое решение, не требующее сторонних внешних компонент.
Проанализируем пример кода для экспорта в UTF-8:
Поток = СоздатьОбъект("ADODB.Stream");
Поток.Type = 2; // Указываем, что работаем с текстовыми данными (text stream)
Поток.Charset = "utf-8"; // Задаем целевую кодировку выгрузки
Поток.Open();
Выборка.ВыбратьСтроки();
Пока Выборка.ПолучитьСтроку() = 1 Цикл
ТекущаяСтрока = ПодготовитьДляCSV(Выборка.Код) + ";" + ПодготовитьДляCSV(Выборка.Наименование);
// Метод WriteText имеет второй параметр:
// 0 - записать без перевода строки
// 1 - записать строку и автоматически добавить системный разделитель (CRLF) в конец
Поток.WriteText(ТекущаяСтрока, 1);
КонецЦикла;
// Сохраняем поток в файл на диске
// Параметр 2 означает "Перезаписать файл, если он уже существует"
Поток.SaveToFile("C:\temp\export_utf8.csv", 2);
Поток.Close();
Использование ADODB.Stream гарантирует, что файл будет сохранен в полноценном стандарте UTF-8 (по умолчанию с BOM-сигнатурой, что упрощает распознавание кодировки текстовыми редакторами типа Excel).
---
Подведем итоги нашего исследования по выгрузке текстовых файлов из 1С:Предприятия 7.7:
Текст помните, что метод ДобавитьСтроку() разделяет элементы списка автоматически при вызове метода Записать(). Никогда не добавляйте РазделительСтрок вручную.СокрЛП() для предотвращения раздувания размера файлов и порчи структуры таблиц.Текст в пользу внешних COM-объектов Windows: Scripting.FileSystemObject или ADODB.Stream.