Чтение файлов формата CSV в среде 1С:Предприятие часто превращается в головную боль для разработчика. Стандартная и простая задача импорта данных рушится, когда в файле появляются многострочные текстовые поля (например, описания товаров с HTML-разметкой или переводами строк внутри ячейки), экранированные кавычки и нестандартные разделители. В то время как Microsoft Excel открывает такие файлы без единой ошибки, встроенные методы 1С, такие как ТекстовыйДокумент или последовательное чтение через ЧтениеТекста, «ломают» строки, превращая один логический объект в тысячи разрозненных фрагментов.
В этой статье мы подробно разберем, почему возникает эта проблема, проанализируем ограничения платформы 1С и реализуем надежные механизмы парсинга сложных CSV-файлов силами встроенного языка, а также альтернативными методами.
Прежде чем писать код, выясним причину «всеяности» Excel и «избирательности» 1С. Существует международный стандарт RFC 4180, который описывает правила формирования файлов формата CSV. Согласно этому стандарту:
"Поле; с разделителем и <br> переводом строки")."".Когда мы используем стандартный метод ТекстовыйДокумент.Прочитать() или ЧтениеТекста.ПрочитатьСтроку(), платформа 1С выполняет **физическое** чтение файла до первого встреченного символа перевода строки. Платформа «не знает», находится ли этот символ внутри кавычек (то есть является частью текста ячейки) или разделяет строки базы данных. В результате строка обрывается посередине описания товара, и весь последующий импорт сдвигается по колонкам.
Для решения этой проблемы нам необходимо разработать парсер, который будет имитировать поведение Excel: анализировать состояние кавычек и принимать решение — является ли текущий перевод строки концом записи или частью данных ячейки.
---
Рассмотрим по шагам логику работы конечного автомата (State Machine). Наш парсер будет посимвольно или поблочно разбирать текст. Главная задача — отслеживать флаг нахождения «внутри текстового блока». Если мы находимся внутри открытых кавычек, любые символы перевода строк и разделителей мы считаем обычным текстом и накапливаем в буфер. Как только кавычка закрывается, мы возвращаемся в режим обычного разбора структуры.
Ниже представлен детальный и оптимизированный алгоритм разбора, который обрабатывает дублирующиеся кавычки, переносы строк внутри полей и кастомный разделитель (в нашем примере — точка с запятой):
Функция РазобратьСтрокуCSV(Знач ФизическаяСтрока, РазделительКолонок = ";") Экспорт
Результат = Новый Массив;
ТекущееПоле = "";
ВнутриКавычек = Ложь;
ДлинаСтроки = СтрДлина(ФизическаяСтрока);
Индекс = 1;
Пока Индекс <= ДлинаСтроки Цикл
Символ = Сред(ФизическаяСтрока, Индекс, 1);
Если Символ = """" Тогда
// Проверим, не является ли это экранированной кавычкой (две кавычки подряд)
Если Индекс < ДлинаСтроки И Сред(ФизическаяСтрока, Индекс + 1, 1) = """" Тогда
ТекущееПоле = ТекущееПоле + """";
Индекс = Индекс + 1; // Пропускаем вторую кавычку
Иначе
// Переключаем режим нахождения внутри кавычек
ВнутриКавычек = Не ВнутриКавычек;
КонецЕсли;
ИначеЕсли Символ = РазделительКолонок И Не ВнутриКавычек Тогда
// Встретили разделитель вне кавычек — фиксируем поле
Результат.Добавить(ТекущееПоле);
ТекущееПоле = "";
Иначе
// Обычный символ (включая перевод строки или разделитель внутри кавычек)
ТекущееПоле = ТекущееПоле + Символ;
КонецЕсли;
Индекс = Индекс + 1;
КонецЦикла;
// Добавляем последнее поле
Результат.Добавить(ТекущееПоле);
Возврат Результат;
КонецФункции
Теперь проанализируем, как организовать цикл чтения самого файла. Поскольку файл содержит переносы строк внутри ячеек, метод ПрочитатьСтроку() вернет нам лишь «огрызок» логической строки. Мы должны продолжать читать физические строки и склеивать их через символ перевода строки до тех пор, пока количество кавычек в склеенном блоке не станет четным (что означает закрытие всех текстовых блоков).
Посмотрим на пример реализации основного цикла чтения файла:
Процедура ИмпортироватьСложныйCSV(ПутьКФайлу)
ФайлЧтение = Новый ЧтениеТекста(ПутьКФайлу, КодировкаТекста.UTF8);
ТаблицаДанных = Новый ТаблицаЗначений;
// Предположим, у нас в CSV 3 колонки: Код, Наименование, ОписаниеHTML
ТаблицаДанных.Колонки.Добавить("Код");
ТаблицаДанных.Колонки.Добавить("Наименование");
ТаблицаДанных.Колонки.Добавить("ОписаниеHTML");
БуферСтроки = "";
КоличествоКавычек = 0;
ФизическаяСтрока = ФайлЧтение.ПрочитатьСтроку();
Пока ФизическаяСтрока <> Неопределено Цикл
// Считаем количество кавычек в новой прочитанной строке
ЧислоКавычекСтроки = СтрЧислоВхождений(ФизическаяСтрока, """");
КоличествоКавычек = КоличествоКавычек + ЧислоКавычекСтроки;
БуферСтроки = БуферСтроки + ?(БуферСтроки = "", "", Символы.ПС) + ФизическаяСтрока;
// Если количество кавычек четное — логическая строка завершена полностью
Если КоличествоКавычек % 2 = 0 Тогда
// Отправляем накопленную многострочную запись на разбор по колонкам
МассивЯчеек = РазобратьСтрокуCSV(БуферСтроки, ";");
// Заполняем таблицу значений (проверяем соответствие размерности колонок)
Если МассивЯчеек.Количество() >= 3 Тогда
НоваяСтрока = ТаблицаДанных.Добавить();
НоваяСтрока.Код = МассивЯчеек[0];
НоваяСтрока.Наименование = МассивЯчеек[1];
НоваяСтрока.ОписаниеHTML = МассивЯчеек[2];
КонецЕсли;
// Сбрасываем накопители для следующей логической строки
БуферСтроки = "";
КоличествоКавычек = 0;
КонецЕсли;
ФизическаяСтрока = ФайлЧтение.ПрочитатьСтроку();
КонецЦикла;
ФайлЧтение.Закрыть();
// Данные успешно загружены в ТаблицуЗначений с сохранением всех HTML-переносов
ВыполнитьОбработкуДанных(ТаблицаДанных);
КонецПроцедуры
---
Если вам не хочется писать сложные циклы разбора на встроенном языке 1С или файлы имеют огромные размеры (сотни тысяч строк), обработка которых в цикле может замедлить систему, можно использовать внешние механизмы операционной системы Windows. Технология Microsoft ActiveX Data Objects (ADO) позволяет прочитать текстовый файл CSV как обычную таблицу реляционной базы данных с помощью SQL-запроса.
Для того чтобы встроенный OLEDB-драйвер Windows понял сложную структуру файла, кодировку и правила разделителей, нам потребуется создать файл конфигурации Schema.ini в той же папке, где лежит наш CSV-файл.
Рассмотрим по шагам этот элегантный метод:
Schema.ini.Проанализируем программный код реализации этого метода в 1С:
Процедура ЗагрузитьCSVЧерезADO(ПолныйПутьКФайлу)
// Разбиваем путь на каталог и имя файла
ФайлНаДиске = Новый Файл(ПолныйПутьКФайлу);
КаталогФайла = ФайлНаДиске.Путь;
ИмяФайла = ФайлНаДиске.Имя;
// Шаг 1. Создаем файл конфигурации Schema.ini в каталоге источника
ТекстШеroot = Новый ЗаписьТекста(КаталогФайла + "Schema.ini", КодировкаТекста.ANSI);
ТекстШеroot.ЗаписатьСтроку("[" + ИмяФайла + "]");
ТекстШеroot.ЗаписатьСтроку("Format=Delimited(;)"); // Указываем разделитель ';'
ТекстШеroot.ЗаписатьСтроку("ColNameHeader=True"); // Первая строка содержит заголовки
ТекстШеroot.ЗаписатьСтроку("CharacterSet=ANSI"); // Кодировка файла (или UTF-8)
ТекстШеroot.Закрыть();
// Шаг 2. Настраиваем подключение ADODB
Соединение = Новый COMОбъект("ADODB.Connection");
СтрокаПодключения = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + КаталогФайла + ";Extended Properties='text;HDR=YES;FMT=Delimited';";
Попытка
Соединение.Open(СтрокаПодключения);
Исключение
// Если драйвер ACE отсутствует, пробуем более старый Jet
СтрокаПодключения = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + КаталогФайла + ";Extended Properties='text;HDR=YES;FMT=Delimited';";
Соединение.Open(СтрокаПодключения);
КонецПопытки;
// Шаг 3. Выполняем SQL-запрос к текстовому файлу как к таблице
Рекордсет = Новый COMОбъект("ADODB.Recordset");
ТекстЗапроса = "SELECT * FROM [" + ИмяФайла + "]";
Рекордсет.Open(ТекстЗапроса, Соединение);
// Шаг 4. Читаем данные
Пока Не Рекордсет.EOF Цикл
Код = Рекордсет.Fields.Item("Код").Value;
Наименование = Рекордсет.Fields.Item("Наименование").Value;
ОписаниеHTML = Рекордсет.Fields.Item("ОписаниеHTML").Value; // Переносы строк здесь сохранятся идеально!
// Обрабатываем полученную запись...
Рекордсет.MoveNext();
КонецЦикла;
// Шаг 5. Закрываем соединения и прибираем за собой временный файл конфигурации
Рекордсет.Close();
Соединение.Close();
Попытка
УдалитьФайлы(КаталогФайла + "Schema.ini");
Исключение
// Файл заблокирован или уже удален
КонецПопытки;
КонецПроцедуры
Важное преимущество метода ADO: всю рутинную работу по парсингу кавычек, экранированию, обработке запятых и точек с запятой внутри HTML-описаний на себя берет высокооптимизированный системный драйвер баз данных Microsoft. Этот метод работает крайне быстро и не требует от разработчика ручного посимвольного перебора огромных текстовых строк на встроенном языке 1С.
---
В случаях, когда файлы присылаются в максимально нестандартном виде, а на сервере 1С установлен пакет MS Office или LibreOffice, мы можем пойти по пути наименьшего сопротивления. Поскольку офисные пакеты идеально умеют импортировать «кривые» файлы формата CSV, мы можем использовать их как промежуточный конвертер для перевода данных в стандартный табличный документ xls или xlsx.
Рассмотрим пример автоматического преобразования CSV в формат XLS с использованием COM-объекта Excel:
Функция КонвертироватьCSVвXLS(ПутьКCSV, ПутьКXLS) Экспорт
Попытка
Эксель = Новый COMОбъект("Excel.Application");
Исключение
ЗаписатьЛогСобытий("Не удалось запустить Excel для конвертации!");
Возврат Ложь;
КонецПопытки;
Эксель.DisplayAlerts = Ложь;
// Открываем CSV-файл с указанием параметров разбора
// Нам важен параметр DataType = 1 (разграниченный текст) и Semicolon = True (разделитель точка с запятой)
Книги = Эксель.Workbooks;
Книга = Книги.Open(ПутьКCSV);
// Сохраняем в полноценный формат Excel (xlOpenXMLWorkbook = 51)
Книга.SaveAs(ПутьКXLS, 51);
Книга.Close();
Эксель.Quit();
Возврат Истина;
КонецФункции
После выполнения данной процедуры мы получаем чистый, структурированный файл xlsx, который 1С способна прочитать штатными встроенными средствами платформы (например, через объект ТабличныйДокумент) всего в несколько строк кода без написания кастомных парсеров:
ТабДок = Новый ТабличныйДокумент;
ТабДок.Прочитать(ПутьКXLS, СпособЧтенияЗначенийТабличногоДокумента.Значение);
Для НомерСтроки = 1 По ТабДок.ВысотаТаблицы Цикл
Код = ТабДок.Область(НомерСтроки, 1).Текст;
Наименование = ТабДок.Область(НомерСтроки, 2).Текст;
ОписаниеHTML = ТабДок.Область(НомерСтроки, 3).Текст; // Все переносы строк импортированы корректно!
КонецЦикла;
---
Мы подробно разобрали три принципиально разных подхода к решению проблемы чтения сложных CSV-файлов в 1С. Подведем итоги и определимся, какой метод в какой ситуации предпочтительнее использовать:
Применяйте данные подходы в зависимости от вашей архитектуры, и вам больше не придется спорить с контрагентами о форматах выгрузки данных!