Как в 1С прочитать CSV-файл с многострочными значениями внутри полей?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При выгрузке отчетов и таблиц из Microsoft Excel в формат CSV часто возникает ситуация, когда в тексте ячеек присутствуют переносы строк (созданные пользователями через сочетание клавиш Alt+Enter). При попытке загрузить такой файл типовыми средствами платформы 1С строки таблицы разрываются, и структура колонок нарушается.

Разберем природу этой проблемы, выясним, почему привычный объект ТекстовыйДокумент разделяет строки не так, как ожидается, и рассмотрим эффективные варианты решения задачи непосредственно в коде 1С.

Почему ТекстовыйДокумент разбивает строки по внутренним переносам

Проанализируем структуру файла, создаваемого Excel при экспорте в CSV. Реальное окончание строки таблицы разделяется последовательностью символов возврата каретки и перевода строки — CRLF (в терминах встроенного языка: Символы.ВК + Символы.ПС, байты 13 и 10). Однако внутри ячеек принудительный перенос сохраняется только как одиночный перевод строки — LF (Символы.ПС, байт 10).

Разработчики часто пытаются указать явный разделитель при чтении файла через метод Прочитать() объекта ТекстовыйДокумент:


ТекстДок = Новый ТекстовыйДокумент;
ТекстДок.Прочитать(ИмяФайла, "UTF-8", Символы.ВК + Символы.ПС);

Однако такой подход не решает проблему. Выясним причину такого поведения платформы: внутри объекта ТекстовыйДокумент единственным системным разделителем строк всегда является Символы.ПС. Параметр разделителя в методе Прочитать() лишь указывает, какую именно последовательность байтов из входящего файла требуется преобразовать в системный перевод строки. Любой одиночный Символы.ПС платформа безусловно считает окончанием строки документа и разбивает текст на отдельные элементы коллекции. Кроме того, загрузка больших CSV-файлов целиком через ТекстовыйДокумент нерационально расходует оперативную память сервера.

Способ 1. Использование возврата каретки в качестве разделителя

Рассмотрим изящный способ обхода системной конвертации, предложенный участниками обсуждения. Если внутри многострочных ячеек гарантированно присутствует только одиночный символ Символы.ПС, а строки файла разделяются парой Символы.ВК + Символы.ПС, мы можем указать в качестве разделителя строк только возврат каретки Символы.ВК.

Разберем реализацию этого алгоритма с помощью потокового объекта ЧтениеТекста:


ФайлЧтения = Новый ЧтениеТекста(ИмяФайла, КодировкаТекста.UTF8, Символы.ВК, "");

Пока Истина Цикл
    СтрокаДанных = ФайлЧтения.ПрочитатьСтроку();
    Если СтрокаДанных = Неопределено Тогда
        Прервать;
    КонецЕсли;
    
    // В начале строки может оставаться символ ПС от предыдущего разделителя
    СтрокаДанных = СокрЛП(СтрокаДанных);
    
    Если ПустаяСтрока(СтрокаДанных) Тогда
        Продолжить;
    КонецЕсли;
    
    // Передаем строку на синтаксический разбор по колонкам
    ОбработатьСтрокуCSV(СтрокаДанных);
КонецЦикла;

ФайлЧтения.Закрыть();

Обратим внимание на четвертый параметр конструктора ЧтениеТекста. По умолчанию параметр КонвертируемыйРазделительСтрок содержит последовательность Символы.ВК + Символы.ПС. Передавая туда пустую строку "", мы отключаем стандартное преобразование и получаем возможность разделить файл строго по границам записей.

Способ 2. Потоковое чтение с контролем парности кавычек (стандарт RFC 4180)

Посмотрим на общепринятый стандарт разбора CSV-файлов. Согласно спецификации RFC 4180, ячейка, содержащая символы перевода строки или разделители колонок, обязательно обрамляется двойными кавычками, а внутренние кавычки дублируются. Это дает возможность отслеживать логическую целостность строки: если общее количество кавычек в считанной строке нечетное, значит, поле не закрыто и внутри ячейки встретился перенос строки.

Разберем по шагам реализацию потокового чтения с объединением многострочных строк:

  1. Открываем файл для чтения с помощью объекта ЧтениеТекста.
  2. Считываем порцию текста методом ПрочитатьСтроку().
  3. Подсчитываем количество символов двойных кавычек в текущем буфере строки.
  4. Если количество кавычек нечетное, в цикле дочитываем следующую строку из файла и соединяем ее с предыдущей через Символы.ПС до тех пор, пока количество кавычек не станет четным.
  5. Готовую цельную строку передаем в процедуру разбора колонок.

Посмотрим на практический пример кода:


Функция КоличествоСимволовВСтроке(ИсходнаяСтрока, ИскомыйСимвол)
    Возврат СтрДлина(ИсходнаяСтрока) - СтрДлина(СтрЗаменить(ИсходнаяСтрока, ИскомыйСимвол, ""));
КонецФункции

Процедура ПрочитатьФайлCSV(ИмяФайла)
    ФайлЧтения = Новый ЧтениеТекста;
    ФайлЧтения.Открыть(ИмяФайла, КодировкаТекста.UTF8);
    
    ТекущаяСтрокаCSV = "";
    Кавычка = """";
    
    Пока Истина Цикл
        СтрокаЧасть = ФайлЧтения.ПрочитатьСтроку();
        Если СтрокаЧасть = Неопределено Тогда
            Прервать;
        КонецЕсли;
        
        Если ПустаяСтрока(ТекущаяСтрокаCSV) Тогда
            ТекущаяСтрокаCSV = СтрокаЧасть;
        Иначе
            ТекущаяСтрокаCSV = ТекущаяСтрокаCSV + Символы.ПС + СтрокаЧасть;
        КонецЕсли;
        
        // Проверяем четность кавычек
        ЧислоКавычек = КоличествоСимволовВСтроке(ТекущаяСтрокаCSV, Кавычка);
        Если ЧислоКавычек % 2 = 0 Тогда
            // Строка завершена, выполняем разбор полей
            РазобратьСтрокуПоКолонкам(ТекущаяСтрокаCSV);
            ТекущаяСтрокаCSV = "";
        КонецЕсли;
    КонецЦикла;
    
    ФайлЧтения.Закрыть();
КонецПроцедуры

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

Способ 3. Использование COM-соединения через ADO

Если разбор выполняется на сервере под управлением ОС Windows, мы можем проанализировать альтернативный вариант: чтение CSV-файла как таблицы реляционной базы данных с помощью драйверов Microsoft Text Driver (*.txt; *.csv) или Microsoft.ACE.OLEDB.

Драйвер OLE DB самостоятельно интерпретирует спецификацию CSV, корректно считывает многострочные ячейки внутри кавычек и возвращает готовый набор записей ADODB.Recordset. Это исключает необходимость вручную собирать строки и обрабатывать экранирование спецсимволов.

← На главную