Как эффективно обработать сложную CSV-строку с кавычками и запятыми в 1С

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

При обмене данными часто возникает ситуация, когда стандартные средства разделения строк, такие как СтрРазделить, не справляются с поставленной задачей. Проблема заключается в структуре формата CSV: внутри текстового поля могут находиться те же символы, что используются в качестве разделителей (запятые) или ограничителей (кавычки). Если просто разбивать строку по запятой, мы получим разорванные данные, которые невозможно корректно интерпретировать. Разберем, как решить эту задачу в 1С наиболее эффективно.

Анализ проблемы: почему не работает простой подход

Рассмотрим типовую строку, которая вызывает сложности: "Имя контрагента ""ООО Копыта""", 42, "Адрес, город, улица". Простой парсер увидит запятую внутри адреса и разделит его на три разные части. Кроме того, числа часто передаются без кавычек, а строки — в двойных кавычках, внутри которых могут быть еще одни кавычки. Наша задача — получить массив, где каждый элемент соответствует логическому полю, независимо от того, что находится внутри него.

Решение 1: Использование регулярных выражений

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

Паттерн: ("([^"]*)"|[^,]*)(,|$)

Этот паттерн работает по следующему алгоритму: он ищет либо блок текста, заключенный в кавычки, либо любую последовательность символов до ближайшей запятой или конца строки. Рассмотрим, как применить это в коде 1С через COM-объект VBScript.RegExp.


// Создаем объект регулярных выражений
RegExp = Новый COMОбъект("VBScript.RegExp");
RegExp.IgnoreCase = Истина;
RegExp.Global = Истина;
RegExp.MultiLine = Истина;

// Устанавливаем паттерн
RegExp.Pattern = "(""([^""]*)""|[^,]*)(,|$)";

// Исходная строка
ИсходнаяСтрока = "ПоступлениеТМЦ,""Имя контрагента """"ООО Копыта"""""",42";

// Выполняем поиск
Matches = RegExp.Execute(ИсходнаяСтрока);

Для Каждого Match Из Matches Цикл
    // Значение поля находится в первой подгруппе захвата
    ЗначениеПоля = Match.SubMatches(0);
    // Дополнительная очистка от обрамляющих кавычек
    ЗначениеПоля = СокрЛП(ЗначениеПоля);
    Если Лев(ЗначениеПоля, 1) = """" И Прав(ЗначениеПоля, 1) = """" Тогда
        ЗначениеПоля = Сред(ЗначениеПоля, 2, СтрДлина(ЗначениеПоля) - 2);
    КонецЕсли;
    Сообщить(ЗначениеПоля);
КонецЦикла;

Решение 2: Оптимизированный паттерн для стандарта RFC 4180

Если ваши данные содержат экранированные кавычки (удвоенные кавычки внутри строки), предыдущий паттерн может работать некорректно. Проанализируем более продвинутый вариант, который учитывает специфику стандарта CSV:

Паттерн: (?<=^|,)(?:"([^"]*(?:""[^"]*)*)"|([^,",]*))(?=,|$)

Этот вариант использует механизмы "просмотра вперед" и "просмотра назад", что позволяет точнее определять границы полей. Он разделяет захват на две группы: первая для данных в кавычках (с учетом удвоенных кавычек), вторая — для чисел и простых строк без кавычек.

Решение 3: Встроенный объект ЧтениеCSV (для современных платформ)

Начиная с версии 8.3.18, в платформе 1С появился специализированный объект ЧтениеCSV. Это самый быстрый и надежный способ. Выясним, почему его стоит предпочесть регулярным выражениям:

  1. Производительность: Реализован на уровне ядра платформы (C++), работает быстрее любого скриптового метода.
  2. Надежность: Автоматически обрабатывает многострочные поля (когда перенос строки находится внутри кавычек).
  3. Гибкость: Позволяет настроить любые символы разделителей.

Посмотрим на пример использования этого объекта:


// Настройка параметров чтения
ПараметрыЧтения = Новый ПараметрыЧтенияCSV;
ПараметрыЧтения.РазделительПолей = ",";
ПараметрыЧтения.СимволЦитаты = """";

Чтение = Новый ЧтениеCSV;
// Если данные в строке, используем ЧтениеТекста как посредник
Текст = Новый ЧтениеТекста;
Текст.УстановитьСодержимое(ИсходнаяСтрока);
Чтение.Открыть(Текст, ПараметрыЧтения);

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

Решение 4: Высокая производительность через ADODB

Если вам нужно обработать десятки тысяч строк (как упоминал автор темы), и вы работаете в среде Windows, можно использовать ADODB.Recordset. Этот метод позволяет обращаться к текстовому файлу как к таблице базы данных. Проанализируем этот подход:

Преимущества:


Connection = Новый COMОбъект("ADODB.Connection");
Recordset  = Новый COMОбъект("ADODB.Recordset");

// Указываем путь к папке с файлом
ПутьКПапке = "C:\Data\";
Файл = "data.csv";

СтрокаПодключения = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + ПутьКПапке + ";Extended Properties=""text;HDR=No;FMT=Delimited"";";
Connection.Open(СтрокаПодключения);

// Выполняем SQL-запрос к текстовому файлу
Recordset.Open("SELECT * FROM [" + Файл + "]", Connection);

Пока НЕ Recordset.EOF Цикл
    Для НомерПоля = 0 По Recordset.Fields.Count - 1 Цикл
        Значение = Recordset.Fields.Item(НомерПоля).Value;
        // Обработка значения
    КонецЦикла;
    Recordset.MoveNext();
КонецЦикла;

Важные рекомендации по оптимизации

Если вы все же решили использовать регулярные выражения, помните о производительности. Создание объекта COMОбъект("VBScript.RegExp") внутри цикла по строкам — это критическая ошибка, которая замедлит обработку в сотни раз. Рассмотрим правильный алгоритм:

  1. Создаем объект RegExp один раз перед началом цикла.
  2. Компилируем паттерн (устанавливаем свойство Pattern).
  3. Внутри цикла вызываем только метод Execute.
  4. Если данных очень много, рассмотрите возможность предварительной подготовки строки (например, замена специфических комбинаций символов), чтобы упростить работу парсера.

В ситуациях, когда исходные данные крайне "грязные" (например, кавычки не закрыты или расставлены хаотично), регулярные выражения могут не справиться. В таких случаях мы рекомендуем использовать метод "Автомата состояний" — посимвольный обход строки с флагом ВнутриКавычек. Это гарантирует 100% контроль над логикой парсинга.

← На главную