При обмене данными через файлы формата CSV (Comma-Separated Values) часто возникает ситуация, когда в качестве разделителя колонок используется запятая, но при этом в самом тексте данных (например, в наименованиях номенклатуры, комментариях или адресах) также присутствуют запятые. Стандартное разбиение строки функцией СтрРазделить() в таких случаях приводит к ошибкам: одна колонка ошибочно разделяется на несколько частей, и вся структура таблицы сдвигается.
Разберем по шагам, как устроен стандарт работы с такими файлами и какие существуют надежные программные способы обработки данных в платформе 1С:Предприятие.
Проанализируем структуру корректно сформированного CSV-файла. Согласно стандарту RFC 4180, если текстовое поле содержит служебные символы (разделитель колонок, кавычки или перевод строки), оно обязательно должно быть обрамлено двойными кавычками (символ Quote Char):
1,Есть,"Если не утро, то день",20,Фабрика
Если внутри самого текста также встречаются кавычки, они удваиваются:
2,Есть,"ООО ""Рога и Копыта"", г. Москва",150,Склад
Рассмотрим различные подходы к разбору таких файлов в 1С: от готовых платформенных механизмов до собственного отказоустойчивого парсера.
Это наиболее надежный алгоритм разбора, работающий на чистом коде 1С без использования сторонних библиотек и компонентов. Мы обходим строку символ за символом и отслеживаем состояние: находимся ли мы сейчас внутри текста в кавычках или в области обычных разделителей.
Посмотрим на пример универсальной функции, которая раскладывает CSV-строку в массив колонок с учетом кавычек и экранирования:
Функция РазложитьCSVСтроку(Знач Стр, Знач Разделитель = ",")
МассивКолонок = Новый Массив;
ТекущееЗначение = "";
ВнутриКавычек = Ложь;
ДлинаСтроки = СтрДлина(Стр);
Индекс = 1;
Пока Индекс <= ДлинаСтроки Цикл
Символ = Сред(Стр, Индекс, 1);
Если Символ = """" Тогда
// Проверяем удвоенную кавычку (экранирование) внутри текста в кавычках
Если ВнутриКавычек И (Индекс < ДлинаСтроки) И (Сред(Стр, Индекс + 1, 1) = """") Тогда
ТекущееЗначение = ТекущееЗначение + """";
Индекс = Индекс + 1; // Пропускаем вторую экранирующую кавычку
Иначе
// Переключаем флаг нахождения внутри кавычек
ВнутриКавычек = Не ВнутриКавычек;
КонецЕсли;
ИначеЕсли (Символ = Разделитель) И (Не ВнутриКавычек) Тогда
// Встретили настоящий разделитель колонок
МассивКолонок.Добавить(ТекущееЗначение);
ТекущееЗначение = "";
Иначе
// Обычный символ данных
ТекущееЗначение = ТекущееЗначение + Символ;
КонецЕсли;
Индекс = Индекс + 1;
КонецЦикла;
// Добавляем последнее значение строки
МассивКолонок.Добавить(ТекущееЗначение);
Возврат МассивКолонок;
КонецФункции
Разберем, как прочитать весь файл целиком с помощью объекта ЧтениеТекста:
Процедура ПрочитатьCSVФайл(ПутьКФайлу)
Чтение = Новый ЧтениеТекста(ПутьКФайлу, КодировкаТекста.UTF8);
СтрокаФайла = Чтение.ПрочитатьСтроку();
Пока СтрокаФайла <> Неопределено Цикл
// Пропускаем пустые строки
Если Не ПустаяСтрока(СокрЛП(СтрокаФайла)) Тогда
Колонки = РазложитьCSVСтроку(СтрокаФайла, ",");
// Обработка полученных значений
// Колонки[0], Колонки[1] и т.д.
КонецЕсли;
СтрокаФайла = Чтение.ПрочитатьСтроку();
КонецЦикла;
Чтение.Закрыть();
КонецПроцедуры
Платформа 1С содержит встроенный быстрый парсер табличных файлов, включая CSV. Метод ТабличныйДокумент.Прочитать() корректно понимает кавычки и запятые внутри значений ячеек.
Рассмотрим, как загрузить CSV через табличный документ:
Процедура ЗагрузитьCSVЧерезТабличныйДокумент(ПутьКФайлу)
ТабДок = Новый ТабличныйДокумент;
ТабДок.Прочитать(ПутьКФайлу, СпособЧтенияЗначенийТабличногоДокумента.Текст);
ВсегоСтрок = ТабДок.ВысотаТаблицы;
ВсегоКолонок = ТабДок.ШиринаТаблицы;
Для НомерСтроки = 1 По ВсегоСтрок Цикл
Для НомерКолонки = 1 По ВсегоКолонок Цикл
ЗначениеЯчейки = ТабДок.Область(НомерСтроки, НомерКолонки).Текст;
// Обработка данных ячейки
КонецЦикла;
КонецЦикла;
КонецПроцедуры
Преимущества: простота реализации и высокая скорость открытия файла силами платформы без написания циклов посимвольного разбора.
В типовых конфигурациях (1С:ERP, 1С:Управление торговлей, 1С:Комплексная автоматизация, 1С:Бухгалтерия) уже реализованы протестированные функции работы со строками в общем модуле СтроковыеФункцииКлиентСервер или в подсистеме «Загрузка данных из внешних файлов».
В зависимости от версии БСП можно использовать готовую функцию:
МассивЗначений = СтроковыеФункцииКлиентСервер.РазложитьСтрокуСУчетомКавычек(СтрокаФайла, ",", """");
Иногда выгрузка сформирована некорректно сторонней системой, и запятая попала в строку без экранирования кавычками, например: 1,Есть,Если не утро, то день,20,Фабрика (в строке 5 колонок по логике, но 6 фрагментов из-за разделителя внутри текста).
Если общее количество колонок строго фиксировано (например, 50 колонок), а проблемное поле только одно и находится на известной позиции (например, колонка 3), применим двунаправленный алгоритм разбора:
СтрРазделить(Строка, ",").Посмотрим на реализацию такой логики:
Функция СобратьПоврежденнуюСтроку(Знач СтрокаCSV, Знач ОжидаемоКолонок, Знач ИндексПроблемнойКолонки)
Части = СтрРазделить(СтрокаCSV, ",");
Если Части.Количество() <= ОжидаемоКолонок Тогда
Возврат Части;
КонецЕсли;
Результат = Новый Массив;
// 1. Заполняем колонки до проблемной
Для Сч = 0 По ИндексПроблемнойКолонки - 1 Цикл
Результат.Добавить(Части[Сч]);
КонецЦикла;
// 2. Склеиваем среднюю часть, разбитую лишними запятыми
ЛишнихЗапятых = Части.Количество() - ОжидаемоКолонок;
ИндексКонцаПроблемной = ИндексПроблемнойКолонки + ЛишнихЗапятых;
ПроблемныйТекст = "";
Для Сч = ИндексПроблемнойКолонки По ИндексКонцаПроблемной Цикл
ПроблемныйТекст = ПроблемныйТекст + ?(ПустаяСтрока(ПроблемныйТекст), "", ",") + Части[Сч];
КонецЦикла;
Результат.Добавить(ПроблемныйТекст);
// 3. Заполняем оставшиеся колонки с конца
Для Сч = ИндексКонцаПроблемной + 1 По Части.Количество() - 1 Цикл
Результат.Добавить(Части[Сч]);
КонецЦикла;
Возврат Результат;
КонецФункции
ТабличныйДокумент.Прочитать().СтроковыеФункцииКлиентСервер.