Почему 1С при чтении CSV-файла некорректно обрабатывает кавычки в значениях колонок?

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

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

1. Диагностика проблемы: Невидимые символы и формат CSV

Первое, что мы должны сделать, столкнувшись с неожиданным поведением при чтении файла, это провести тщательную диагностику. Обычный текстовый редактор может скрывать от нас важные детали, такие как непечатаемые символы или особенности кодировки. В такой ситуации незаменимым инструментом становится HEX-редактор.

  1. Проверка через HEX-редактор:

    Начнем с того, что откроем проблемный CSV-файл в HEX-редакторе. Этот инструмент позволяет нам увидеть каждый байт файла в шестнадцатеричном представлении, а также его символьное соответствие. Часто именно здесь обнаруживаются "невидимые" символы, которые могут влиять на парсинг. Например, это могут быть скрытые символы новой строки, табуляции или даже служебные кавычки, которые обычный текстовый редактор не отображает явно.

    В нашем случае, как показал опыт, HEX-редактор действительно выявил наличие кавычек. Но самое интересное, что эти кавычки появлялись тогда, когда само значение поля уже содержало одну или несколько кавычек. Это ключевой момент, указывающий на понимание стандарта CSV.

  2. Понимание стандарта CSV и обработки кавычек:

    Дело в том, что формат CSV (Comma Separated Values) не имеет строгого и универсального стандарта, хотя чаще всего ссылаются на RFC 4180. Согласно этому стандарту и общепринятой практике:

    • Если значение поля содержит служебные символы (например, разделитель полей — запятая, точка с запятой или пробел в нашем случае; символ новой строки; или сами кавычки), то все это поле заключается в двойные кавычки.
    • Если внутри такого поля, уже заключенного в кавычки, встречается сама двойная кавычка, то она должна быть удвоена (экранирована).

    Приведем пример. Если у нас есть строка: `He said, "Hello"` и разделитель — запятая, то в CSV она будет выглядеть так: `"He said, ""Hello"""`. Обратите внимание, что внешние кавычки заключают все поле, а внутренняя кавычка, которая является частью текста, удвоена.

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

2. Почему простое разбиение по разделителю недостаточно?

Многие из нас, приступая к чтению CSV, пытаются решить задачу "в лоб" – прочитать строку, а затем разбить ее на части по символу-разделителю (например, пробелу или запятой) с помощью функции Разделить() или подобных методов. Однако, как мы с вами уже выяснили, это работает только для самых простых случаев, когда данные не содержат служебных символов.

Рассмотрим основные сложности:

  1. Разделитель внутри значения: Если, например, наш разделитель — запятая, а в поле "Адрес" есть "г. Москва, ул. Ленина", то простое разбиение по запятой даст нам два поля вместо одного.
  2. Кавычки внутри значения: Как в нашем текущем случае, если значение поля содержит кавычки (например, `Продукт "Супер"`), и все поле взято в кавычки (`"Продукт ""Супер"""`), то без специальной логики парсер воспримет `""Супер"""` как часть текста или некорректно разделит его.
  3. Символы новой строки внутри значения: Некоторые CSV-файлы могут содержать многострочные значения, где символ переноса строки (`\n` или `\r\n`) находится внутри поля, а не сигнализирует о конце записи. Если поле заключено в кавычки, парсер должен прочитать все строки до закрывающей кавычки.

Из-за этих сложностей для корректного парсинга "сложных" CSV-файлов необходим более продвинутый подход, часто реализуемый с помощью конечного автомата (Finite State Machine - FSM). Конечный автомат отслеживает текущее состояние парсера (например, находится ли он внутри поля в кавычках или нет, встретил ли он первую кавычку, или вторую для экранирования) и реагирует на каждый прочитанный символ соответствующим образом. Это позволяет правильно обрабатывать экранированные кавычки и многострочные поля.

3. Альтернативные и надежные методы чтения CSV в 1С

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

3.1. Реализация собственного парсера (по принципу конечного автомата)

Если у вас есть время и желание создать максимально гибкое и контролируемое решение, вы можете написать свой собственный парсер. Это потребует понимания логики работы с файлами посимвольно и отслеживания состояний. Вот как это работает на концептуальном уровне:

  1. Состояния парсера:
    • `ВнеПоля` (OutsideField): Парсер находится между полями или в начале строки.
    • `ВнутриОбычногоПоля` (InsideNormalField): Парсер читает поле, которое не заключено в кавычки.
    • `ВнутриПоляВКавычках` (InsideQuotedField): Парсер читает поле, заключенное в кавычки.
    • `ВозможнаяДвойнаяКавычка` (PossibleDoubleQuote): Парсер только что встретил кавычку внутри поля, заключенного в кавычки. Нужно определить, это закрывающая кавычка поля или экранированная внутренняя кавычка.
  2. Логика перехода между состояниями:
    • При чтении символа разделителя, если парсер находится в состоянии `ВнеПоля` или `ВнутриОбычногоПоля`, он заканчивает текущее поле и переходит к следующему.
    • При встрече кавычки:
      • Если это первая кавычка в начале поля, парсер переходит в состояние `ВнутриПоляВКавычках`.
      • Если это кавычка внутри `ВнутриПоляВКавычках`, парсер переходит в состояние `ВозможнаяДвойнаяКавычка`.
      • Если после `ВозможнаяДвойнаяКавычка` идет еще одна кавычка, это экранированная кавычка, и мы добавляем одну кавычку к значению поля, оставаясь в `ВнутриПоляВКавычках`.
      • Если после `ВозможнаяДвойнаяКавычка` идет разделитель или символ конца строки, это была закрывающая кавычка поля, и поле завершается.
    • Символы новой строки обрабатываются внутри `ВнутриПоляВКавычках` как часть значения, а не как конец записи.

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


Функция ПрочитатьCSVСтроку(ФайлТекста, Разделитель)
    // Здесь будет сложная логика конечного автомата
    // Чтение файла посимвольно или по небольшим кускам
    // Отслеживание состояний: ВнутриКавычек, ВнеКавычек
    // Обработка экранированных кавычек "" -> "
    // Сбор значений полей
КонецФункции;

Это может быть реализовано с использованием объекта ТекстЧтения или путем прямого чтения файла в бинарном режиме для полного контроля над байтами и кодировками.

3.2. Использование внешних источников данных или OLEDB

Один из самых надежных и, зачастую, простых способов работы со сложными CSV-файлами в 1С — это использование OLEDB-провайдеров. Эти провайдеры позволяют обращаться к текстовым файлам (включая CSV) как к обычным таблицам базы данных, используя SQL-запросы. При этом всю сложную логику парсинга, обработки разделителей, кавычек и экранирования берет на себя провайдер.

Для работы с текстовыми файлами через OLEDB мы обычно используем следующие провайдеры:

Разберем шаги по использованию OLEDB:

  1. Файл `schema.ini`:

    Для того чтобы OLEDB-провайдер корректно интерпретировал ваш CSV-файл, ему нужен "схема" — файл `schema.ini`. Этот файл должен находиться в той же папке, что и ваш CSV-файл. В нем мы указываем параметры текстового файла: название файла, разделитель, наличие заголовков, типы данных колонок и кодировку. Это критически важный шаг!

    Пример содержимого файла `schema.ini` для файла `ВашФайл.csv`:

    
    [ВашФайл.csv]
    ColNameHeader=True
    Format=Delimited( )
    MaxScanRows=0
    CharacterSet=OEM
    Col1=Колонка1 Text
    Col2=Колонка2 Text
    Col3=Колонка3 Text
    Col4=Колонка4 Text
    TextDelimiter="
    

    Пояснения к параметрам:

    • `[ВашФайл.csv]`: Указываем имя нашего CSV-файла.
    • `ColNameHeader=True`: Указываем, что первая строка файла содержит заголовки колонок. Если заголовков нет, поставьте `False`.
    • `Format=Delimited( )`: Указываем, что файл разделен символами. В скобках указываем символ-разделитель. В вашем случае это был пробел, поэтому `( )`. Для запятой было бы `(,)`, для табуляции `(Tab)`.
    • `MaxScanRows=0`: Провайдер сканирует указанное количество строк для определения типов данных. `0` означает сканировать весь файл.
    • `CharacterSet=OEM`: Указываем кодировку. `OEM` часто соответствует кодировке Windows-1251. Для UTF-8 используем `UNICODE` (или `65001`). Важно подобрать правильную кодировку, иначе будут "кракозябры".
    • `Col1=Колонка1 Text`: Определяем имена и типы данных для каждой колонки. `Text` — для текстовых данных. Можно использовать `Integer`, `Double`, `Date` и т.д.
    • `TextDelimiter="`: Явно указываем, что текстовые поля могут быть заключены в двойные кавычки. Это позволяет провайдеру правильно обрабатывать экранирование кавычек.

    Подготовив `schema.ini`, мы готовы к подключению.

  2. Подключение и чтение данных в 1С:

    В 1С мы можем использовать COM-объекты `ADODB.Connection` и `ADODB.Recordset` для выполнения SQL-запросов к нашему CSV-файлу.

    
    &НаКлиенте
    Процедура ПрочитатьCSVЧерезOLEDB()
    
        ПутьКФайлуCSV = "C:\МоиДокументы\ВашФайл.csv"; // Полный путь к CSV
        ПутьКПапке = КаталогФайла(ПутьКФайлуCSV); // Папка, где лежит CSV и schema.ini
        ИмяФайлаCSV = ИмяФайла(ПутьКФайлуCSV);
    
        Попытка
            // Создаем объект подключения ADODB
            Соединение = Новый COMОбъект("ADODB.Connection");
    
            // Строка подключения к текстовому файлу (для ACE OLEDB 12.0)
            // Если используется Microsoft.Jet.OLEDB.4.0, измените Provider
            СтрокаСоединения = "Provider=Microsoft.ACE.OLEDB.12.0;" +
                               "Data Source=""" + ПутьКПапке + """;" +
                               "Extended Properties=""Text;HDR=Yes;FMT=Delimited;IMEX=1;""";
                               // HDR=Yes - заголовки в первой строке (берется из ColNameHeader в schema.ini)
                               // FMT=Delimited - тип формата (тоже берется из schema.ini, но лучше указать)
                               // IMEX=1 - режим импорта, для чтения
    
            Соединение.Open(СтрокаСоединения);
    
            // Создаем объект Recordset для выполнения запроса
            НаборЗаписей = Новый COMОбъект("ADODB.Recordset");
    
            // SQL-запрос к нашему CSV-файлу как к таблице
            // Имя файла без расширения или с расширением, если schema.ini не указано
            // В нашем случае, если есть schema.ini, можно обращаться к имени файла.
            // Если ColNameHeader=True, то используем имена из первой строки CSV
            // или из ColN= в schema.ini
            ТекстЗапроса = "SELECT * FROM [" + ИмяФайлаCSV + "]";
    
            НаборЗаписей.Open(ТекстЗапроса, Соединение);
    
            // Если есть данные, обходим их
            Если Не НаборЗаписей.EOF Тогда
                НаборЗаписей.MoveFirst(); // Переходим на первую запись
    
                Пока Не НаборЗаписей.EOF Цикл
                    // Получаем значения полей
                    // Обращаемся по имени колонки, как в schema.ini или заголовках CSV
                    ЗначениеКолонки1 = НаборЗаписей.Fields.Item("Колонка1").Value;
                    ЗначениеКолонки2 = НаборЗаписей.Fields.Item("Колонка2").Value;
                    ЗначениеКолонки3 = НаборЗаписей.Fields.Item("Колонка3").Value;
                    ЗначениеКолонки4 = НаборЗаписей.Fields.Item("Колонка4").Value;
    
                    Сообщить("Строка: " + ЗначениеКолонки1 + ", " + ЗначениеКолонки2 + ", " + ЗначениеКолонки3 + ", " + ЗначениеКолонки4);
    
                    НаборЗаписей.MoveNext(); // Переходим к следующей записи
                КонецЦикла;
            Иначе
                Сообщить("Файл CSV пуст или содержит только заголовки.");
            КонецЕсли;
    
        Исключение
            Сообщить("Ошибка при чтении CSV через OLEDB: " + ОписаниеОшибки());
        КонецПопытки;
    
        // Закрываем объекты и освобождаем ресурсы
        Если НаборЗаписей <> Неопределено Тогда
            Если НаборЗаписей.State = 1 Тогда // adStateOpen
                НаборЗаписей.Close();
            КонецЕсли;
            НаборЗаписей = Неопределено;
        КонецЕсли;
    
        Если Соединение <> Неопределено Тогда
            Если Соединение.State = 1 Тогда // adStateOpen
                Соединение.Close();
            КонецЕсли;
            Соединение = Неопределено;
        КонецЕсли;
    
    КонецПроцедуры
    

    Этот подход значительно упрощает работу, так как провайдер `Microsoft.ACE.OLEDB` берет на себя всю сложность парсинга, включая обработку кавычек и экранированных символов, в соответствии с правилами, определенными в `schema.ini`.

  3. ВнешниеИсточникиДанных в 1С:

    Для более высокоуровневой работы с OLEDB в 1С можно использовать объект ВнешниеИсточникиДанных. Вы можете настроить внешний источник данных в конфигурации 1С, указав тип источника "Текстовые файлы" и параметры подключения. Это позволит вам обращаться к данным CSV через обычные запросы 1С, как к любой другой таблице в базе данных, что значительно упрощает интеграцию и дальнейшую работу с данными.

Заключение

Проблема некорректного чтения CSV-файлов в 1С, когда значения колонок берутся в кавычки, часто связана с тем, что стандартные методы чтения не учитывают правила экранирования кавычек и служебных символов в формате CSV. Мы с вами убедились, что диагностика с помощью HEX-редактора помогает выявить истинную причину – наличие кавычек внутри самих данных, которые должны быть экранированы.

Для решения этой проблемы мы рассмотрели два основных подхода:

  1. Реализация собственного парсера на основе принципа конечного автомата, который дает максимальный контроль над процессом чтения, но требует более сложной реализации.
  2. Использование OLEDB-провайдеров с файлом `schema.ini`, который является более простым и надежным решением, перекладывая всю сложность парсинга на внешний компонент.

Выбор метода зависит от ваших конкретных требований, сложности CSV-файла и имеющихся ресурсов. В большинстве случаев использование OLEDB будет более предпочтительным из-за его надежности и меньшей трудоемкости реализации. Надеемся, что этот подробный разбор поможет вам успешно справиться с чтением любых CSV-файлов в 1С!

← На главную