При интеграции и загрузке данных из внешних систем разработчики часто сталкиваются с форматом CSV. Типичная задача — разбить строку с разделителями (например, точкой с запятой) на отдельные колонки. Однако если ячейка содержит текст, внутри которого уже присутствует точка с запятой (например: "Значение 1";"Другое значение; с точкой запятой";"Еще значение"), стандартные попытки разделения строки могут дать некорректный результат.
Разберем причины возникновения этой ошибки, выясним особенности встроенных функций платформы 1С и рассмотрим проверенные способы корректного разбора таких данных.
Проанализируем поведение функции СтрРазделить. Часто разработчики пытаются передать в качестве разделителя комбинацию символов кавычки и точки с запятой: Разделитель = Символ(34) + ";" + Символ(34). Однако результат оказывается ошибочным.
Причина кроется в механике работы платформы: параметр Разделитель функции СтрРазделить воспринимается платформой как набор отдельных символов, каждый из которых является самостоятельным разделителем. Если передать туда строку ";", платформа разобьет исходный текст по каждому вхождению символа кавычки " и по каждому вхождению символа ;, что приведет к появлению множества пустых и некорректных элементов массива.
Кроме того, в платформе 1С отсутствует нативный синтаксический анализатор стандарта RFC 4180, поэтому функция не умеет определять, находится ли разделитель внутри экранированного блока текста или нет.
Если структура файла гарантированно простая (все поля обрамлены кавычками, а внутри самих значений нет переносов строк и сдвоенных кавычек), можно использовать быстрое строковое преобразование. Для этого мы отсекаем внешние кавычки строки, заменяем составной разделитель ";" на символ перевода строки и затем считываем полученный многострочный текст.
Посмотрим на пример реализации данного алгоритма:
// Исходная строка с разделителями внутри кавычек
ИсхСтрока = """Значение 1"";""Другое значение; с точкой запятой"";""Еще значение""";
// Убираем первую и последнюю кавычки
Если Лев(ИсхСтрока, 1) = """" И Прав(ИсхСтрока, 1) = """" Тогда
ИсхСтрока = Сред(ИсхСтрока, 2, СтрДлина(ИсхСтрока) - 2);
КонецЕсли;
// Заменяем разделитель составного вида (кавычка-точка_с_запятой-кавычка) на перевод строки
РазделительКолонок = Символ(34) + ";" + Символ(34);
МногострочныйТекст = СтрЗаменить(ИсхСтрока, РазделительКолонок, Символы.ПС);
// Заполняем массив значений
МассивКолонок = Новый Массив;
КоличествоСтрок = СтрЧислоСтрок(МногострочныйТекст);
Для НомерСтроки = 1 По КоличествоСтрок Цикл
ЗначениеКолонки = СтрПолучитьСтроку(МногострочныйТекст, НомерСтроки);
МассивКолонок.Добавить(ЗначениеКолонки);
КонецЦикла;
Данный вариант работает очень быстро и не требует привлечения сторонних COM-объектов, однако он чувствителен к нестандартному форматированию (например, если часть ячеек в файле идет без кавычек).
Рассмотрим наиболее надежный и кроссплатформенный подход, который работает как на Windows, так и на Linux-серверах. Этот алгоритм полностью учитывает правила формата CSV:
; только тогда, когда он находится вне кавычек."" в одну кавычку ".Разберем программную реализацию функции парсинга строки CSV:
Функция РазобратьСтрокуCSV(Знач СтрCSV, Разделитель = ";") Экспорт
Результат = Новый Массив;
ТекущееЗначение = "";
ВнутриКавычек = Ложь;
ДлинаСтроки = СтрДлина(СтрCSV);
Индекс = 1;
Пока Индекс <= ДлинаСтроки Цикл
СимволТек = Сред(СтрCSV, Индекс, 1);
Если СимволТек = """" Тогда
// Проверяем экранирование: сдвоенная ли кавычка идет следом
Если ВнутриКавычек И Индекс < ДлинаСтроки И Сред(СтрCSV, Индекс + 1, 1) = """" Тогда
ТекущееЗначение = ТекущееЗначение + """";
Индекс = Индекс + 1; // Пропускаем вторую кавычку из пары
Иначе
// Переключаем флаг нахождения внутри кавычек
ВнутриКавычек = НЕ ВнутриКавычек;
КонецЕсли;
ИначеЕсли СимволТек = Разделитель И НЕ ВнутриКавычек Тогда
// Достигли границы колонки
Результат.Добавить(ТекущееЗначение);
ТекущееЗначение = "";
Иначе
// Обычный символ значения ячейки
ТекущееЗначение = ТекущееЗначение + СимволТек;
КонецЕсли;
Индекс = Индекс + 1;
КонецЦикла;
// Добавляем последнее значение строки
Результат.Добавить(ТекущееЗначение);
Возврат Результат;
КонецФункции
Для файловых или клиент-серверных баз, работающих под управлением операционной системы Windows, можно использовать объект VBScript.RegExp. Регулярное выражение позволяет выделить содержимое каждого поля независимо от наличия внутри служебных знаков препинания.
Посмотрим на пример использования регулярного выражения:
РегВыр = Новый COMОбъект("VBScript.RegExp");
РегВыр.Global = Истина;
РегВыр.IgnoreCase = Истина;
// Шаблон разбирает строку на значения в кавычках и без них
РегВыр.Pattern = "(?:^|;)(?:""([^""]*(?:""""[^""]*)*)""|([^;]*))";
КоллекцияСовпадений = РегВыр.Execute(ИсхСтрока);
МассивРезультат = Новый Массив;
Для Каждого Совпадение Из КоллекцияСовпадений Цикл
// Проверяем захваченные подгруппы
Если Совпадение.SubMatches.Item(0) <> Неопределено Тогда
// Значение было в кавычках, заменяем сдвоенные кавычки на одинарные
ТекстЯчейки = СтрЗаменить(Совпадение.SubMatches.Item(0), """""", """");
Иначе
ТекстЯчейки = Совпадение.SubMatches.Item(1);
КонецЕсли;
МассивРезультат.Добавить(ТекстЯчейки);
КонецЦикла;
Резюме: для разовых простых задач с гарантированной структурой подходит замена через СтрЗаменить. Для надежной промышленной разработки и универсальных механизмов импорта рекомендуется использовать посимвольный разбор на базе конечного автомата, так как он обеспечивает полную независимость от операционной системы и стабильную работу с любыми данными.